1// Copyright 2010 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package json 6 7import "bytes" 8 9// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 10// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 11// so that the JSON will be safe to embed inside HTML <script> tags. 12// For historical reasons, web browsers don't honor standard HTML 13// escaping within <script> tags, so an alternative JSON encoding must be used. 14func HTMLEscape(dst *bytes.Buffer, src []byte) { 15 dst.Grow(len(src)) 16 dst.Write(appendHTMLEscape(dst.AvailableBuffer(), src)) 17} 18 19func appendHTMLEscape(dst, src []byte) []byte { 20 // The characters can only appear in string literals, 21 // so just scan the string one byte at a time. 22 start := 0 23 for i, c := range src { 24 if c == '<' || c == '>' || c == '&' { 25 dst = append(dst, src[start:i]...) 26 dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF]) 27 start = i + 1 28 } 29 // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9). 30 if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 { 31 dst = append(dst, src[start:i]...) 32 dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF]) 33 start = i + len("\u2029") 34 } 35 } 36 return append(dst, src[start:]...) 37} 38 39// Compact appends to dst the JSON-encoded src with 40// insignificant space characters elided. 41func Compact(dst *bytes.Buffer, src []byte) error { 42 dst.Grow(len(src)) 43 b := dst.AvailableBuffer() 44 b, err := appendCompact(b, src, false) 45 dst.Write(b) 46 return err 47} 48 49func appendCompact(dst, src []byte, escape bool) ([]byte, error) { 50 origLen := len(dst) 51 scan := newScanner() 52 defer freeScanner(scan) 53 start := 0 54 for i, c := range src { 55 if escape && (c == '<' || c == '>' || c == '&') { 56 if start < i { 57 dst = append(dst, src[start:i]...) 58 } 59 dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF]) 60 start = i + 1 61 } 62 // Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9). 63 if escape && c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 { 64 if start < i { 65 dst = append(dst, src[start:i]...) 66 } 67 dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF]) 68 start = i + 3 69 } 70 v := scan.step(scan, c) 71 if v >= scanSkipSpace { 72 if v == scanError { 73 break 74 } 75 if start < i { 76 dst = append(dst, src[start:i]...) 77 } 78 start = i + 1 79 } 80 } 81 if scan.eof() == scanError { 82 return dst[:origLen], scan.err 83 } 84 if start < len(src) { 85 dst = append(dst, src[start:]...) 86 } 87 return dst, nil 88} 89 90func appendNewline(dst []byte, prefix, indent string, depth int) []byte { 91 dst = append(dst, '\n') 92 dst = append(dst, prefix...) 93 for i := 0; i < depth; i++ { 94 dst = append(dst, indent...) 95 } 96 return dst 97} 98 99// indentGrowthFactor specifies the growth factor of indenting JSON input. 100// Empirically, the growth factor was measured to be between 1.4x to 1.8x 101// for some set of compacted JSON with the indent being a single tab. 102// Specify a growth factor slightly larger than what is observed 103// to reduce probability of allocation in appendIndent. 104// A factor no higher than 2 ensures that wasted space never exceeds 50%. 105const indentGrowthFactor = 2 106 107// Indent appends to dst an indented form of the JSON-encoded src. 108// Each element in a JSON object or array begins on a new, 109// indented line beginning with prefix followed by one or more 110// copies of indent according to the indentation nesting. 111// The data appended to dst does not begin with the prefix nor 112// any indentation, to make it easier to embed inside other formatted JSON data. 113// Although leading space characters (space, tab, carriage return, newline) 114// at the beginning of src are dropped, trailing space characters 115// at the end of src are preserved and copied to dst. 116// For example, if src has no trailing spaces, neither will dst; 117// if src ends in a trailing newline, so will dst. 118func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { 119 dst.Grow(indentGrowthFactor * len(src)) 120 b := dst.AvailableBuffer() 121 b, err := appendIndent(b, src, prefix, indent) 122 dst.Write(b) 123 return err 124} 125 126func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) { 127 origLen := len(dst) 128 scan := newScanner() 129 defer freeScanner(scan) 130 needIndent := false 131 depth := 0 132 for _, c := range src { 133 scan.bytes++ 134 v := scan.step(scan, c) 135 if v == scanSkipSpace { 136 continue 137 } 138 if v == scanError { 139 break 140 } 141 if needIndent && v != scanEndObject && v != scanEndArray { 142 needIndent = false 143 depth++ 144 dst = appendNewline(dst, prefix, indent, depth) 145 } 146 147 // Emit semantically uninteresting bytes 148 // (in particular, punctuation in strings) unmodified. 149 if v == scanContinue { 150 dst = append(dst, c) 151 continue 152 } 153 154 // Add spacing around real punctuation. 155 switch c { 156 case '{', '[': 157 // delay indent so that empty object and array are formatted as {} and []. 158 needIndent = true 159 dst = append(dst, c) 160 case ',': 161 dst = append(dst, c) 162 dst = appendNewline(dst, prefix, indent, depth) 163 case ':': 164 dst = append(dst, c, ' ') 165 case '}', ']': 166 if needIndent { 167 // suppress indent in empty object/array 168 needIndent = false 169 } else { 170 depth-- 171 dst = appendNewline(dst, prefix, indent, depth) 172 } 173 dst = append(dst, c) 174 default: 175 dst = append(dst, c) 176 } 177 } 178 if scan.eof() == scanError { 179 return dst[:origLen], scan.err 180 } 181 return dst, nil 182} 183