// Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package httpsfv import ( "slices" "strconv" "strings" "testing" "time" ) func TestParseList(t *testing.T) { tests := []struct { name string in string wantMembers []string wantParams []string wantOk bool }{ { name: "valid list", in: `a, b,c`, wantMembers: []string{"a", "b", "c"}, wantParams: []string{"", "", ""}, wantOk: true, }, { name: "valid list with params", in: `a;foo=bar, b,c; baz=baz`, wantMembers: []string{"a", "b", "c"}, wantParams: []string{";foo=bar", "", "; baz=baz"}, wantOk: true, }, { name: "valid list with fake commas", in: `a;foo=",", (",")`, wantMembers: []string{"a", `(",")`}, wantParams: []string{`;foo=","`, ""}, wantOk: true, }, { name: "valid list with inner list member", in: `(a b c); foo, bar;baz`, wantMembers: []string{"(a b c)", "bar"}, wantParams: []string{"; foo", ";baz"}, wantOk: true, }, { name: "invalid list with trailing comma", in: `a;foo=bar, b,c; baz=baz,`, wantMembers: []string{"a", "b", "c"}, wantParams: []string{";foo=bar", "", "; baz=baz"}, }, { name: "invalid list with unclosed string", in: `", b, c,d`, }, } for _, tc := range tests { var gotMembers, gotParams []string f := func(member, param string) { gotMembers = append(gotMembers, member) gotParams = append(gotParams, param) } ok := ParseList(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if !slices.Equal(tc.wantMembers, gotMembers) { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotMembers, tc.wantMembers) } if !slices.Equal(tc.wantParams, gotParams) { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams) } } } func TestConsumeBareInnerList(t *testing.T) { tests := []struct { name string in string wantBareItems []string wantParams []string wantListParam string wantOk bool }{ { name: "valid inner list without param", in: `(a b c)`, wantBareItems: []string{"a", "b", "c"}, wantParams: []string{"", "", ""}, wantOk: true, }, { name: "valid inner list with param", in: `(a;d b c;e)`, wantBareItems: []string{"a", "b", "c"}, wantParams: []string{";d", "", ";e"}, wantOk: true, }, { name: "valid inner list with fake ending parenthesis", in: `(")";foo=")")`, wantBareItems: []string{`")"`}, wantParams: []string{`;foo=")"`}, wantOk: true, }, { name: "valid inner list with list parameter", in: `(a b;c); d`, wantBareItems: []string{"a", "b"}, wantParams: []string{"", ";c"}, wantOk: true, }, { name: "valid inner list with more content after", in: `(a b;c); d, a`, wantBareItems: []string{"a", "b"}, wantParams: []string{"", ";c"}, wantOk: true, }, { name: "invalid inner list", in: `(a b;c `, wantBareItems: []string{"a", "b"}, wantParams: []string{"", ";c"}, }, } for _, tc := range tests { var gotBareItems, gotParams []string f := func(bareItem, param string) { gotBareItems = append(gotBareItems, bareItem) gotParams = append(gotParams, param) } gotConsumed, gotRest, ok := consumeBareInnerList(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if !slices.Equal(tc.wantBareItems, gotBareItems) { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItems, tc.wantBareItems) } if !slices.Equal(tc.wantParams, gotParams) { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams) } if gotConsumed+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, gotConsumed, gotRest, tc.in) } } } func TestParseBareInnerList(t *testing.T) { tests := []struct { name string in string wantBareItems []string wantParams []string wantOk bool }{ { name: "valid inner list", in: `(a b;c)`, wantBareItems: []string{"a", "b"}, wantParams: []string{"", ";c"}, wantOk: true, }, { name: "valid inner list with list parameter", in: `(a b;c); d`, wantBareItems: []string{"a", "b"}, wantParams: []string{"", ";c"}, }, { name: "invalid inner list", in: `(a b;c `, wantBareItems: []string{"a", "b"}, wantParams: []string{"", ";c"}, }, } for _, tc := range tests { var gotBareItems, gotParams []string f := func(bareItem, param string) { gotBareItems = append(gotBareItems, bareItem) gotParams = append(gotParams, param) } ok := ParseBareInnerList(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if !slices.Equal(tc.wantBareItems, gotBareItems) { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItems, tc.wantBareItems) } if !slices.Equal(tc.wantParams, gotParams) { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams) } } } func TestConsumeItem(t *testing.T) { tests := []struct { name string in string wantBareItem string wantParam string wantOk bool }{ { name: "valid bare item", in: `fookey`, wantBareItem: `fookey`, wantOk: true, }, { name: "valid bare item and param", in: `fookey; a="123"`, wantBareItem: `fookey`, wantParam: `; a="123"`, wantOk: true, }, { name: "valid item with content after", in: `fookey; a="123", otheritem; otherparam=1`, wantBareItem: `fookey`, wantParam: `; a="123"`, wantOk: true, }, { name: "invalid just param", in: `;a="123"`, }, } for _, tc := range tests { var gotBareItem, gotParam string f := func(bareItem, param string) { gotBareItem = bareItem gotParam = param } gotConsumed, gotRest, ok := consumeItem(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.wantBareItem != gotBareItem { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItem, tc.wantBareItem) } if tc.wantParam != gotParam { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam) } if gotConsumed+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, gotConsumed, gotRest, tc.in) } } } func TestParseItem(t *testing.T) { tests := []struct { name string in string wantBareItem string wantParam string wantOk bool }{ { name: "valid bare item", in: `fookey`, wantBareItem: `fookey`, wantOk: true, }, { name: "valid bare item and param", in: `fookey; a="123"`, wantBareItem: `fookey`, wantParam: `; a="123"`, wantOk: true, }, { name: "valid item with content after", in: `fookey; a="123", otheritem; otherparam=1`, wantBareItem: `fookey`, wantParam: `; a="123"`, }, { name: "invalid just param", in: `;a="123"`, }, } for _, tc := range tests { var gotBareItem, gotParam string f := func(bareItem, param string) { gotBareItem = bareItem gotParam = param } ok := ParseItem(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.wantBareItem != gotBareItem { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItem, tc.wantBareItem) } if tc.wantParam != gotParam { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam) } } } func TestParseDictionary(t *testing.T) { tests := []struct { name string in string wantVal string wantParam string wantOk bool }{ { name: "valid dictionary with simple value", in: `a=b, want=foo, c=d`, wantVal: "foo", wantOk: true, }, { name: "valid dictionary with implicit value", in: `a, want, c=d`, wantVal: "?1", wantOk: true, }, { name: "valid dictionary with parameter", in: `a, want=foo;bar=baz, c=d`, wantVal: "foo", wantParam: ";bar=baz", wantOk: true, }, { name: "valid dictionary with inner list", in: `a, want=(a b c d;e;f);g=h, c=d`, wantVal: "(a b c d;e;f)", wantParam: ";g=h", wantOk: true, }, { name: "valid dictionary with fake commas", in: `a=(";");b=";",want=foo;bar`, wantVal: "foo", wantParam: ";bar", wantOk: true, }, { name: "invalid dictionary with bad key", in: `UPPERCASEKEY=BAD, want=foo, c=d`, }, { name: "invalid dictionary with trailing comma", in: `trailing=comma,`, }, { name: "invalid dictionary with unclosed string", in: `a=""",want=foo;bar`, }, } for _, tc := range tests { var gotVal, gotParam string f := func(key, val, param string) { if key == "want" { gotVal = val gotParam = param } } ok := ParseDictionary(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.wantVal != gotVal { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotVal, tc.wantVal) } if tc.wantParam != gotParam { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam) } } } func TestConsumeParameter(t *testing.T) { tests := []struct { name string in string want any wantOk bool }{ { name: "valid string", in: `;parameter;want="wantvalue"`, want: "wantvalue", wantOk: true, }, { name: "valid integer", in: `;parameter;want=123456;something`, want: 123456, wantOk: true, }, { name: "valid decimal", in: `;parameter;want=3.14;something`, want: 3.14, wantOk: true, }, { name: "valid implicit bool", in: `;parameter;want;something`, want: true, wantOk: true, }, { name: "valid token", in: `;want=*atoken;something`, want: "*atoken", wantOk: true, }, { name: "valid byte sequence", in: `;want=:eWF5Cg==:;something`, want: "eWF5Cg==", wantOk: true, }, { name: "valid repeated key", in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!"`, want: "overwritten!", wantOk: true, }, { name: "valid parameter with content after", in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!", some=stuff`, want: "overwritten!", wantOk: true, }, { name: "invalid parameter", in: `;UPPERCASEKEY=NOT_ACCEPTED`, }, } for _, tc := range tests[len(tests)-1:] { var got any f := func(key, val string) { if key != "want" { return } switch { case strings.HasPrefix(val, "?"): // Bool got = val == "?1" case strings.HasPrefix(val, `"`): // String got = val[1 : len(val)-1] case strings.HasPrefix(val, "*"): // Token got = val case strings.HasPrefix(val, ":"): // Byte sequence got = val[1 : len(val)-1] default: if valConv, err := strconv.Atoi(val); err == nil { // Integer got = valConv return } if valConv, err := strconv.ParseFloat(val, 64); err == nil { // Float got = valConv return } } } consumed, rest, ok := consumeParameter(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if got != tc.want { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if consumed+rest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, rest, tc.in) } } } func TestParseParameter(t *testing.T) { tests := []struct { name string in string want any wantOk bool }{ { name: "valid parameter", in: `;parameter;want="wantvalue"`, want: "wantvalue", wantOk: true, }, { name: "valid parameter with content after", in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!", some=stuff`, want: "overwritten!", }, { name: "invalid parameter", in: `;UPPERCASEKEY=NOT_ACCEPTED`, }, } for _, tc := range tests[len(tests)-1:] { var got any f := func(key, val string) { if key != "want" { return } switch { case strings.HasPrefix(val, "?"): // Bool got = val == "?1" case strings.HasPrefix(val, `"`): // String got = val[1 : len(val)-1] case strings.HasPrefix(val, "*"): // Token got = val case strings.HasPrefix(val, ":"): // Byte sequence got = val[1 : len(val)-1] default: if valConv, err := strconv.Atoi(val); err == nil { // Integer got = valConv return } if valConv, err := strconv.ParseFloat(val, 64); err == nil { // Float got = valConv return } } } ok := ParseParameter(tc.in, f) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if got != tc.want { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestConsumeKey(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid basic key", in: `fookey`, want: `fookey`, wantOk: true, }, { name: "valid basic key with more content after", in: `fookey,u=7`, want: `fookey`, wantOk: true, }, { name: "invalid key", in: `1keycannotstartwithnum`, }, } for _, tc := range tests { got, gotRest, ok := consumeKey(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestConsumeIntegerOrDecimal(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid integer", in: "123456", want: "123456", wantOk: true, }, { name: "valid integer with more content after", in: "123456,12345", want: "123456", wantOk: true, }, { name: "valid max integer", in: "999999999999999", want: "999999999999999", wantOk: true, }, { name: "valid min integer", in: "-999999999999999", want: "-999999999999999", wantOk: true, }, { name: "invalid integer too high", in: "9999999999999999", }, { name: "invalid integer too low", in: "-9999999999999999", }, { name: "valid decimal", in: "-123456789012.123", want: "-123456789012.123", wantOk: true, }, { name: "invalid decimal integer component too long", in: "1234567890123.1", }, { name: "invalid decimal fraction component too long", in: "1.1234", }, { name: "invalid decimal trailing dot", in: "1.", }, } for _, tc := range tests { got, gotRest, ok := consumeIntegerOrDecimal(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestParseInteger(t *testing.T) { tests := []struct { name string in string want int64 wantOk bool }{ { name: "valid integer", in: "123456", want: 123456, wantOk: true, }, { name: "valid integer with more content after", in: "123456,12345", }, { name: "valid max integer", in: "999999999999999", want: 999999999999999, wantOk: true, }, { name: "valid min integer", in: "-999999999999999", want: -999999999999999, wantOk: true, }, { name: "invalid integer too high", in: "9999999999999999", }, { name: "invalid integer too low", in: "-9999999999999999", }, { name: "invalid integer with fraction", in: "-123456789012.123", }, } for _, tc := range tests { got, ok := ParseInteger(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestParseDecimal(t *testing.T) { tests := []struct { name string in string want float64 wantOk bool }{ { name: "valid decimal", in: "123456.789", want: 123456.789, wantOk: true, }, { name: "valid decimal with more content after", in: "123456.789, 123", }, { name: "invalid decimal with no fraction", in: "123456", }, { name: "invalid decimal integer component too long", in: "1234567890123.1", }, { name: "invalid decimal fraction component too long", in: "1.1234", }, { name: "invalid decimal trailing dot", in: "1.", }, } for _, tc := range tests { got, ok := ParseDecimal(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestConsumeString(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid basic string", in: `"foo bar"`, want: `"foo bar"`, wantOk: true, }, { name: "valid basic string with more content after", in: `"foo bar", a=3`, want: `"foo bar"`, wantOk: true, }, { name: "valid string with escaped dquote", in: `"foo bar \""`, want: `"foo bar \""`, wantOk: true, }, { name: "invalid string no starting dquote", in: `foo bar"`, }, { name: "invalid string no closing dquote", in: `"foo bar`, }, { name: "invalid string invalid character", in: string([]byte{'"', 0x00, '"'}), }, } for _, tc := range tests { got, gotRest, ok := consumeString(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestParseString(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid basic string", in: `"foo bar"`, want: "foo bar", wantOk: true, }, { name: "valid basic string with more content after", in: `"foo bar", a=3`, }, { name: "valid string with escaped dquote", in: `"foo bar \""`, want: `foo bar \"`, wantOk: true, }, { name: "invalid string no starting dquote", in: `foo bar"`, }, { name: "invalid string no closing dquote", in: `"foo bar`, }, { name: "invalid string invalid character", in: string([]byte{'"', 0x00, '"'}), }, } for _, tc := range tests { got, ok := ParseString(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestConsumeToken(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid token", in: "*atoken", want: "*atoken", wantOk: true, }, { name: "valid token with more content after", in: "*atoken something", want: "*atoken", wantOk: true, }, { name: "invalid token", in: "0invalid", }, } for _, tc := range tests { got, gotRest, ok := consumeToken(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestParseToken(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid token", in: "a_b-c.d3:f%00/*", want: "a_b-c.d3:f%00/*", wantOk: true, }, { name: "valid token with uppercase", in: "FOOBAR", want: "FOOBAR", wantOk: true, }, { name: "valid token with content after", in: "FOOBAR, foobar", }, { name: "invalid token", in: "0invalid", }, } for _, tc := range tests { got, ok := ParseToken(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestConsumeByteSequence(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid byte sequence", in: ":aGVsbG8gd29ybGQ=:", want: ":aGVsbG8gd29ybGQ=:", wantOk: true, }, { name: "valid byte sequence with more content after", in: ":aGVsbG8gd29ybGQ=::aGVsbG8gd29ybGQ=:", want: ":aGVsbG8gd29ybGQ=:", wantOk: true, }, { name: "invalid byte sequence character", in: ":-:", }, { name: "invalid byte sequence opening", in: "aGVsbG8gd29ybGQ=:", }, { name: "invalid byte sequence closing", in: ":aGVsbG8gd29ybGQ=", }, } for _, tc := range tests { got, gotRest, ok := consumeByteSequence(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestParseByteSequence(t *testing.T) { tests := []struct { name string in string want []byte wantOk bool }{ { name: "valid byte sequence", in: ":aGVsbG8gd29ybGQ=:", want: []byte("aGVsbG8gd29ybGQ="), wantOk: true, }, { name: "valid byte sequence with more content after", in: ":aGVsbG8gd29ybGQ=::aGVsbG8gd29ybGQ=:", }, { name: "invalid byte sequence character", in: ":-:", }, { name: "invalid byte sequence opening", in: "aGVsbG8gd29ybGQ=:", }, { name: "invalid byte sequence closing", in: ":aGVsbG8gd29ybGQ=", }, } for _, tc := range tests { got, ok := ParseByteSequence(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if !slices.Equal(tc.want, got) { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestConsumeBoolean(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid boolean", in: "?0", want: "?0", wantOk: true, }, { name: "valid boolean with more content after", in: "?1, a=1", want: "?1", wantOk: true, }, { name: "invalid boolean", in: "!2", }, } for _, tc := range tests { got, gotRest, ok := consumeBoolean(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestParseBoolean(t *testing.T) { tests := []struct { name string in string want bool wantOk bool }{ { name: "valid boolean false", in: "?0", want: false, wantOk: true, }, { name: "valid boolean true", in: "?1", want: true, wantOk: true, }, { name: "valid boolean with more content after", in: "?1, a=1", }, { name: "invalid boolean", in: "?2", }, } for _, tc := range tests { got, ok := ParseBoolean(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestConsumeDate(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid zero date", in: "@0", want: "@0", wantOk: true, }, { name: "valid positive date", in: "@1659578233", want: "@1659578233", wantOk: true, }, { name: "valid negative date", in: "@-1659578233", want: "@-1659578233", wantOk: true, }, { name: "valid large date", in: "@25340221440", want: "@25340221440", wantOk: true, }, { name: "valid small date", in: "@-62135596800", want: "@-62135596800", wantOk: true, }, { name: "invalid decimal date", in: "@1.2", }, { name: "valid date with more content after", in: "@1659578233, foo;bar", want: "@1659578233", wantOk: true, }, } for _, tc := range tests { got, gotRest, ok := consumeDate(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestParseDate(t *testing.T) { tests := []struct { name string in string want time.Time wantOk bool }{ { name: "valid zero date", in: "@0", want: time.Unix(0, 0), wantOk: true, }, { name: "valid positive date", in: "@1659578233", want: time.Date(2022, 8, 4, 1, 57, 13, 0, time.UTC).Local(), wantOk: true, }, { name: "valid negative date", in: "@-1659578233", want: time.Date(1917, 5, 30, 22, 2, 47, 0, time.UTC).Local(), wantOk: true, }, { name: "valid max date required", in: "@253402214400", want: time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC).Local(), wantOk: true, }, { name: "valid min date required", in: "@-62135596800", want: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Local(), wantOk: true, }, { name: "invalid date with fraction", in: "@0.123", }, { name: "valid date with more content after", in: "@0, @0", }, } for _, tc := range tests { got, ok := ParseDate(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } } func TestConsumeDisplayString(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid ascii string", in: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"", want: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"", wantOk: true, }, { name: "valid lowercase non-ascii string", in: `%"f%c3%bc%c3%bc"`, want: `%"f%c3%bc%c3%bc"`, wantOk: true, }, { name: "invalid uppercase non-ascii string", in: `%"f%C3%BC%C3%BC"`, }, { name: "invalid unquoted string", in: "%foo", }, { name: "invalid string missing initial quote", in: `%foo"`, }, { name: "invalid string missing closing quote", in: `%"foo`, }, { name: "invalid tab in string", in: "%\"\t\"", }, { name: "invalid newline in string", in: "%\"\n\"", }, { name: "invalid single quoted string", in: `%'foo'`, }, { name: "invalid string bad escaping", in: `%\"foo %a"`, }, { name: "valid string with escaped quotes", in: `%"foo %22bar%22 \\ baz"`, want: `%"foo %22bar%22 \\ baz"`, wantOk: true, }, { name: "invalid sequence id utf-8 string", in: `%"%a0%a1"`, }, { name: "invalid 2 bytes sequence utf-8 string", in: `%"%c3%28"`, }, { name: "invalid 3 bytes sequence utf-8 string", in: `%"%e2%28%a1"`, }, { name: "invalid 4 bytes sequence utf-8 string", in: `%"%f0%28%8c%28"`, }, { name: "invalid hex utf-8 string", in: `%"%g0%1w"`, }, { name: "valid byte order mark in display string", in: `%"BOM: %ef%bb%bf"`, want: `%"BOM: %ef%bb%bf"`, wantOk: true, }, { name: "valid string with content after", in: `%"foo\nbar", foo;bar`, want: `%"foo\nbar"`, wantOk: true, }, { name: "invalid unfinished 4 bytes rune", in: `%"%f0%9f%98"`, }, } for _, tc := range tests { got, gotRest, ok := consumeDisplayString(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } if got+gotRest != tc.in { t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) } } } func TestParseDisplayString(t *testing.T) { tests := []struct { name string in string want string wantOk bool }{ { name: "valid ascii string", in: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"", want: " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", wantOk: true, }, { name: "valid lowercase non-ascii string", in: `%"f%c3%bc%c3%bc"`, want: "füü", wantOk: true, }, { name: "invalid uppercase non-ascii string", in: `%"f%C3%BC%C3%BC"`, }, { name: "invalid unquoted string", in: "%foo", }, { name: "invalid string missing initial quote", in: `%foo"`, }, { name: "invalid string missing closing quote", in: `%"foo`, }, { name: "invalid tab in string", in: "%\"\t\"", }, { name: "invalid newline in string", in: "%\"\n\"", }, { name: "invalid single quoted string", in: `%'foo'`, }, { name: "invalid string bad escaping", in: `%\"foo %a"`, }, { name: "valid string with escaped quotes", in: "%\"foo %22bar%22 \\ baz\"", want: "foo \"bar\" \\ baz", wantOk: true, }, { name: "invalid sequence id utf-8 string", in: `%"%a0%a1"`, }, { name: "invalid 2 bytes sequence utf-8 string", in: `%"%c3%28"`, }, { name: "invalid 3 bytes sequence utf-8 string", in: `%"%e2%28%a1"`, }, { name: "invalid 4 bytes sequence utf-8 string", in: `%"%f0%28%8c%28"`, }, { name: "invalid hex utf-8 string", in: `%"%g0%1w"`, }, { name: "valid byte order mark in display string", in: `%"BOM: %ef%bb%bf"`, want: "BOM: \uFEFF", wantOk: true, }, { name: "valid string with content after", in: `%"foo\nbar", foo;bar`, }, { name: "invalid unfinished 4 bytes rune", in: `%"%f0%9f%98"`, }, } for _, tc := range tests { got, ok := ParseDisplayString(tc.in) if ok != tc.wantOk { t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) } if tc.want != got { t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) } } }