Source file src/html/template/js.go
1
2
3
4
5 package template
6
7 import (
8 "bytes"
9 "encoding/json"
10 "fmt"
11 "reflect"
12 "strings"
13 "unicode/utf8"
14 )
15
16
17
18
19
20
21
22
23
24
25
26
27
28 func nextJSCtx(s []byte, preceding jsCtx) jsCtx {
29 s = bytes.TrimRight(s, "\t\n\f\r \u2028\u2029")
30 if len(s) == 0 {
31 return preceding
32 }
33
34
35 switch c, n := s[len(s)-1], len(s); c {
36 case '+', '-':
37
38
39 start := n - 1
40
41 for start > 0 && s[start-1] == c {
42 start--
43 }
44 if (n-start)&1 == 1 {
45
46
47 return jsCtxRegexp
48 }
49 return jsCtxDivOp
50 case '.':
51
52 if n != 1 && '0' <= s[n-2] && s[n-2] <= '9' {
53 return jsCtxDivOp
54 }
55 return jsCtxRegexp
56
57
58 case ',', '<', '>', '=', '*', '%', '&', '|', '^', '?':
59 return jsCtxRegexp
60
61
62 case '!', '~':
63 return jsCtxRegexp
64
65
66 case '(', '[':
67 return jsCtxRegexp
68
69
70 case ':', ';', '{':
71 return jsCtxRegexp
72
73
74
75
76
77
78
79
80
81
82
83 case '}':
84 return jsCtxRegexp
85 default:
86
87
88 j := n
89 for j > 0 && isJSIdentPart(rune(s[j-1])) {
90 j--
91 }
92 if regexpPrecederKeywords[string(s[j:])] {
93 return jsCtxRegexp
94 }
95 }
96
97
98
99 return jsCtxDivOp
100 }
101
102
103
104 var regexpPrecederKeywords = map[string]bool{
105 "break": true,
106 "case": true,
107 "continue": true,
108 "delete": true,
109 "do": true,
110 "else": true,
111 "finally": true,
112 "in": true,
113 "instanceof": true,
114 "return": true,
115 "throw": true,
116 "try": true,
117 "typeof": true,
118 "void": true,
119 }
120
121 var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
122
123
124
125 func indirectToJSONMarshaler(a interface{}) interface{} {
126
127
128
129
130 if a == nil {
131 return nil
132 }
133
134 v := reflect.ValueOf(a)
135 for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
136 v = v.Elem()
137 }
138 return v.Interface()
139 }
140
141
142
143 func jsValEscaper(args ...interface{}) string {
144 var a interface{}
145 if len(args) == 1 {
146 a = indirectToJSONMarshaler(args[0])
147 switch t := a.(type) {
148 case JS:
149 return string(t)
150 case JSStr:
151
152 return `"` + string(t) + `"`
153 case json.Marshaler:
154
155 case fmt.Stringer:
156 a = t.String()
157 }
158 } else {
159 for i, arg := range args {
160 args[i] = indirectToJSONMarshaler(arg)
161 }
162 a = fmt.Sprint(args...)
163 }
164
165
166
167 b, err := json.Marshal(a)
168 if err != nil {
169
170
171
172
173
174
175 return fmt.Sprintf(" /* %s */null ", strings.ReplaceAll(err.Error(), "*/", "* /"))
176 }
177
178
179
180
181
182
183 if len(b) == 0 {
184
185
186 return " null "
187 }
188 first, _ := utf8.DecodeRune(b)
189 last, _ := utf8.DecodeLastRune(b)
190 var buf strings.Builder
191
192
193 pad := isJSIdentPart(first) || isJSIdentPart(last)
194 if pad {
195 buf.WriteByte(' ')
196 }
197 written := 0
198
199
200 for i := 0; i < len(b); {
201 rune, n := utf8.DecodeRune(b[i:])
202 repl := ""
203 if rune == 0x2028 {
204 repl = `\u2028`
205 } else if rune == 0x2029 {
206 repl = `\u2029`
207 }
208 if repl != "" {
209 buf.Write(b[written:i])
210 buf.WriteString(repl)
211 written = i + n
212 }
213 i += n
214 }
215 if buf.Len() != 0 {
216 buf.Write(b[written:])
217 if pad {
218 buf.WriteByte(' ')
219 }
220 return buf.String()
221 }
222 return string(b)
223 }
224
225
226
227
228 func jsStrEscaper(args ...interface{}) string {
229 s, t := stringify(args...)
230 if t == contentTypeJSStr {
231 return replace(s, jsStrNormReplacementTable)
232 }
233 return replace(s, jsStrReplacementTable)
234 }
235
236
237
238
239
240 func jsRegexpEscaper(args ...interface{}) string {
241 s, _ := stringify(args...)
242 s = replace(s, jsRegexpReplacementTable)
243 if s == "" {
244
245 return "(?:)"
246 }
247 return s
248 }
249
250
251
252
253
254
255 func replace(s string, replacementTable []string) string {
256 var b strings.Builder
257 r, w, written := rune(0), 0, 0
258 for i := 0; i < len(s); i += w {
259
260 r, w = utf8.DecodeRuneInString(s[i:])
261 var repl string
262 switch {
263 case int(r) < len(replacementTable) && replacementTable[r] != "":
264 repl = replacementTable[r]
265 case r == '\u2028':
266 repl = `\u2028`
267 case r == '\u2029':
268 repl = `\u2029`
269 default:
270 continue
271 }
272 if written == 0 {
273 b.Grow(len(s))
274 }
275 b.WriteString(s[written:i])
276 b.WriteString(repl)
277 written = i + w
278 }
279 if written == 0 {
280 return s
281 }
282 b.WriteString(s[written:])
283 return b.String()
284 }
285
286 var jsStrReplacementTable = []string{
287 0: `\0`,
288 '\t': `\t`,
289 '\n': `\n`,
290 '\v': `\x0b`,
291 '\f': `\f`,
292 '\r': `\r`,
293
294
295 '"': `\x22`,
296 '&': `\x26`,
297 '\'': `\x27`,
298 '+': `\x2b`,
299 '/': `\/`,
300 '<': `\x3c`,
301 '>': `\x3e`,
302 '\\': `\\`,
303 }
304
305
306
307 var jsStrNormReplacementTable = []string{
308 0: `\0`,
309 '\t': `\t`,
310 '\n': `\n`,
311 '\v': `\x0b`,
312 '\f': `\f`,
313 '\r': `\r`,
314
315
316 '"': `\x22`,
317 '&': `\x26`,
318 '\'': `\x27`,
319 '+': `\x2b`,
320 '/': `\/`,
321 '<': `\x3c`,
322 '>': `\x3e`,
323 }
324
325 var jsRegexpReplacementTable = []string{
326 0: `\0`,
327 '\t': `\t`,
328 '\n': `\n`,
329 '\v': `\x0b`,
330 '\f': `\f`,
331 '\r': `\r`,
332
333
334 '"': `\x22`,
335 '$': `\$`,
336 '&': `\x26`,
337 '\'': `\x27`,
338 '(': `\(`,
339 ')': `\)`,
340 '*': `\*`,
341 '+': `\x2b`,
342 '-': `\-`,
343 '.': `\.`,
344 '/': `\/`,
345 '<': `\x3c`,
346 '>': `\x3e`,
347 '?': `\?`,
348 '[': `\[`,
349 '\\': `\\`,
350 ']': `\]`,
351 '^': `\^`,
352 '{': `\{`,
353 '|': `\|`,
354 '}': `\}`,
355 }
356
357
358
359
360
361 func isJSIdentPart(r rune) bool {
362 switch {
363 case r == '$':
364 return true
365 case '0' <= r && r <= '9':
366 return true
367 case 'A' <= r && r <= 'Z':
368 return true
369 case r == '_':
370 return true
371 case 'a' <= r && r <= 'z':
372 return true
373 }
374 return false
375 }
376
377
378
379
380 func isJSType(mimeType string) bool {
381
382
383
384
385
386 mimeType = strings.ToLower(mimeType)
387
388 if i := strings.Index(mimeType, ";"); i >= 0 {
389 mimeType = mimeType[:i]
390 }
391 mimeType = strings.TrimSpace(mimeType)
392 switch mimeType {
393 case
394 "application/ecmascript",
395 "application/javascript",
396 "application/json",
397 "application/ld+json",
398 "application/x-ecmascript",
399 "application/x-javascript",
400 "module",
401 "text/ecmascript",
402 "text/javascript",
403 "text/javascript1.0",
404 "text/javascript1.1",
405 "text/javascript1.2",
406 "text/javascript1.3",
407 "text/javascript1.4",
408 "text/javascript1.5",
409 "text/jscript",
410 "text/livescript",
411 "text/x-ecmascript",
412 "text/x-javascript":
413 return true
414 default:
415 return false
416 }
417 }
418
View as plain text