in json/visitor.go [205:285]
func (vs *Visitor) OnString(s string) error {
if err := vs.tryElemNext(); err != nil {
return err
}
escapeSet := vs.escapeSet
vs.writeByte('"')
start := 0
for i := 0; i < len(s); {
if b := s[i]; b < utf8.RuneSelf {
if !escapeSet[b] {
i++
continue
}
if start < i {
vs.writeString(s[start:i])
}
switch b {
case '\\', '"':
vs.scratch[0], vs.scratch[1] = '\\', b
vs.w.write(vs.scratch[:2])
case '\n':
vs.scratch[0], vs.scratch[1] = '\\', 'n'
vs.w.write(vs.scratch[:2])
case '\r':
vs.scratch[0], vs.scratch[1] = '\\', 'r'
vs.w.write(vs.scratch[:2])
case '\t':
vs.scratch[0], vs.scratch[1] = '\\', 't'
vs.w.write(vs.scratch[:2])
default:
// This vsodes bytes < 0x20 except for \n and \r,
// as well as <, > and &. The latter are escaped because they
// can lead to security holes when user-controlled strings
// are rendered into JSON and served to some browsers.
vs.scratch[0], vs.scratch[1], vs.scratch[2], vs.scratch[3] = '\\', 'u', '0', '0'
vs.scratch[4] = hex[b>>4]
vs.scratch[5] = hex[b&0xF]
vs.w.write(vs.scratch[:6])
}
i++
start = i
continue
}
c, size := utf8.DecodeRuneInString(s[i:])
if c == utf8.RuneError && size == 1 {
if start < i {
vs.writeString(s[start:i])
}
vs.w.write(invalidCharSym)
i += size
start = i
continue
}
// U+2028 is LINE SEPARATOR.
// U+2029 is PARAGRAPH SEPARATOR.
// They are both technically valid characters in JSON strings,
// but don't work in JSONP, which has to be evaluated as JavaScript,
// and can lead to security holes there. It is valid JSON to
// escape them, so we do so unconditionally.
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
if c == '\u2028' || c == '\u2029' {
if start < i {
vs.writeString(s[start:i])
}
vs.writeString(`\u202`)
vs.writeByte(hex[c&0xF])
i += size
start = i
continue
}
i += size
}
if start < len(s) {
vs.writeString(s[start:])
}
vs.writeByte('"')
return nil
}