Skip to content

Commit

Permalink
fix html escaping
Browse files Browse the repository at this point in the history
golint
  • Loading branch information
zerosnake0 committed Oct 8, 2020
1 parent 6d9982e commit 39fd02e
Show file tree
Hide file tree
Showing 96 changed files with 694 additions and 532 deletions.
19 changes: 19 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,44 @@ import (
"io"
)

// Decoder is almost standard library compatible
// The following standard methods are not implemented
// - Token
type Decoder struct {
it *Iterator
err error
}

// Release decoder, decoder should not be reused after call
func (dec *Decoder) Release() {
dec.it.Release()
dec.it = nil
}

// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
// Number instead of as a float64.
func (dec *Decoder) UseNumber() {
dec.it.useNumber = true
}

// DisallowUnknownFields causes the Decoder to return an error when the destination
// is a struct and the input contains object keys which do not match any
// non-ignored, exported fields in the destination.
func (dec *Decoder) DisallowUnknownFields() {
dec.it.disallowUnknownFields = true
}

// Decode reads the next JSON-encoded value from its
// input and stores it in the value pointed to by v.
func (dec *Decoder) Decode(v interface{}) error {
if dec.err == nil {
dec.err = dec.it.ReadVal(v)
}
return dec.err
}

// Buffered returns a reader of the data remaining in the Decoder's
// buffer. The reader is valid until the next call to Decode.
func (dec *Decoder) Buffered() io.Reader {
return bytes.NewReader(dec.it.Buffer())
}
Expand All @@ -38,6 +51,8 @@ func (dec *Decoder) Buffered() io.Reader {
// panic("not implemented")
// }

// More reports whether there is another element in the
// current array or object being parsed.
func (dec *Decoder) More() bool {
if dec.err != nil {
return false
Expand All @@ -46,6 +61,10 @@ func (dec *Decoder) More() bool {
return err == nil
}

// InputOffset returns the input stream byte offset of the current decoder position.
// The offset gives the location of the end of the most recently returned token
// and the beginning of the next token.
// Whitespace may present at the position of offset
func (dec *Decoder) InputOffset() int64 {
return int64(dec.it.offset + dec.it.head)
}
10 changes: 9 additions & 1 deletion decoder_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (
)

var (
// Default decoder config is compatible with standard lib
// DefaultDecoderConfig is compatible with standard lib
DefaultDecoderConfig = NewDecoderConfig(nil)
)

// DecoderOption can be used to customize the decoder config
type DecoderOption struct {
// custom value decoders
ValDecoders map[reflect.Type]ValDecoder
Expand All @@ -33,6 +34,7 @@ type DecoderOption struct {

type decoderCache = map[rtype]ValDecoder

// DecoderConfig is a frozen config for decoding
type DecoderConfig struct {
cacheMu sync.Mutex
decoderCache atomic.Value
Expand All @@ -47,6 +49,8 @@ type DecoderConfig struct {
disallowUnknownFields bool
}

// NewDecoderConfig returns a new decoder config
// If the input option is nil, the default option will be applied
func NewDecoderConfig(opt *DecoderOption) *DecoderConfig {
decCfg := DecoderConfig{
tag: "json",
Expand All @@ -69,6 +73,7 @@ func NewDecoderConfig(opt *DecoderOption) *DecoderConfig {
return &decCfg
}

// Unmarshal behave like json.Unmarshal
func (decCfg *DecoderConfig) Unmarshal(data []byte, obj interface{}) error {
it := decCfg.NewIterator()
err := it.Unmarshal(data, obj)
Expand All @@ -79,10 +84,12 @@ func (decCfg *DecoderConfig) Unmarshal(data []byte, obj interface{}) error {
return err
}

// UnmarshalFromString behave like json.Unmarshal but with a string
func (decCfg *DecoderConfig) UnmarshalFromString(s string, obj interface{}) error {
return decCfg.Unmarshal(localStringToBytes(s), obj)
}

// UnmarshalFromReader behave like json.Unmarshal but with an io.Reader
func (decCfg *DecoderConfig) UnmarshalFromReader(r io.Reader, obj interface{}) error {
it := decCfg.NewIterator()
err := it.UnmarshalFromReader(r, obj)
Expand All @@ -97,6 +104,7 @@ func (decCfg *DecoderConfig) getDecoderFromCache(rType rtype) ValDecoder {
return decCfg.decoderCache.Load().(decoderCache)[rType]
}

// NewDecoder returns a new decoder that reads from r.
func (decCfg *DecoderConfig) NewDecoder(r io.Reader) *Decoder {
it := decCfg.NewIterator()
it.Reset(r)
Expand Down
5 changes: 3 additions & 2 deletions decoder_config_adaptor.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package jzon

// NewIterator returns a new iterator.
func (decCfg *DecoderConfig) NewIterator() *Iterator {
it := defaultIteratorPool.BorrowIterator()
it := defaultIteratorPool.borrowIterator()
it.cfg = decCfg
it.useNumber = decCfg.useNumber
it.disallowUnknownFields = decCfg.disallowUnknownFields
Expand All @@ -10,5 +11,5 @@ func (decCfg *DecoderConfig) NewIterator() *Iterator {

func (decCfg *DecoderConfig) returnIterator(it *Iterator) {
it.cfg = nil
defaultIteratorPool.ReturnIterator(it)
defaultIteratorPool.returnIterator(it)
}
13 changes: 13 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package jzon

// Encoder is almost standard library compatible
// The following standard methods are not implemented
// - SetIndent
type Encoder struct {
s *Streamer
err error
}

// Release encoder, encoder should not be reused after call
func (e *Encoder) Release() {
e.s.Release()
e.s = nil
Expand All @@ -22,13 +26,22 @@ func encodeInternal(s *Streamer, v interface{}) error {
return nil
}

// Encode writes the JSON encoding of v to the stream,
// followed by a newline character.
func (e *Encoder) Encode(v interface{}) error {
if e.err == nil {
e.err = encodeInternal(e.s, v)
}
return e.err
}

// SetEscapeHTML specifies whether problematic HTML characters
// should be escaped inside JSON quoted strings.
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
// to avoid certain safety problems that can arise when embedding JSON in HTML.
//
// In non-HTML settings where the escaping interferes with the readability
// of the output, SetEscapeHTML(false) disables this behavior.
func (e *Encoder) SetEscapeHTML(on bool) {
e.s.EscapeHTML(on)
}
Expand Down
25 changes: 15 additions & 10 deletions encoder_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (
)

var (
// Default encoder config is compatible with standard lib
// DefaultEncoderConfig is compatible with standard lib
DefaultEncoderConfig = NewEncoderConfig(nil)
)

// EncoderOption can be used to customize the encoder config
type EncoderOption struct {
ValEncoders map[reflect.Type]ValEncoder

Expand All @@ -34,12 +35,12 @@ func (cache encoderCache) preferPtrEncoder(typ reflect.Type) ValEncoder {
ptrEncoder := cache[rtypeOfType(ptrType)]
if pe, ok := ptrEncoder.(*pointerEncoder); ok {
return pe.encoder
} else {
// the element has a special pointer encoder
return &directEncoder{ptrEncoder}
}
// the element has a special pointer encoder
return &directEncoder{ptrEncoder}
}

// EncoderConfig is a frozen config for encoding
type EncoderConfig struct {
cacheMu sync.Mutex
// the encoder cache, or root encoder cache
Expand All @@ -51,13 +52,15 @@ type EncoderConfig struct {
onlyTaggedField bool

// can override during runtime
escapeHtml bool
escapeHTML bool
}

// NewEncoderConfig returns a new encoder config
// If the input option is nil, the default option will be applied
func NewEncoderConfig(opt *EncoderOption) *EncoderConfig {
encCfg := EncoderConfig{
tag: "json",
escapeHtml: true,
escapeHTML: true,
}
cache := encoderCache{}
internalCache := encoderCache{}
Expand All @@ -67,7 +70,7 @@ func NewEncoderConfig(opt *EncoderOption) *EncoderConfig {
cache[rtype] = valEnc
internalCache[rtype] = valEnc
}
encCfg.escapeHtml = opt.EscapeHTML
encCfg.escapeHTML = opt.EscapeHTML
if opt.Tag != "" {
encCfg.tag = opt.Tag
}
Expand All @@ -78,6 +81,7 @@ func NewEncoderConfig(opt *EncoderOption) *EncoderConfig {
return &encCfg
}

// Marshal behave like json.Marshal
func (encCfg *EncoderConfig) Marshal(obj interface{}) ([]byte, error) {
s := encCfg.NewStreamer()
defer s.Release()
Expand All @@ -94,6 +98,7 @@ func (encCfg *EncoderConfig) Marshal(obj interface{}) ([]byte, error) {
return b, nil
}

// NewEncoder returns a new encoder that writes to w.
func (encCfg *EncoderConfig) NewEncoder(w io.Writer) *Encoder {
s := encCfg.NewStreamer()
s.Reset(w)
Expand Down Expand Up @@ -162,13 +167,13 @@ func (encCfg *EncoderConfig) createEncoderInternal(cache, internalCache encoderC
internalCache[rType] = w.encoder
rebuildMap[rType] = w
} else {
v := pointerJsonMarshalerEncoder(rType)
v := pointerJSONMarshalerEncoder(rType)
internalCache[rType] = v
cache[rType] = &directEncoder{v}
}
continue
}
v := &directJsonMarshalerEncoder{
v := &directJSONMarshalerEncoder{
isEmpty: isEmptyFunctions[kind],
rtype: rType,
}
Expand Down Expand Up @@ -357,7 +362,7 @@ func (encCfg *EncoderConfig) createEncoderInternal(cache, internalCache encoderC
for i := range x.fields {
fi := &x.fields[i]
v := internalCache.preferPtrEncoder(fi.ptrType.Elem())
x.encoder.fields.add(fi, encCfg.escapeHtml, v)
x.encoder.fields.add(fi, v)
}
if ifaceIndir(rType) {
cache[rType] = x.encoder
Expand Down
7 changes: 4 additions & 3 deletions encoder_config_adaptor.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package jzon

// NewStreamer returns a new streamer.
func (encCfg *EncoderConfig) NewStreamer() *Streamer {
s := defaultStreamerPool.BorrowStreamer()
s := defaultStreamerPool.borrowStreamer()
s.cfg = encCfg
s.EscapeHTML(s.cfg.escapeHtml)
s.EscapeHTML(s.cfg.escapeHTML)
return s
}

func (encCfg *EncoderConfig) returnStreamer(s *Streamer) {
s.cfg = nil
defaultStreamerPool.ReturnStreamer(s)
defaultStreamerPool.returnStreamer(s)
}
53 changes: 34 additions & 19 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,42 @@ var _ encFace = &json.Encoder{}
var _ encFace = &Encoder{}

func TestEncoder_SetEscapeHTML(t *testing.T) {
must := require.New(t)
s := "<>&"
buf := bytes.NewBuffer(nil)
f := func(enc encFace) {
// enabled
buf.Reset()
err := enc.Encode(s)
must.NoError(err)
must.Equal(`"\u003c\u003e\u0026"`+"\n", buf.String(), "%T", enc)
f := func(t *testing.T, o interface{}, expOn, expOff string) {
must := require.New(t)
buf := bytes.NewBuffer(nil)
f := func(enc encFace) {
// enabled
buf.Reset()
err := enc.Encode(o)
must.NoError(err)
must.Equal(expOn+"\n", buf.String(), "%T", enc)

// disabled
buf.Reset()
enc.SetEscapeHTML(false)
err = enc.Encode(s)
must.NoError(err)
must.Equal(`"`+s+`"`+"\n", buf.String(), "%T", enc)
// disabled
buf.Reset()
enc.SetEscapeHTML(false)
err = enc.Encode(o)
must.NoError(err)
must.Equal(expOff+"\n", buf.String(), "%T", enc)
}
f(json.NewEncoder(buf))
enc := NewEncoder(buf)
defer enc.Release()
f(enc)
}
f(json.NewEncoder(buf))
enc := NewEncoder(buf)
defer enc.Release()
f(enc)
t.Run("normal", func(t *testing.T) {
s := "<>&"
expOn := `"\u003c\u003e\u0026"`
expOff := `"` + s + `"`
f(t, s, expOn, expOff)
})
t.Run("struct", func(t *testing.T) {
s := struct {
A int `json:"&"`
}{}
expOn := `{"\u0026":0}`
expOff := `{"&":0}`
f(t, s, expOn, expOff)
})
}

// func TestEncoder_SetIndent(t *testing.T) {
Expand Down
Loading

0 comments on commit 39fd02e

Please sign in to comment.