package xfer
import (
"strconv"
"strings"
"testing"
)
// The unversioned cards (RFC 0020) are framed by byte count, as file
// cards are: a uvfile's payload is SIZE bytes unless flag 0x0004 (or
// the deletion flag 0x0001, or SIZE 0) says it is not there, and a line
// inside it is never a card. Each card's span covers its payload, so
// the front can cut it out of what the child sees.
func TestUVCardsAreFramedByByteCount(t *testing.T) {
payload := "login TEXT x y\nclo" + "ne 3 1\nuvgimme secret.txt\npush evil CODE\n"
head := "uvfile a.txt 1700000000 0123456789abcdef0123456789abcdef01234567 " + strconv.Itoa(len(payload)) + " 0\n"
omitted := "uvfile b.txt 1700000001 89abcdef0123456789abcdef0123456789abcdef 42 4\n"
tomb := "uvfile c.txt 1700000002 - 0 1\n"
body := "pragma client-version 22800 20260101 000000\n" +
"pragma uv-hash da39a3ee5e6b4b0d3255bfef95601890afd80709\n" +
"pull SRV PROJ\n" +
head + payload +
omitted +
tomb +
"uvgimme d.txt\n" +
"igot 0123456789abcdef0123456789abcdef01234567\n"
pr := scanRequest([]byte(body))
if pr.uvBad != "" || pr.hasPush {
t.Fatalf("FAIL: a well-framed request failed closed: %q push=%v", pr.uvBad, pr.hasPush)
}
if pr.user != "" || pr.newClone || pr.projectCode != "" {
t.Errorf("FAIL: the uvfile payload was read as cards: user=%q clone=%v code=%q", pr.user, pr.newClone, pr.projectCode)
} else {
t.Log("ok: a line inside a uvfile payload is never a card")
}
kinds := []string{}
for _, c := range pr.uv {
kinds = append(kinds, c.kind)
}
if got, want := strings.Join(kinds, ","), "uv-hash,uvfile,uvfile,uvfile,uvgimme"; got != want {
t.Fatalf("FAIL: cards = %v, want %v", kinds, want)
}
if !pr.uvWrite {
t.Error("FAIL: a uvfile did not mark the request as an unversioned write")
}
if got := string(pr.uv[1].payload); got != payload {
t.Errorf("FAIL: payload = %q", got)
} else {
t.Log("ok: the payload is exactly SIZE bytes")
}
if pr.uv[2].payload != nil || pr.uv[3].payload != nil {
t.Errorf("FAIL: an omitted payload was read: %q %q", pr.uv[2].payload, pr.uv[3].payload)
} else {
t.Log("ok: flag 0x0004 and a deletion carry no payload")
}
if pr.uv[4].f[1] != "d.txt" {
t.Errorf("FAIL: uvgimme tokens %v", pr.uv[4].f)
}
dag := string(stripUV([]byte(body), pr.uv))
if want := "pragma client-version 22800 20260101 000000\npull SRV PROJ\nigot 0123456789abcdef0123456789abcdef01234567\n"; dag != want {
t.Errorf("FAIL: what the child sees =\n%q\nwant\n%q", dag, want)
} else {
t.Log("ok: the child sees the request without a single unversioned card")
}
}
// An unversioned card the gate cannot frame fails closed: the request
// is a push (so a front with no unversioned seam behaves as it always
// did) and carries fossil's own words for the refusal.
func TestUnframableUVCardsFailClosed(t *testing.T) {
for name, body := range map[string]string{
"a short payload": "uvfile a.txt 1 0123456789abcdef0123456789abcdef01234567 99 0\nshort",
"too few tokens": "uvfile a.txt 1 0123456789abcdef0123456789abcdef01234567 5\nhello",
"too many tokens": "uvfile a.txt 1 0123456789abcdef0123456789abcdef01234567 5 0 x\nhello",
"a signed size": "uvfile a.txt 1 0123456789abcdef0123456789abcdef01234567 +5 0\nhello",
"flags that are not 10": "uvfile a.txt 1 0123456789abcdef0123456789abcdef01234567 5 0x4\nhello",
"a uvgimme of two": "uvgimme a.txt b.txt\n",
"a bare uvgimme": "uvgimme\n",
} {
pr := scanRequest([]byte(body))
if !pr.hasPush || pr.uvBad == "" {
t.Errorf("FAIL: %s did not fail closed (push=%v bad=%q)", name, pr.hasPush, pr.uvBad)
} else {
t.Logf("ok: %s fails closed", name)
}
}
}