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