stream_str.go (7986B)
1 package jsoniter 2 3 import ( 4 "unicode/utf8" 5 ) 6 7 // htmlSafeSet holds the value true if the ASCII character with the given 8 // array position can be safely represented inside a JSON string, embedded 9 // inside of HTML <script> tags, without any additional escaping. 10 // 11 // All values are true except for the ASCII control characters (0-31), the 12 // double quote ("), the backslash character ("\"), HTML opening and closing 13 // tags ("<" and ">"), and the ampersand ("&"). 14 var htmlSafeSet = [utf8.RuneSelf]bool{ 15 ' ': true, 16 '!': true, 17 '"': false, 18 '#': true, 19 '$': true, 20 '%': true, 21 '&': false, 22 '\'': true, 23 '(': true, 24 ')': true, 25 '*': true, 26 '+': true, 27 ',': true, 28 '-': true, 29 '.': true, 30 '/': true, 31 '0': true, 32 '1': true, 33 '2': true, 34 '3': true, 35 '4': true, 36 '5': true, 37 '6': true, 38 '7': true, 39 '8': true, 40 '9': true, 41 ':': true, 42 ';': true, 43 '<': false, 44 '=': true, 45 '>': false, 46 '?': true, 47 '@': true, 48 'A': true, 49 'B': true, 50 'C': true, 51 'D': true, 52 'E': true, 53 'F': true, 54 'G': true, 55 'H': true, 56 'I': true, 57 'J': true, 58 'K': true, 59 'L': true, 60 'M': true, 61 'N': true, 62 'O': true, 63 'P': true, 64 'Q': true, 65 'R': true, 66 'S': true, 67 'T': true, 68 'U': true, 69 'V': true, 70 'W': true, 71 'X': true, 72 'Y': true, 73 'Z': true, 74 '[': true, 75 '\\': false, 76 ']': true, 77 '^': true, 78 '_': true, 79 '`': true, 80 'a': true, 81 'b': true, 82 'c': true, 83 'd': true, 84 'e': true, 85 'f': true, 86 'g': true, 87 'h': true, 88 'i': true, 89 'j': true, 90 'k': true, 91 'l': true, 92 'm': true, 93 'n': true, 94 'o': true, 95 'p': true, 96 'q': true, 97 'r': true, 98 's': true, 99 't': true, 100 'u': true, 101 'v': true, 102 'w': true, 103 'x': true, 104 'y': true, 105 'z': true, 106 '{': true, 107 '|': true, 108 '}': true, 109 '~': true, 110 '\u007f': true, 111 } 112 113 // safeSet holds the value true if the ASCII character with the given array 114 // position can be represented inside a JSON string without any further 115 // escaping. 116 // 117 // All values are true except for the ASCII control characters (0-31), the 118 // double quote ("), and the backslash character ("\"). 119 var safeSet = [utf8.RuneSelf]bool{ 120 ' ': true, 121 '!': true, 122 '"': false, 123 '#': true, 124 '$': true, 125 '%': true, 126 '&': true, 127 '\'': true, 128 '(': true, 129 ')': true, 130 '*': true, 131 '+': true, 132 ',': true, 133 '-': true, 134 '.': true, 135 '/': true, 136 '0': true, 137 '1': true, 138 '2': true, 139 '3': true, 140 '4': true, 141 '5': true, 142 '6': true, 143 '7': true, 144 '8': true, 145 '9': true, 146 ':': true, 147 ';': true, 148 '<': true, 149 '=': true, 150 '>': true, 151 '?': true, 152 '@': true, 153 'A': true, 154 'B': true, 155 'C': true, 156 'D': true, 157 'E': true, 158 'F': true, 159 'G': true, 160 'H': true, 161 'I': true, 162 'J': true, 163 'K': true, 164 'L': true, 165 'M': true, 166 'N': true, 167 'O': true, 168 'P': true, 169 'Q': true, 170 'R': true, 171 'S': true, 172 'T': true, 173 'U': true, 174 'V': true, 175 'W': true, 176 'X': true, 177 'Y': true, 178 'Z': true, 179 '[': true, 180 '\\': false, 181 ']': true, 182 '^': true, 183 '_': true, 184 '`': true, 185 'a': true, 186 'b': true, 187 'c': true, 188 'd': true, 189 'e': true, 190 'f': true, 191 'g': true, 192 'h': true, 193 'i': true, 194 'j': true, 195 'k': true, 196 'l': true, 197 'm': true, 198 'n': true, 199 'o': true, 200 'p': true, 201 'q': true, 202 'r': true, 203 's': true, 204 't': true, 205 'u': true, 206 'v': true, 207 'w': true, 208 'x': true, 209 'y': true, 210 'z': true, 211 '{': true, 212 '|': true, 213 '}': true, 214 '~': true, 215 '\u007f': true, 216 } 217 218 var hex = "0123456789abcdef" 219 220 // WriteStringWithHTMLEscaped write string to stream with html special characters escaped 221 func (stream *Stream) WriteStringWithHTMLEscaped(s string) { 222 valLen := len(s) 223 stream.buf = append(stream.buf, '"') 224 // write string, the fast path, without utf8 and escape support 225 i := 0 226 for ; i < valLen; i++ { 227 c := s[i] 228 if c < utf8.RuneSelf && htmlSafeSet[c] { 229 stream.buf = append(stream.buf, c) 230 } else { 231 break 232 } 233 } 234 if i == valLen { 235 stream.buf = append(stream.buf, '"') 236 return 237 } 238 writeStringSlowPathWithHTMLEscaped(stream, i, s, valLen) 239 } 240 241 func writeStringSlowPathWithHTMLEscaped(stream *Stream, i int, s string, valLen int) { 242 start := i 243 // for the remaining parts, we process them char by char 244 for i < valLen { 245 if b := s[i]; b < utf8.RuneSelf { 246 if htmlSafeSet[b] { 247 i++ 248 continue 249 } 250 if start < i { 251 stream.WriteRaw(s[start:i]) 252 } 253 switch b { 254 case '\\', '"': 255 stream.writeTwoBytes('\\', b) 256 case '\n': 257 stream.writeTwoBytes('\\', 'n') 258 case '\r': 259 stream.writeTwoBytes('\\', 'r') 260 case '\t': 261 stream.writeTwoBytes('\\', 't') 262 default: 263 // This encodes bytes < 0x20 except for \t, \n and \r. 264 // If escapeHTML is set, it also escapes <, >, and & 265 // because they can lead to security holes when 266 // user-controlled strings are rendered into JSON 267 // and served to some browsers. 268 stream.WriteRaw(`\u00`) 269 stream.writeTwoBytes(hex[b>>4], hex[b&0xF]) 270 } 271 i++ 272 start = i 273 continue 274 } 275 c, size := utf8.DecodeRuneInString(s[i:]) 276 if c == utf8.RuneError && size == 1 { 277 if start < i { 278 stream.WriteRaw(s[start:i]) 279 } 280 stream.WriteRaw(`\ufffd`) 281 i++ 282 start = i 283 continue 284 } 285 // U+2028 is LINE SEPARATOR. 286 // U+2029 is PARAGRAPH SEPARATOR. 287 // They are both technically valid characters in JSON strings, 288 // but don't work in JSONP, which has to be evaluated as JavaScript, 289 // and can lead to security holes there. It is valid JSON to 290 // escape them, so we do so unconditionally. 291 // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. 292 if c == '\u2028' || c == '\u2029' { 293 if start < i { 294 stream.WriteRaw(s[start:i]) 295 } 296 stream.WriteRaw(`\u202`) 297 stream.writeByte(hex[c&0xF]) 298 i += size 299 start = i 300 continue 301 } 302 i += size 303 } 304 if start < len(s) { 305 stream.WriteRaw(s[start:]) 306 } 307 stream.writeByte('"') 308 } 309 310 // WriteString write string to stream without html escape 311 func (stream *Stream) WriteString(s string) { 312 valLen := len(s) 313 stream.buf = append(stream.buf, '"') 314 // write string, the fast path, without utf8 and escape support 315 i := 0 316 for ; i < valLen; i++ { 317 c := s[i] 318 if c > 31 && c != '"' && c != '\\' { 319 stream.buf = append(stream.buf, c) 320 } else { 321 break 322 } 323 } 324 if i == valLen { 325 stream.buf = append(stream.buf, '"') 326 return 327 } 328 writeStringSlowPath(stream, i, s, valLen) 329 } 330 331 func writeStringSlowPath(stream *Stream, i int, s string, valLen int) { 332 start := i 333 // for the remaining parts, we process them char by char 334 for i < valLen { 335 if b := s[i]; b < utf8.RuneSelf { 336 if safeSet[b] { 337 i++ 338 continue 339 } 340 if start < i { 341 stream.WriteRaw(s[start:i]) 342 } 343 switch b { 344 case '\\', '"': 345 stream.writeTwoBytes('\\', b) 346 case '\n': 347 stream.writeTwoBytes('\\', 'n') 348 case '\r': 349 stream.writeTwoBytes('\\', 'r') 350 case '\t': 351 stream.writeTwoBytes('\\', 't') 352 default: 353 // This encodes bytes < 0x20 except for \t, \n and \r. 354 // If escapeHTML is set, it also escapes <, >, and & 355 // because they can lead to security holes when 356 // user-controlled strings are rendered into JSON 357 // and served to some browsers. 358 stream.WriteRaw(`\u00`) 359 stream.writeTwoBytes(hex[b>>4], hex[b&0xF]) 360 } 361 i++ 362 start = i 363 continue 364 } 365 i++ 366 continue 367 } 368 if start < len(s) { 369 stream.WriteRaw(s[start:]) 370 } 371 stream.writeByte('"') 372 }