github.com/go-haru/field@v0.0.2/json.go (about) 1 package field 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "unicode/utf8" 8 _ "unsafe" 9 ) 10 11 //go:linkname htmlSafeSet encoding/json.htmlSafeSet 12 var htmlSafeSet [utf8.RuneSelf]bool 13 14 //go:linkname safeSet encoding/json.safeSet 15 var safeSet [utf8.RuneSelf]bool 16 17 var hex = "0123456789abcdef" 18 19 func appendString[Bytes []byte | string](dst []byte, src Bytes, escapeHTML bool) []byte { 20 dst = append(dst, '"') 21 start := 0 22 for i := 0; i < len(src); { 23 if b := src[i]; b < utf8.RuneSelf { 24 if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { 25 i++ 26 continue 27 } 28 dst = append(dst, src[start:i]...) 29 switch b { 30 case '\\', '"': 31 dst = append(dst, '\\', b) 32 case '\n': 33 dst = append(dst, '\\', 'n') 34 case '\r': 35 dst = append(dst, '\\', 'r') 36 case '\t': 37 dst = append(dst, '\\', 't') 38 default: 39 // This encodes bytes < 0x20 except for \t, \n and \r. 40 // If escapeHTML is set, it also escapes <, >, and & 41 // because they can lead to security holes when 42 // user-controlled strings are rendered into JSON 43 // and served to some browsers. 44 dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) 45 } 46 i++ 47 start = i 48 continue 49 } 50 // TODO(https://go.dev/issue/56948): Use generic utf8 functionality. 51 // For now, cast only a small portion of byte slices to a string 52 // so that it can be stack allocated. This slows down []byte slightly 53 // due to the extra copy, but keeps string performance roughly the same. 54 n := len(src) - i 55 if n > utf8.UTFMax { 56 n = utf8.UTFMax 57 } 58 c, size := utf8.DecodeRuneInString(string(src[i : i+n])) 59 if c == utf8.RuneError && size == 1 { 60 dst = append(dst, src[start:i]...) 61 dst = append(dst, `\ufffd`...) 62 i += size 63 start = i 64 continue 65 } 66 // U+2028 is LINE SEPARATOR. 67 // U+2029 is PARAGRAPH SEPARATOR. 68 // They are both technically valid characters in JSON strings, 69 // but don't work in JSONP, which has to be evaluated as JavaScript, 70 // and can lead to security holes there. It is valid JSON to 71 // escape them, so we do so unconditionally. 72 // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. 73 if c == '\u2028' || c == '\u2029' { 74 dst = append(dst, src[start:i]...) 75 dst = append(dst, '\\', 'u', '2', '0', '2', hex[c&0xF]) 76 i += size 77 start = i 78 continue 79 } 80 i += size 81 } 82 dst = append(dst, src[start:]...) 83 dst = append(dst, '"') 84 return dst 85 } 86 87 func appendJsonStringBuf(w Buffer, s string) (err error) { 88 var _, wrote = w.Write(appendString[string](nil, s, false)) 89 return wrote 90 } 91 92 type jsonErr interface { 93 error 94 json.Marshaler 95 } 96 97 func asJsonErrMarshaler(err error) (jsonErr jsonErr) { 98 if err == nil { 99 return nil 100 } 101 if errors.As(err, &jsonErr) { 102 return jsonErr 103 } 104 return jsonErrMarshaler{err} 105 } 106 107 type jsonErrMarshaler struct{ error } 108 109 func (e jsonErrMarshaler) MarshalJSON() ([]byte, error) { 110 if e.error == nil { 111 return []byte("null"), nil 112 } 113 var buffer bytes.Buffer 114 if err := appendJsonStringBuf(&buffer, e.Error()); err != nil { 115 return nil, err 116 } 117 return buffer.Bytes(), nil 118 }