diff --git a/internal/encoder/compact.go b/internal/encoder/compact.go index d70d9e3..4f1bd79 100644 --- a/internal/encoder/compact.go +++ b/internal/encoder/compact.go @@ -43,13 +43,32 @@ func Compact(buf *bytes.Buffer, src []byte, escape bool) error { } func compact(dst, src []byte, escape bool) ([]byte, error) { - buf, _, err := compactValue(dst, src, 0, escape) + buf, cursor, err := compactValue(dst, src, 0, escape) if err != nil { return nil, err } + if err := validateEndBuf(src, cursor); err != nil { + return nil, err + } return buf, nil } +func validateEndBuf(src []byte, cursor int64) error { + for { + switch src[cursor] { + case ' ', '\t', '\n', '\r': + cursor++ + continue + case nul: + return nil + } + return errors.ErrSyntax( + fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]), + cursor+1, + ) + } +} + func skipWhiteSpace(buf []byte, cursor int64) int64 { LOOP: if isWhiteSpace[buf[cursor]] { @@ -90,12 +109,9 @@ func compactValue(dst, src []byte, cursor int64, escape bool) ([]byte, int64, er } func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { - switch src[cursor] { - case 'n': - return compactNull(dst, src, cursor) - case '{': + if src[cursor] == '{' { dst = append(dst, '{') - default: + } else { return nil, 0, errors.ErrExpected("expected { character for object value", cursor) } cursor = skipWhiteSpace(src, cursor+1) @@ -135,12 +151,9 @@ func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, e } func compactArray(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) { - switch src[cursor] { - case 'n': - return compactNull(dst, src, cursor) - case '[': + if src[cursor] == '[' { dst = append(dst, '[') - default: + } else { return nil, 0, errors.ErrExpected("expected [ character for array value", cursor) } cursor = skipWhiteSpace(src, cursor+1) diff --git a/internal/encoder/indent.go b/internal/encoder/indent.go index 7eed1d8..d593301 100644 --- a/internal/encoder/indent.go +++ b/internal/encoder/indent.go @@ -26,10 +26,13 @@ func Indent(buf *bytes.Buffer, src []byte, prefix, indentStr string) error { } func doIndent(dst, src []byte, prefix, indentStr string, escape bool) ([]byte, error) { - buf, _, err := indentValue(dst, src, 0, 0, []byte(prefix), []byte(indentStr), escape) + buf, cursor, err := indentValue(dst, src, 0, 0, []byte(prefix), []byte(indentStr), escape) if err != nil { return nil, err } + if err := validateEndBuf(src, cursor); err != nil { + return nil, err + } return buf, nil } @@ -78,12 +81,9 @@ func indentObject( prefix []byte, indentBytes []byte, escape bool) ([]byte, int64, error) { - switch src[cursor] { - case 'n': - return compactNull(dst, src, cursor) - case '{': + if src[cursor] == '{' { dst = append(dst, '{') - default: + } else { return nil, 0, errors.ErrExpected("expected { character for object value", cursor) } cursor = skipWhiteSpace(src, cursor+1) @@ -139,12 +139,9 @@ func indentArray( prefix []byte, indentBytes []byte, escape bool) ([]byte, int64, error) { - switch src[cursor] { - case 'n': - return compactNull(dst, src, cursor) - case '[': + if src[cursor] == '[' { dst = append(dst, '[') - default: + } else { return nil, 0, errors.ErrExpected("expected [ character for array value", cursor) } cursor = skipWhiteSpace(src, cursor+1) diff --git a/json_test.go b/json_test.go index 4abac57..47a6c38 100644 --- a/json_test.go +++ b/json_test.go @@ -2,6 +2,7 @@ package json_test import ( "bytes" + stdjson "encoding/json" "math" "math/rand" "reflect" @@ -81,9 +82,34 @@ func TestCompact(t *testing.T) { } } t.Run("invalid", func(t *testing.T) { - buf.Reset() - if err := json.Compact(&buf, []byte(`invalid`)); err == nil { - t.Fatal(err) + for _, src := range []string{ + `invalid`, + `}`, + `]`, + `{"a":1}}`, + `{"a" 1}`, + `{"a": 1 "b": 2}`, + `["a" "b"]`, + `"\`, + `{"a":"\\""}`, + `tr`, + `{"a": tru, "b": 1}`, + `fal`, + `{"a": fals, "b": 1}`, + `nu`, + `{"a": nul, "b": 1}`, + `1.234.567`, + `[nul]`, + `{} 1`, + } { + buf.Reset() + if err := stdjson.Compact(&buf, []byte(src)); err == nil { + t.Fatal("invalid test case") + } + buf.Reset() + if err := json.Compact(&buf, []byte(src)); err == nil { + t.Fatalf("%q: expected error", src) + } } }) } @@ -125,6 +151,37 @@ func TestIndent(t *testing.T) { t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent) } } + t.Run("invalid", func(t *testing.T) { + for _, src := range []string{ + `invalid`, + `}`, + `]`, + `{"a":1}}`, + `{"a" 1}`, + `{"a": 1 "b": 2}`, + `["a" "b"]`, + `"\`, + `{"a":"\\""}`, + `tr`, + `{"a": tru, "b": 1}`, + `fal`, + `{"a": fals, "b": 1}`, + `nu`, + `{"a": nul, "b": 1}`, + `1.234.567`, + `[nul]`, + `{} 1`, + } { + buf.Reset() + if err := stdjson.Indent(&buf, []byte(src), "", " "); err == nil { + t.Fatal("invalid test case") + } + buf.Reset() + if err := json.Indent(&buf, []byte(src), "", " "); err == nil { + t.Fatalf("%q: expected error", src) + } + } + }) } // Tests of a large random structure.