From 38737cbeb673381e56de43445747dbe0775cf024 Mon Sep 17 00:00:00 2001 From: sid sri Date: Sun, 26 Jul 2026 22:05:38 +0530 Subject: [PATCH] Reject ambiguous Unicode line separators in comments --- json_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ parse.go | 15 ++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/json_test.go b/json_test.go index 2b02559..be621d8 100644 --- a/json_test.go +++ b/json_test.go @@ -333,3 +333,59 @@ func Test(t *testing.T) { } } } + +func TestLineSeparators(t *testing.T) { + tests := []struct { + name string + in string + wantErr string + }{ + { + name: "U+2028 in string", + in: "\"a\u2028b\"", + }, + { + name: "U+2029 in string", + in: "\"a\u2029b\"", + }, + { + name: "U+2028 in block comment", + in: "/* a\u2028b */ null", + }, + { + name: "U+2029 in block comment", + in: "/* a\u2029b */ null", + }, + { + name: "U+2028 as whitespace", + in: "null\u2028", + wantErr: "hujson: line 1, column 5: invalid character '\\u2028' after top-level value", + }, + { + name: "U+2029 as whitespace", + in: "null\u2029", + wantErr: "hujson: line 1, column 5: invalid character '\\u2029' after top-level value", + }, + { + name: "U+2028 in line comment", + in: "// hidden\u2028null\ntrue", + wantErr: "hujson: line 1, column 10: invalid character '\\u2028' in line comment", + }, + { + name: "U+2029 in line comment", + in: "// hidden\u2029null\ntrue", + wantErr: "hujson: line 1, column 10: invalid character '\\u2029' in line comment", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := Parse([]byte(tt.in)) + switch { + case err == nil && tt.wantErr != "": + t.Fatalf("Parse() error = nil, want %q", tt.wantErr) + case err != nil && err.Error() != tt.wantErr: + t.Fatalf("Parse() error = %q, want %q", err, tt.wantErr) + } + }) + } +} diff --git a/parse.go b/parse.go index f3f1f62..627eec4 100644 --- a/parse.go +++ b/parse.go @@ -221,6 +221,7 @@ func consumeExtra(n int, b []byte) (int, error) { n += consumeWhitespace(b[n:]) // Skip past comments. case '/': + isLineComment := bytes.HasPrefix(b[n:], lineCommentStart) switch nc := consumeComment(b[n:]); { case nc == 0: return n, nil @@ -228,6 +229,11 @@ func consumeExtra(n int, b []byte) (int, error) { return n, fmt.Errorf("parsing comment: %w", io.ErrUnexpectedEOF) case !utf8.Valid(b[n : n+nc]): return n, fmt.Errorf("invalid UTF-8 in comment") + case isLineComment: + if i := indexLineSeparator(b[n : n+nc]); i >= 0 { + return n + i, newInvalidCharacterError(b[n+i:], "in line comment") + } + n += nc default: n += nc } @@ -245,6 +251,15 @@ func consumeWhitespace(b []byte) (n int) { return n } +func indexLineSeparator(b []byte) int { + i := bytes.IndexRune(b, '\u2028') + j := bytes.IndexRune(b, '\u2029') + if i < 0 || (j >= 0 && j < i) { + return j + } + return i +} + // consumeComment consumes a line or block comment start in b. // It returns the length of the comment if valid, otherwise // it returns 0 if it is not a comment and -1 if it is invalid.