| | |
| | |
| | |
| |
|
| | |
| |
|
| | package jsonwire |
| |
|
| | import ( |
| | "math" |
| | "slices" |
| | "strconv" |
| | "unicode/utf16" |
| | "unicode/utf8" |
| |
|
| | "encoding/json/internal/jsonflags" |
| | ) |
| |
|
| | |
| | |
| | var escapeASCII = [...]uint8{ |
| | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| | 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, |
| | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, |
| | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| | } |
| |
|
| | |
| | |
| | |
| | func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool { |
| | var i int |
| | for uint(len(src)) > uint(i) { |
| | if c := src[i]; c < utf8.RuneSelf { |
| | if escapeASCII[c] > 0 { |
| | return true |
| | } |
| | i++ |
| | } else { |
| | r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[i:]))) |
| | if r == utf8.RuneError || r == '\u2028' || r == '\u2029' { |
| | return true |
| | } |
| | i += rn |
| | } |
| | } |
| | return false |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflags.Flags) ([]byte, error) { |
| | var i, n int |
| | var hasInvalidUTF8 bool |
| | dst = slices.Grow(dst, len(`"`)+len(src)+len(`"`)) |
| | dst = append(dst, '"') |
| | for uint(len(src)) > uint(n) { |
| | if c := src[n]; c < utf8.RuneSelf { |
| | |
| | n++ |
| | if escapeASCII[c] == 0 { |
| | continue |
| | } |
| | |
| | if !(c == '<' || c == '>' || c == '&') || flags.Get(jsonflags.EscapeForHTML) { |
| | dst = append(dst, src[i:n-1]...) |
| | dst = appendEscapedASCII(dst, c) |
| | i = n |
| | } |
| | } else { |
| | |
| | r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:]))) |
| | n += rn |
| | if r != utf8.RuneError && r != '\u2028' && r != '\u2029' { |
| | continue |
| | } |
| | |
| | switch { |
| | case isInvalidUTF8(r, rn): |
| | hasInvalidUTF8 = true |
| | dst = append(dst, src[i:n-rn]...) |
| | dst = append(dst, "\ufffd"...) |
| | i = n |
| | case (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS): |
| | dst = append(dst, src[i:n-rn]...) |
| | dst = appendEscapedUnicode(dst, r) |
| | i = n |
| | } |
| | } |
| | } |
| | dst = append(dst, src[i:n]...) |
| | dst = append(dst, '"') |
| | if hasInvalidUTF8 && !flags.Get(jsonflags.AllowInvalidUTF8) { |
| | return dst, ErrInvalidUTF8 |
| | } |
| | return dst, nil |
| | } |
| |
|
| | func appendEscapedASCII(dst []byte, c byte) []byte { |
| | switch c { |
| | case '"', '\\': |
| | dst = append(dst, '\\', c) |
| | case '\b': |
| | dst = append(dst, "\\b"...) |
| | case '\f': |
| | dst = append(dst, "\\f"...) |
| | case '\n': |
| | dst = append(dst, "\\n"...) |
| | case '\r': |
| | dst = append(dst, "\\r"...) |
| | case '\t': |
| | dst = append(dst, "\\t"...) |
| | default: |
| | dst = appendEscapedUTF16(dst, uint16(c)) |
| | } |
| | return dst |
| | } |
| |
|
| | func appendEscapedUnicode(dst []byte, r rune) []byte { |
| | if r1, r2 := utf16.EncodeRune(r); r1 != '\ufffd' && r2 != '\ufffd' { |
| | dst = appendEscapedUTF16(dst, uint16(r1)) |
| | dst = appendEscapedUTF16(dst, uint16(r2)) |
| | } else { |
| | dst = appendEscapedUTF16(dst, uint16(r)) |
| | } |
| | return dst |
| | } |
| |
|
| | func appendEscapedUTF16(dst []byte, x uint16) []byte { |
| | const hex = "0123456789abcdef" |
| | return append(dst, '\\', 'u', hex[(x>>12)&0xf], hex[(x>>8)&0xf], hex[(x>>4)&0xf], hex[(x>>0)&0xf]) |
| | } |
| |
|
| | |
| | |
| | |
| | func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) { |
| | |
| | var valFlags ValueFlags |
| | n, err := ConsumeString(&valFlags, src, !flags.Get(jsonflags.AllowInvalidUTF8)) |
| | if err != nil { |
| | return dst, n, err |
| | } |
| |
|
| | |
| | |
| | |
| | if !flags.Get(jsonflags.AnyEscape) && |
| | (valFlags.IsCanonical() || flags.Get(jsonflags.PreserveRawStrings)) { |
| | dst = append(dst, src[:n]...) |
| | return dst, n, nil |
| | } |
| |
|
| | |
| | |
| | |
| | if flags.Get(jsonflags.PreserveRawStrings) { |
| | var i, lastAppendIndex int |
| | for i < n { |
| | if c := src[i]; c < utf8.RuneSelf { |
| | if (c == '<' || c == '>' || c == '&') && flags.Get(jsonflags.EscapeForHTML) { |
| | dst = append(dst, src[lastAppendIndex:i]...) |
| | dst = appendEscapedASCII(dst, c) |
| | lastAppendIndex = i + 1 |
| | } |
| | i++ |
| | } else { |
| | r, rn := utf8.DecodeRune(truncateMaxUTF8(src[i:])) |
| | if (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS) { |
| | dst = append(dst, src[lastAppendIndex:i]...) |
| | dst = appendEscapedUnicode(dst, r) |
| | lastAppendIndex = i + rn |
| | } |
| | i += rn |
| | } |
| | } |
| | return append(dst, src[lastAppendIndex:n]...), n, nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | b, _ := AppendUnquote(nil, src[:n]) |
| | dst, _ = AppendQuote(dst, b, flags) |
| | return dst, n, nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func AppendFloat(dst []byte, src float64, bits int) []byte { |
| | if bits == 32 { |
| | src = float64(float32(src)) |
| | } |
| |
|
| | abs := math.Abs(src) |
| | fmt := byte('f') |
| | if abs != 0 { |
| | if bits == 64 && (float64(abs) < 1e-6 || float64(abs) >= 1e21) || |
| | bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { |
| | fmt = 'e' |
| | } |
| | } |
| | dst = strconv.AppendFloat(dst, src, fmt, -1, bits) |
| | if fmt == 'e' { |
| | |
| | n := len(dst) |
| | if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' { |
| | dst[n-2] = dst[n-1] |
| | dst = dst[:n-1] |
| | } |
| | } |
| | return dst |
| | } |
| |
|
| | |
| | |
| | |
| | func ReformatNumber(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) { |
| | n, err := ConsumeNumber(src) |
| | if err != nil { |
| | return dst, n, err |
| | } |
| | if !flags.Get(jsonflags.CanonicalizeNumbers) { |
| | dst = append(dst, src[:n]...) |
| | return dst, n, nil |
| | } |
| |
|
| | |
| | var isFloat bool |
| | for _, c := range src[:n] { |
| | if c == '.' || c == 'e' || c == 'E' { |
| | isFloat = true |
| | break |
| | } |
| | } |
| |
|
| | |
| | switch { |
| | case string(src[:n]) == "-0": |
| | break |
| | case isFloat: |
| | if !flags.Get(jsonflags.CanonicalizeRawFloats) { |
| | dst = append(dst, src[:n]...) |
| | return dst, n, nil |
| | } |
| | default: |
| | |
| | |
| | const maxExactIntegerDigits = 16 |
| | if !flags.Get(jsonflags.CanonicalizeRawInts) || n < maxExactIntegerDigits { |
| | dst = append(dst, src[:n]...) |
| | return dst, n, nil |
| | } |
| | } |
| |
|
| | |
| | fv, _ := strconv.ParseFloat(string(src[:n]), 64) |
| | switch { |
| | case fv == 0: |
| | fv = 0 |
| | case math.IsInf(fv, +1): |
| | fv = +math.MaxFloat64 |
| | case math.IsInf(fv, -1): |
| | fv = -math.MaxFloat64 |
| | } |
| | return AppendFloat(dst, fv, 64), n, nil |
| | } |
| |
|