From 23b0485e4447b114226bcb2131bc30625673718e Mon Sep 17 00:00:00 2001 From: George Papanikolaou Date: Thu, 10 Oct 2024 12:02:07 +0300 Subject: [PATCH 1/2] Cherry pick valyala/fastjson/pull/87 --- parser.go | 55 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/parser.go b/parser.go index 885e184..ee550a1 100644 --- a/parser.go +++ b/parser.go @@ -267,28 +267,41 @@ func parseObject(s string, c *cache, depth int) (*Value, string, error) { } func escapeString(dst []byte, s string) []byte { - if !hasSpecialChars(s) { - // Fast path - nothing to escape. - dst = append(dst, '"') - dst = append(dst, s...) - dst = append(dst, '"') - return dst - } - - // Slow path. - return strconv.AppendQuote(dst, s) -} - -func hasSpecialChars(s string) bool { - if strings.IndexByte(s, '"') >= 0 || strings.IndexByte(s, '\\') >= 0 { - return true - } + dst = append(dst, '"') for i := 0; i < len(s); i++ { - if s[i] < 0x20 { - return true - } - } - return false + c := s[i] + switch { + case c == '"': + // quotation mark + dst = append(dst, []byte{'\\', '"'}...) + case c == '\\': + // reverse solidus + dst = append(dst, []byte{'\\', '\\'}...) + case c >= 0x20: + // default, rest below are control chars + dst = append(dst, c) + case c == 0x08: + dst = append(dst, []byte{'\\', 'b'}...) + case c < 0x09: + dst = append(dst, []byte{'\\', 'u', '0', '0', '0', '0' + c}...) + case c == 0x09: + dst = append(dst, []byte{'\\', 't'}...) + case c == 0x0a: + dst = append(dst, []byte{'\\', 'n'}...) + case c == 0x0c: + dst = append(dst, []byte{'\\', 'f'}...) + case c == 0x0d: + dst = append(dst, []byte{'\\', 'r'}...) + case c < 0x10: + dst = append(dst, []byte{'\\', 'u', '0', '0', '0', 0x57 + c}...) + case c < 0x1a: + dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x20 + c}...) + case c < 0x20: + dst = append(dst, []byte{'\\', 'u', '0', '0', '1', 0x47 + c}...) + } + } + dst = append(dst, '"') + return dst } func unescapeStringBestEffort(s string) string { From b4ef4ca75378e0d392da84f53b2bb164c38e501b Mon Sep 17 00:00:00 2001 From: George Papanikolaou Date: Thu, 10 Oct 2024 13:39:39 +0300 Subject: [PATCH 2/2] Add tests --- parser_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/parser_test.go b/parser_test.go index b691185..90be8ba 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,6 +1,8 @@ package fastjson import ( + "bytes" + "encoding/json" "fmt" "math" "strings" @@ -1275,3 +1277,59 @@ func testParseGetSerial(s string) error { } return nil } + +// Tests for https://github.com/valyala/fastjson/issues/90 +// This was manifesting due to the use of strconv.AppendQuote +func TestUTF8NonPrintableArtifacts(t *testing.T) { + testCases := []struct { + name string + s string + }{ + { + name: "problematic bytes", + s: "data:\"\\xd6`\\xb76d\\xf6E\U000E8737(\\x91\\xb294\"", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jsonEnvelope := struct { + Inner string + }{ + Inner: tc.s, + } + + m, err := json.Marshal(jsonEnvelope) // `m` is a full valid json + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + // we will pass that `m` json through a fastjson marshal/unmarshal cycle + + fastjsonValue, err := ParseBytes(m) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + o, err := fastjsonValue.Object() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + // In order to trigger the bug we need to visit all the keys and call Type() on them + o.Visit(func(k []byte, v *Value) { + v.Type() + }) + + res := fastjsonValue.MarshalTo(nil) + if !bytes.Equal(res, m) { + t.Fatalf("unexpected result; got %q; want %q", res, m) + } + + err = ValidateBytes(res) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + }) + } +}