1package jsoniter 2 3import ( 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 ("&"). 14var 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 ("\"). 119var 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 218var hex = "0123456789abcdef" 219 220// WriteStringWithHTMLEscaped write string to stream with html special characters escaped 221func (stream *Stream) WriteStringWithHTMLEscaped(s string) { 222 stream.ensure(32) 223 valLen := len(s) 224 toWriteLen := valLen 225 bufLengthMinusTwo := len(stream.buf) - 2 // make room for the quotes 226 if stream.n+toWriteLen > bufLengthMinusTwo { 227 toWriteLen = bufLengthMinusTwo - stream.n 228 } 229 n := stream.n 230 stream.buf[n] = '"' 231 n++ 232 // write string, the fast path, without utf8 and escape support 233 i := 0 234 for ; i < toWriteLen; i++ { 235 c := s[i] 236 if c < utf8.RuneSelf && htmlSafeSet[c] { 237 stream.buf[n] = c 238 n++ 239 } else { 240 break 241 } 242 } 243 if i == valLen { 244 stream.buf[n] = '"' 245 n++ 246 stream.n = n 247 return 248 } 249 stream.n = n 250 writeStringSlowPathWithHTMLEscaped(stream, i, s, valLen) 251} 252 253func writeStringSlowPathWithHTMLEscaped(stream *Stream, i int, s string, valLen int) { 254 start := i 255 // for the remaining parts, we process them char by char 256 for i < valLen { 257 if b := s[i]; b < utf8.RuneSelf { 258 if htmlSafeSet[b] { 259 i++ 260 continue 261 } 262 if start < i { 263 stream.WriteRaw(s[start:i]) 264 } 265 switch b { 266 case '\\', '"': 267 stream.writeTwoBytes('\\', b) 268 case '\n': 269 stream.writeTwoBytes('\\', 'n') 270 case '\r': 271 stream.writeTwoBytes('\\', 'r') 272 case '\t': 273 stream.writeTwoBytes('\\', 't') 274 default: 275 // This encodes bytes < 0x20 except for \t, \n and \r. 276 // If escapeHTML is set, it also escapes <, >, and & 277 // because they can lead to security holes when 278 // user-controlled strings are rendered into JSON 279 // and served to some browsers. 280 stream.WriteRaw(`\u00`) 281 stream.writeTwoBytes(hex[b>>4], hex[b&0xF]) 282 } 283 i++ 284 start = i 285 continue 286 } 287 c, size := utf8.DecodeRuneInString(s[i:]) 288 if c == utf8.RuneError && size == 1 { 289 if start < i { 290 stream.WriteRaw(s[start:i]) 291 } 292 stream.WriteRaw(`\ufffd`) 293 i++ 294 start = i 295 continue 296 } 297 // U+2028 is LINE SEPARATOR. 298 // U+2029 is PARAGRAPH SEPARATOR. 299 // They are both technically valid characters in JSON strings, 300 // but don't work in JSONP, which has to be evaluated as JavaScript, 301 // and can lead to security holes there. It is valid JSON to 302 // escape them, so we do so unconditionally. 303 // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. 304 if c == '\u2028' || c == '\u2029' { 305 if start < i { 306 stream.WriteRaw(s[start:i]) 307 } 308 stream.WriteRaw(`\u202`) 309 stream.writeByte(hex[c&0xF]) 310 i += size 311 start = i 312 continue 313 } 314 i += size 315 } 316 if start < len(s) { 317 stream.WriteRaw(s[start:]) 318 } 319 stream.writeByte('"') 320} 321 322// WriteString write string to stream without html escape 323func (stream *Stream) WriteString(s string) { 324 stream.ensure(32) 325 valLen := len(s) 326 toWriteLen := valLen 327 bufLengthMinusTwo := len(stream.buf) - 2 // make room for the quotes 328 if stream.n+toWriteLen > bufLengthMinusTwo { 329 toWriteLen = bufLengthMinusTwo - stream.n 330 } 331 n := stream.n 332 stream.buf[n] = '"' 333 n++ 334 // write string, the fast path, without utf8 and escape support 335 i := 0 336 for ; i < toWriteLen; i++ { 337 c := s[i] 338 if c > 31 && c != '"' && c != '\\' { 339 stream.buf[n] = c 340 n++ 341 } else { 342 break 343 } 344 } 345 if i == valLen { 346 stream.buf[n] = '"' 347 n++ 348 stream.n = n 349 return 350 } 351 stream.n = n 352 writeStringSlowPath(stream, i, s, valLen) 353} 354 355func writeStringSlowPath(stream *Stream, i int, s string, valLen int) { 356 start := i 357 // for the remaining parts, we process them char by char 358 for i < valLen { 359 if b := s[i]; b < utf8.RuneSelf { 360 if safeSet[b] { 361 i++ 362 continue 363 } 364 if start < i { 365 stream.WriteRaw(s[start:i]) 366 } 367 switch b { 368 case '\\', '"': 369 stream.writeTwoBytes('\\', b) 370 case '\n': 371 stream.writeTwoBytes('\\', 'n') 372 case '\r': 373 stream.writeTwoBytes('\\', 'r') 374 case '\t': 375 stream.writeTwoBytes('\\', 't') 376 default: 377 // This encodes bytes < 0x20 except for \t, \n and \r. 378 // If escapeHTML is set, it also escapes <, >, and & 379 // because they can lead to security holes when 380 // user-controlled strings are rendered into JSON 381 // and served to some browsers. 382 stream.WriteRaw(`\u00`) 383 stream.writeTwoBytes(hex[b>>4], hex[b&0xF]) 384 } 385 i++ 386 start = i 387 continue 388 } 389 i++ 390 continue 391 } 392 if start < len(s) { 393 stream.WriteRaw(s[start:]) 394 } 395 stream.writeByte('"') 396} 397