From 111cdaffb625f7db9b8c5a27e386999944346e0d Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Tue, 16 Aug 2022 21:50:07 -0600 Subject: Fix those now-failing error checks --- compat/json/compat.go | 11 ++++++++++- decode.go | 29 +++++++++++++++++++++++++++++ reencode.go | 40 ++++++++++++++++++++++++++++++++-------- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/compat/json/compat.go b/compat/json/compat.go index 8d6ec35..59eff6c 100644 --- a/compat/json/compat.go +++ b/compat/json/compat.go @@ -78,6 +78,9 @@ func convertReEncodeError(err error) error { msg: se.Err.Error(), Offset: se.Offset + 1, } + if errors.Is(se.Err, io.ErrUnexpectedEOF) { + err.(*SyntaxError).msg = "unexpected end of JSON input" + } } return err } @@ -91,6 +94,9 @@ func HTMLEscape(dst *bytes.Buffer, src []byte) { func reencode(src []byte, formatter *lowmemjson.ReEncoder) error { _, err := formatter.Write(src) + if err == nil { + err = formatter.Close() + } return convertReEncodeError(err) } @@ -130,6 +136,9 @@ func convertDecodeError(err error) error { msg: terr.Err.Error(), Offset: terr.Offset, } + if errors.Is(terr.Err, io.ErrUnexpectedEOF) { + err.(*SyntaxError).msg = "unexpected end of JSON input" + } case *lowmemjson.DecodeTypeError: if typeErr, ok := terr.Err.(*json.UnmarshalTypeError); ok { err = &UnmarshalTypeError{ @@ -160,7 +169,7 @@ func convertDecodeError(err error) error { } func Unmarshal(data []byte, ptr any) error { - return convertDecodeError(lowmemjson.Decode(bytes.NewReader(data), ptr)) + return convertDecodeError(lowmemjson.DecodeThenEOF(bytes.NewReader(data), ptr)) } type Decoder struct { diff --git a/decode.go b/decode.go index a17a572..52fcb5e 100644 --- a/decode.go +++ b/decode.go @@ -121,6 +121,26 @@ func Decode(r interface { return NewDecoder(r).Decode(ptr) } +// DecodeThenEOF is like decode, but emits an error if there is extra data after the JSON. +func DecodeThenEOF(r interface { + io.Reader + io.RuneScanner +}, ptr any) error { + dec := NewDecoder(r) + if err := dec.Decode(ptr); err != nil { + return err + } + if c, _, err := r.ReadRune(); err == nil { + return &DecodeError{ + Err: &DecodeSyntaxError{ + Err: fmt.Errorf("invalid character %q after top-level value", c), + Offset: dec.InputOffset(), + }, + } + } + return nil +} + func (dec *Decoder) Decode(ptr any) (err error) { ptrVal := reflect.ValueOf(ptr) if ptrVal.Kind() != reflect.Pointer || ptrVal.IsNil() || !ptrVal.Elem().CanSet() { @@ -147,6 +167,15 @@ func (dec *Decoder) Decode(ptr any) (err error) { } }() dec.decode(ptrVal.Elem(), false) + for { + _, s, t, _ := dec.io.ReadRuneType() + if t == RuneTypeEOF || t == RuneTypeError { + if s > 0 { + dec.io.UnreadRune() + } + break + } + } return nil } diff --git a/reencode.go b/reencode.go index 1e5f5aa..7c5ce52 100644 --- a/reencode.go +++ b/reencode.go @@ -5,7 +5,6 @@ package lowmemjson import ( - "errors" "fmt" "io" "unicode/utf8" @@ -16,6 +15,8 @@ type reencodeState func(rune) error type ReEncoder struct { Out io.Writer + AllowMultipleValues bool + // Whether to minify the JSON. Compact bool // String to use to indent; ignored if Compact is true. @@ -83,13 +84,22 @@ func (enc *ReEncoder) Close() error { } } if _, err := enc.par.HandleEOF(); err != nil { - enc.err = err + enc.err = &ReEncodeSyntaxError{ + Err: err, + Offset: enc.inputPos, + } return enc.err } if err := enc.handleRune(0, 0); err != nil { - enc.err = err + enc.err = &ReEncodeSyntaxError{ + Err: err, + Offset: enc.inputPos, + } return enc.err } + if enc.AllowMultipleValues { + enc.par.Reset() + } return nil } @@ -97,22 +107,36 @@ func (enc *ReEncoder) WriteRune(c rune) (n int, err error) { if enc.err != nil { return 0, enc.err } - if enc.bufLen != 0 { - enc.err = errors.New("lowmemjson.ReEncoder: cannot .WriteRune() when there is a partial rune that has been .Write()n") + if enc.bufLen > 0 { + enc.err = fmt.Errorf("lowmemjson.ReEncoder: cannot .WriteRune() when there is a partial rune that has been .Write()en: %q", enc.buf[:enc.bufLen]) return 0, enc.err } + enc.written = 0 + +rehandle: t, err := enc.par.HandleRune(c) if err != nil { enc.err = &ReEncodeSyntaxError{ Err: err, Offset: enc.inputPos, } - return 0, enc.err + return enc.written, enc.err } - - enc.written = 0 enc.err = enc.handleRune(c, t) + if enc.err == nil && t == RuneTypeEOF { + if enc.AllowMultipleValues { + enc.par.Reset() + goto rehandle + } else { + enc.err = &ReEncodeSyntaxError{ + Err: fmt.Errorf("invalid character %q after top-level value", c), + Offset: enc.inputPos, + } + return enc.written, enc.err + } + } + enc.inputPos += int64(utf8.RuneLen(c)) return enc.written, enc.err } -- cgit v1.1-4-g5e80