mirror of
https://github.com/golang/go.git
synced 2025-05-08 00:53:07 +00:00
encoding/json: add Encoder.DisableHTMLEscaping
This provides a way to disable the escaping of <, >, and & in JSON strings. Fixes #14749. Change-Id: I1afeb0244455fc8b06c6cce920444532f229555b Reviewed-on: https://go-review.googlesource.com/21796 Run-TryBot: Caleb Spare <cespare@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
97360096e5
commit
ab52ad894f
@ -49,6 +49,7 @@ import (
|
|||||||
// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e"
|
// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e"
|
||||||
// to keep some browsers from misinterpreting JSON output as HTML.
|
// to keep some browsers from misinterpreting JSON output as HTML.
|
||||||
// Ampersand "&" is also escaped to "\u0026" for the same reason.
|
// Ampersand "&" is also escaped to "\u0026" for the same reason.
|
||||||
|
// This escaping can be disabled using an Encoder with DisableHTMLEscaping.
|
||||||
//
|
//
|
||||||
// Array and slice values encode as JSON arrays, except that
|
// Array and slice values encode as JSON arrays, except that
|
||||||
// []byte encodes as a base64-encoded string, and a nil slice
|
// []byte encodes as a base64-encoded string, and a nil slice
|
||||||
@ -136,7 +137,7 @@ import (
|
|||||||
//
|
//
|
||||||
func Marshal(v interface{}) ([]byte, error) {
|
func Marshal(v interface{}) ([]byte, error) {
|
||||||
e := &encodeState{}
|
e := &encodeState{}
|
||||||
err := e.marshal(v)
|
err := e.marshal(v, encOpts{escapeHTML: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -259,7 +260,7 @@ func newEncodeState() *encodeState {
|
|||||||
return new(encodeState)
|
return new(encodeState)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encodeState) marshal(v interface{}) (err error) {
|
func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
if _, ok := r.(runtime.Error); ok {
|
if _, ok := r.(runtime.Error); ok {
|
||||||
@ -271,7 +272,7 @@ func (e *encodeState) marshal(v interface{}) (err error) {
|
|||||||
err = r.(error)
|
err = r.(error)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
e.reflectValue(reflect.ValueOf(v))
|
e.reflectValue(reflect.ValueOf(v), opts)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,11 +298,18 @@ func isEmptyValue(v reflect.Value) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encodeState) reflectValue(v reflect.Value) {
|
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
|
||||||
valueEncoder(v)(e, v, false)
|
valueEncoder(v)(e, v, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
type encoderFunc func(e *encodeState, v reflect.Value, quoted bool)
|
type encOpts struct {
|
||||||
|
// quoted causes primitive fields to be encoded inside JSON strings.
|
||||||
|
quoted bool
|
||||||
|
// escapeHTML causes '<', '>', and '&' to be escaped in JSON strings.
|
||||||
|
escapeHTML bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts)
|
||||||
|
|
||||||
var encoderCache struct {
|
var encoderCache struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
@ -333,9 +341,9 @@ func typeEncoder(t reflect.Type) encoderFunc {
|
|||||||
}
|
}
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
encoderCache.m[t] = func(e *encodeState, v reflect.Value, quoted bool) {
|
encoderCache.m[t] = func(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
f(e, v, quoted)
|
f(e, v, opts)
|
||||||
}
|
}
|
||||||
encoderCache.Unlock()
|
encoderCache.Unlock()
|
||||||
|
|
||||||
@ -405,11 +413,11 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidValueEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func invalidValueEncoder(e *encodeState, v reflect.Value, _ encOpts) {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
return
|
return
|
||||||
@ -418,14 +426,14 @@ func marshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
|||||||
b, err := m.MarshalJSON()
|
b, err := m.MarshalJSON()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// copy JSON into buffer, checking validity.
|
// copy JSON into buffer, checking validity.
|
||||||
err = compact(&e.Buffer, b, true)
|
err = compact(&e.Buffer, b, opts.escapeHTML)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.error(&MarshalerError{v.Type(), err})
|
e.error(&MarshalerError{v.Type(), err})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addrMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func addrMarshalerEncoder(e *encodeState, v reflect.Value, _ encOpts) {
|
||||||
va := v.Addr()
|
va := v.Addr()
|
||||||
if va.IsNil() {
|
if va.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
@ -442,7 +450,7 @@ func addrMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func textMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func textMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
return
|
return
|
||||||
@ -452,10 +460,10 @@ func textMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
e.error(&MarshalerError{v.Type(), err})
|
e.error(&MarshalerError{v.Type(), err})
|
||||||
}
|
}
|
||||||
e.stringBytes(b)
|
e.stringBytes(b, opts.escapeHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addrTextMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func addrTextMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
va := v.Addr()
|
va := v.Addr()
|
||||||
if va.IsNil() {
|
if va.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
@ -466,11 +474,11 @@ func addrTextMarshalerEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
e.error(&MarshalerError{v.Type(), err})
|
e.error(&MarshalerError{v.Type(), err})
|
||||||
}
|
}
|
||||||
e.stringBytes(b)
|
e.stringBytes(b, opts.escapeHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func boolEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
if v.Bool() {
|
if v.Bool() {
|
||||||
@ -478,46 +486,46 @@ func boolEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
|||||||
} else {
|
} else {
|
||||||
e.WriteString("false")
|
e.WriteString("false")
|
||||||
}
|
}
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func intEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func intEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
b := strconv.AppendInt(e.scratch[:0], v.Int(), 10)
|
b := strconv.AppendInt(e.scratch[:0], v.Int(), 10)
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
e.Write(b)
|
e.Write(b)
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func uintEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func uintEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
b := strconv.AppendUint(e.scratch[:0], v.Uint(), 10)
|
b := strconv.AppendUint(e.scratch[:0], v.Uint(), 10)
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
e.Write(b)
|
e.Write(b)
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type floatEncoder int // number of bits
|
type floatEncoder int // number of bits
|
||||||
|
|
||||||
func (bits floatEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
|
func (bits floatEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
f := v.Float()
|
f := v.Float()
|
||||||
if math.IsInf(f, 0) || math.IsNaN(f) {
|
if math.IsInf(f, 0) || math.IsNaN(f) {
|
||||||
e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))})
|
e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))})
|
||||||
}
|
}
|
||||||
b := strconv.AppendFloat(e.scratch[:0], f, 'g', -1, int(bits))
|
b := strconv.AppendFloat(e.scratch[:0], f, 'g', -1, int(bits))
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
e.Write(b)
|
e.Write(b)
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -527,7 +535,7 @@ var (
|
|||||||
float64Encoder = (floatEncoder(64)).encode
|
float64Encoder = (floatEncoder(64)).encode
|
||||||
)
|
)
|
||||||
|
|
||||||
func stringEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func stringEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.Type() == numberType {
|
if v.Type() == numberType {
|
||||||
numStr := v.String()
|
numStr := v.String()
|
||||||
// In Go1.5 the empty string encodes to "0", while this is not a valid number literal
|
// In Go1.5 the empty string encodes to "0", while this is not a valid number literal
|
||||||
@ -541,26 +549,26 @@ func stringEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
|||||||
e.WriteString(numStr)
|
e.WriteString(numStr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if quoted {
|
if opts.quoted {
|
||||||
sb, err := Marshal(v.String())
|
sb, err := Marshal(v.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.error(err)
|
e.error(err)
|
||||||
}
|
}
|
||||||
e.string(string(sb))
|
e.string(string(sb), opts.escapeHTML)
|
||||||
} else {
|
} else {
|
||||||
e.string(v.String())
|
e.string(v.String(), opts.escapeHTML)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func interfaceEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func interfaceEncoder(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.reflectValue(v.Elem())
|
e.reflectValue(v.Elem(), opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsupportedTypeEncoder(e *encodeState, v reflect.Value, quoted bool) {
|
func unsupportedTypeEncoder(e *encodeState, v reflect.Value, _ encOpts) {
|
||||||
e.error(&UnsupportedTypeError{v.Type()})
|
e.error(&UnsupportedTypeError{v.Type()})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,7 +577,7 @@ type structEncoder struct {
|
|||||||
fieldEncs []encoderFunc
|
fieldEncs []encoderFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (se *structEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
|
func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
e.WriteByte('{')
|
e.WriteByte('{')
|
||||||
first := true
|
first := true
|
||||||
for i, f := range se.fields {
|
for i, f := range se.fields {
|
||||||
@ -582,9 +590,10 @@ func (se *structEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
|
|||||||
} else {
|
} else {
|
||||||
e.WriteByte(',')
|
e.WriteByte(',')
|
||||||
}
|
}
|
||||||
e.string(f.name)
|
e.string(f.name, opts.escapeHTML)
|
||||||
e.WriteByte(':')
|
e.WriteByte(':')
|
||||||
se.fieldEncs[i](e, fv, f.quoted)
|
opts.quoted = f.quoted
|
||||||
|
se.fieldEncs[i](e, fv, opts)
|
||||||
}
|
}
|
||||||
e.WriteByte('}')
|
e.WriteByte('}')
|
||||||
}
|
}
|
||||||
@ -605,7 +614,7 @@ type mapEncoder struct {
|
|||||||
elemEnc encoderFunc
|
elemEnc encoderFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *mapEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
|
func (me *mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
return
|
return
|
||||||
@ -627,9 +636,9 @@ func (me *mapEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
e.WriteByte(',')
|
e.WriteByte(',')
|
||||||
}
|
}
|
||||||
e.string(kv.s)
|
e.string(kv.s, opts.escapeHTML)
|
||||||
e.WriteByte(':')
|
e.WriteByte(':')
|
||||||
me.elemEnc(e, v.MapIndex(kv.v), false)
|
me.elemEnc(e, v.MapIndex(kv.v), opts)
|
||||||
}
|
}
|
||||||
e.WriteByte('}')
|
e.WriteByte('}')
|
||||||
}
|
}
|
||||||
@ -642,7 +651,7 @@ func newMapEncoder(t reflect.Type) encoderFunc {
|
|||||||
return me.encode
|
return me.encode
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeByteSlice(e *encodeState, v reflect.Value, _ bool) {
|
func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) {
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
return
|
return
|
||||||
@ -669,12 +678,12 @@ type sliceEncoder struct {
|
|||||||
arrayEnc encoderFunc
|
arrayEnc encoderFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
|
func (se *sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
se.arrayEnc(e, v, false)
|
se.arrayEnc(e, v, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSliceEncoder(t reflect.Type) encoderFunc {
|
func newSliceEncoder(t reflect.Type) encoderFunc {
|
||||||
@ -692,14 +701,14 @@ type arrayEncoder struct {
|
|||||||
elemEnc encoderFunc
|
elemEnc encoderFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, _ bool) {
|
func (ae *arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
e.WriteByte('[')
|
e.WriteByte('[')
|
||||||
n := v.Len()
|
n := v.Len()
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
e.WriteByte(',')
|
e.WriteByte(',')
|
||||||
}
|
}
|
||||||
ae.elemEnc(e, v.Index(i), false)
|
ae.elemEnc(e, v.Index(i), opts)
|
||||||
}
|
}
|
||||||
e.WriteByte(']')
|
e.WriteByte(']')
|
||||||
}
|
}
|
||||||
@ -713,12 +722,12 @@ type ptrEncoder struct {
|
|||||||
elemEnc encoderFunc
|
elemEnc encoderFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
|
func (pe *ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
e.WriteString("null")
|
e.WriteString("null")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pe.elemEnc(e, v.Elem(), quoted)
|
pe.elemEnc(e, v.Elem(), opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPtrEncoder(t reflect.Type) encoderFunc {
|
func newPtrEncoder(t reflect.Type) encoderFunc {
|
||||||
@ -730,11 +739,11 @@ type condAddrEncoder struct {
|
|||||||
canAddrEnc, elseEnc encoderFunc
|
canAddrEnc, elseEnc encoderFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ce *condAddrEncoder) encode(e *encodeState, v reflect.Value, quoted bool) {
|
func (ce *condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||||
if v.CanAddr() {
|
if v.CanAddr() {
|
||||||
ce.canAddrEnc(e, v, quoted)
|
ce.canAddrEnc(e, v, opts)
|
||||||
} else {
|
} else {
|
||||||
ce.elseEnc(e, v, quoted)
|
ce.elseEnc(e, v, opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,13 +821,14 @@ func (sv byString) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
|
|||||||
func (sv byString) Less(i, j int) bool { return sv[i].s < sv[j].s }
|
func (sv byString) Less(i, j int) bool { return sv[i].s < sv[j].s }
|
||||||
|
|
||||||
// NOTE: keep in sync with stringBytes below.
|
// NOTE: keep in sync with stringBytes below.
|
||||||
func (e *encodeState) string(s string) int {
|
func (e *encodeState) string(s string, escapeHTML bool) int {
|
||||||
len0 := e.Len()
|
len0 := e.Len()
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
start := 0
|
start := 0
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
if b := s[i]; b < utf8.RuneSelf {
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
if 0x20 <= b && b != '\\' && b != '"' &&
|
||||||
|
(!escapeHTML || b != '<' && b != '>' && b != '&') {
|
||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -839,10 +849,11 @@ func (e *encodeState) string(s string) int {
|
|||||||
e.WriteByte('\\')
|
e.WriteByte('\\')
|
||||||
e.WriteByte('t')
|
e.WriteByte('t')
|
||||||
default:
|
default:
|
||||||
// This encodes bytes < 0x20 except for \n and \r,
|
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||||
// as well as <, > and &. The latter are escaped because they
|
// If escapeHTML is set, it also escapes <, >, and &
|
||||||
// can lead to security holes when user-controlled strings
|
// because they can lead to security holes when
|
||||||
// are rendered into JSON and served to some browsers.
|
// user-controlled strings are rendered into JSON
|
||||||
|
// and served to some browsers.
|
||||||
e.WriteString(`\u00`)
|
e.WriteString(`\u00`)
|
||||||
e.WriteByte(hex[b>>4])
|
e.WriteByte(hex[b>>4])
|
||||||
e.WriteByte(hex[b&0xF])
|
e.WriteByte(hex[b&0xF])
|
||||||
@ -888,13 +899,14 @@ func (e *encodeState) string(s string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: keep in sync with string above.
|
// NOTE: keep in sync with string above.
|
||||||
func (e *encodeState) stringBytes(s []byte) int {
|
func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int {
|
||||||
len0 := e.Len()
|
len0 := e.Len()
|
||||||
e.WriteByte('"')
|
e.WriteByte('"')
|
||||||
start := 0
|
start := 0
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
if b := s[i]; b < utf8.RuneSelf {
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
if 0x20 <= b && b != '\\' && b != '"' &&
|
||||||
|
(!escapeHTML || b != '<' && b != '>' && b != '&') {
|
||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -915,10 +927,11 @@ func (e *encodeState) stringBytes(s []byte) int {
|
|||||||
e.WriteByte('\\')
|
e.WriteByte('\\')
|
||||||
e.WriteByte('t')
|
e.WriteByte('t')
|
||||||
default:
|
default:
|
||||||
// This encodes bytes < 0x20 except for \n and \r,
|
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||||
// as well as <, >, and &. The latter are escaped because they
|
// If escapeHTML is set, it also escapes <, >, and &
|
||||||
// can lead to security holes when user-controlled strings
|
// because they can lead to security holes when
|
||||||
// are rendered into JSON and served to some browsers.
|
// user-controlled strings are rendered into JSON
|
||||||
|
// and served to some browsers.
|
||||||
e.WriteString(`\u00`)
|
e.WriteString(`\u00`)
|
||||||
e.WriteByte(hex[b>>4])
|
e.WriteByte(hex[b>>4])
|
||||||
e.WriteByte(hex[b&0xF])
|
e.WriteByte(hex[b&0xF])
|
||||||
|
@ -376,41 +376,45 @@ func TestDuplicatedFieldDisappears(t *testing.T) {
|
|||||||
|
|
||||||
func TestStringBytes(t *testing.T) {
|
func TestStringBytes(t *testing.T) {
|
||||||
// Test that encodeState.stringBytes and encodeState.string use the same encoding.
|
// Test that encodeState.stringBytes and encodeState.string use the same encoding.
|
||||||
es := &encodeState{}
|
|
||||||
var r []rune
|
var r []rune
|
||||||
for i := '\u0000'; i <= unicode.MaxRune; i++ {
|
for i := '\u0000'; i <= unicode.MaxRune; i++ {
|
||||||
r = append(r, i)
|
r = append(r, i)
|
||||||
}
|
}
|
||||||
s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too
|
s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too
|
||||||
es.string(s)
|
|
||||||
|
|
||||||
esBytes := &encodeState{}
|
for _, escapeHTML := range []bool{true, false} {
|
||||||
esBytes.stringBytes([]byte(s))
|
es := &encodeState{}
|
||||||
|
es.string(s, escapeHTML)
|
||||||
|
|
||||||
enc := es.Buffer.String()
|
esBytes := &encodeState{}
|
||||||
encBytes := esBytes.Buffer.String()
|
esBytes.stringBytes([]byte(s), escapeHTML)
|
||||||
if enc != encBytes {
|
|
||||||
i := 0
|
|
||||||
for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
enc = enc[i:]
|
|
||||||
encBytes = encBytes[i:]
|
|
||||||
i = 0
|
|
||||||
for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
enc = enc[:len(enc)-i]
|
|
||||||
encBytes = encBytes[:len(encBytes)-i]
|
|
||||||
|
|
||||||
if len(enc) > 20 {
|
enc := es.Buffer.String()
|
||||||
enc = enc[:20] + "..."
|
encBytes := esBytes.Buffer.String()
|
||||||
}
|
if enc != encBytes {
|
||||||
if len(encBytes) > 20 {
|
i := 0
|
||||||
encBytes = encBytes[:20] + "..."
|
for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] {
|
||||||
}
|
i++
|
||||||
|
}
|
||||||
|
enc = enc[i:]
|
||||||
|
encBytes = encBytes[i:]
|
||||||
|
i = 0
|
||||||
|
for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
enc = enc[:len(enc)-i]
|
||||||
|
encBytes = encBytes[:len(encBytes)-i]
|
||||||
|
|
||||||
t.Errorf("encodings differ at %#q vs %#q", enc, encBytes)
|
if len(enc) > 20 {
|
||||||
|
enc = enc[:20] + "..."
|
||||||
|
}
|
||||||
|
if len(encBytes) > 20 {
|
||||||
|
encBytes = encBytes[:20] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("with escapeHTML=%t, encodings differ at %#q vs %#q",
|
||||||
|
escapeHTML, enc, encBytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,8 +166,9 @@ func nonSpace(b []byte) bool {
|
|||||||
|
|
||||||
// An Encoder writes JSON values to an output stream.
|
// An Encoder writes JSON values to an output stream.
|
||||||
type Encoder struct {
|
type Encoder struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
err error
|
err error
|
||||||
|
escapeHTML bool
|
||||||
|
|
||||||
indentBuf *bytes.Buffer
|
indentBuf *bytes.Buffer
|
||||||
indentPrefix string
|
indentPrefix string
|
||||||
@ -176,7 +177,7 @@ type Encoder struct {
|
|||||||
|
|
||||||
// NewEncoder returns a new encoder that writes to w.
|
// NewEncoder returns a new encoder that writes to w.
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
return &Encoder{w: w}
|
return &Encoder{w: w, escapeHTML: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode writes the JSON encoding of v to the stream,
|
// Encode writes the JSON encoding of v to the stream,
|
||||||
@ -189,7 +190,7 @@ func (enc *Encoder) Encode(v interface{}) error {
|
|||||||
return enc.err
|
return enc.err
|
||||||
}
|
}
|
||||||
e := newEncodeState()
|
e := newEncodeState()
|
||||||
err := e.marshal(v)
|
err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -225,6 +226,12 @@ func (enc *Encoder) Indent(prefix, indent string) {
|
|||||||
enc.indentValue = indent
|
enc.indentValue = indent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableHTMLEscaping causes the encoder not to escape angle brackets
|
||||||
|
// ("<" and ">") or ampersands ("&") in JSON strings.
|
||||||
|
func (enc *Encoder) DisableHTMLEscaping() {
|
||||||
|
enc.escapeHTML = false
|
||||||
|
}
|
||||||
|
|
||||||
// RawMessage is a raw encoded JSON value.
|
// RawMessage is a raw encoded JSON value.
|
||||||
// It implements Marshaler and Unmarshaler and can
|
// It implements Marshaler and Unmarshaler and can
|
||||||
// be used to delay JSON decoding or precompute a JSON encoding.
|
// be used to delay JSON decoding or precompute a JSON encoding.
|
||||||
|
@ -87,6 +87,39 @@ func TestEncoderIndent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncoderDisableHTMLEscaping(t *testing.T) {
|
||||||
|
var c C
|
||||||
|
var ct CText
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
v interface{}
|
||||||
|
wantEscape string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"c", c, `"\u003c\u0026\u003e"`, `"<&>"`},
|
||||||
|
{"ct", ct, `"\"\u003c\u0026\u003e\""`, `"\"<&>\""`},
|
||||||
|
{`"<&>"`, "<&>", `"\u003c\u0026\u003e"`, `"<&>"`},
|
||||||
|
} {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := NewEncoder(&buf)
|
||||||
|
if err := enc.Encode(tt.v); err != nil {
|
||||||
|
t.Fatalf("Encode(%s): %s", tt.name, err)
|
||||||
|
}
|
||||||
|
if got := strings.TrimSpace(buf.String()); got != tt.wantEscape {
|
||||||
|
t.Errorf("Encode(%s) = %#q, want %#q", tt.name, got, tt.wantEscape)
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
enc.DisableHTMLEscaping()
|
||||||
|
if err := enc.Encode(tt.v); err != nil {
|
||||||
|
t.Fatalf("DisableHTMLEscaping Encode(%s): %s", tt.name, err)
|
||||||
|
}
|
||||||
|
if got := strings.TrimSpace(buf.String()); got != tt.want {
|
||||||
|
t.Errorf("DisableHTMLEscaping Encode(%s) = %#q, want %#q",
|
||||||
|
tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDecoder(t *testing.T) {
|
func TestDecoder(t *testing.T) {
|
||||||
for i := 0; i <= len(streamTest); i++ {
|
for i := 0; i <= len(streamTest); i++ {
|
||||||
// Use stream without newlines as input,
|
// Use stream without newlines as input,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user