package xfer
import (
"crypto/sha1"
"encoding/hex"
"net/http"
"strconv"
"testing"
)
// A pushed file's CONTENT is never read as cards (zit#213). A Go file
// holding an SQL column named "login" became a login card for user
// "TEXT" that beat the real x-f-l-c cookie, so every push carrying
// that file was refused as TEXT. The payload here carries the exact
// line, plus a "clone" and a "push" line that used to flip newClone and
// overwrite the project code.
func TestFileContentIsNeverReadAsCards(t *testing.T) {
payload := "package oltp\n\tlog" + "in TEXT NOT NULL,\n" +
"clo" + "ne 3 1\n" + "pu" + "sh evil WRONGCODE\n"
body := []byte("pragma client-version 20260926 x y\n" +
"push SRV PROJ123\n" +
"file 0123456789abcdef0123456789abcdef01234567 " + strconv.Itoa(len(payload)) + "\n" +
payload +
"igot 89abcdef0123456789abcdef0123456789abcdef\n")
pr := scanRequest(body)
for what, bad := range map[string]bool{
"took a login card out of the file content": pr.user != "",
"read a clone card out of the file content": pr.newClone,
"took the project code out of the file": pr.projectCode != "PROJ123",
"lost the real push card": !pr.hasPush,
} {
if bad {
t.Errorf("FAIL: the scan %s", what)
} else {
t.Logf("ok: the scan never %s", what)
}
}
// Round 2+ of a push authenticates ONLY by the x-f-l-c cookie, whose
// nonce covers the whole body. The pusher must be the cookie's user,
// which is the path that failed: a test with a real body login card
// would pass even with the old preference for the body.
const secret = "8d4a1f2b9c0e7a6d5b3f8e1c2a9d4b7e6f0c3a15"
sum := sha1.Sum(body)
nonce := hex.EncodeToString(sum[:])
sig := sha1.Sum([]byte(nonce + secret))
req := probe(fossilUA)
req.AddCookie(&http.Cookie{Name: "x-f-l-c", Value: "login+alice+" + nonce + "+" + hex.EncodeToString(sig[:])})
f := New()
f.SetAuth(secrets{"alice": secret})
if r := f.reader("alpha", pr, req, body, false); r.Account != "alice" {
t.Errorf("FAIL: the round's reader is %q (claimed %q), want alice from the cookie", r.Account, r.Claimed)
} else {
t.Log("ok: the cookie's user is the reader, not a line inside a file")
}
}
// A payload card whose framing this gate cannot follow exactly as zit
// would makes the request a push, so it needs a verified credential.
// zit aborts such a request anyway; the gate must never let a
// difference in parsing become a write it did not authenticate.
func TestUnframableRequestsFailClosed(t *testing.T) {
for name, body := range map[string]string{
"a signed size": "file 0123456789abcdef0123456789abcdef01234567 +5\nhello",
"a size with a gap": "file 0123456789abcdef0123456789abcdef01234567 1_0\n0123456789",
"a size past the end": "file 0123456789abcdef0123456789abcdef01234567 99\nshort",
"a bad arity": "file 5\nhello",
"config without LF": "config /x 3\nabcZ",
"a cfile in a request": "cfile 0123456789abcdef0123456789abcdef01234567 5 3\nabc",
} {
if pr := scanRequest([]byte(body)); !pr.hasPush {
t.Errorf("FAIL: %s did not fail closed", name)
} else {
t.Logf("ok: %s fails closed", name)
}
}
// A well-framed config card is read past, and a card after it counts.
if pr := scanRequest([]byte("config /x 3\nabc\npull aaaa bbbb\n")); !pr.hasPull || pr.hasPush {
t.Errorf("FAIL: a framed config card threw the scan off (pull=%v push=%v)", pr.hasPull, pr.hasPush)
} else {
t.Log("ok: a framed config card is skipped exactly")
}
}