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 v := reflect.ValueOf(a)
127 for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
128 v = v.Elem()
129 }
130 return v.Interface()
131 }
132
133
134
135 func jsValEscaper(args ...interface{}) string {
136 var a interface{}
137 if len(args) == 1 {
138 a = indirectToJSONMarshaler(args[0])
139 switch t := a.(type) {
140 case JS:
141 return string(t)
142 case JSStr:
143
144 return `"` + string(t) + `"`
145 case json.Marshaler:
146
147 case fmt.Stringer:
148 a = t.String()
149 }
150 } else {
151 for i, arg := range args {
152 args[i] = indirectToJSONMarshaler(arg)
153 }
154 a = fmt.Sprint(args...)
155 }
156
157
158
159 b, err := json.Marshal(a)
160 if err != nil {
161
162
163
164
165
166
167 return fmt.Sprintf(" /* %s */null ", strings.Replace(err.Error(), "*/", "* /", -1))
168 }
169
170
171
172
173
174
175 if len(b) == 0 {
176
177
178 return " null "
179 }
180 first, _ := utf8.DecodeRune(b)
181 last, _ := utf8.DecodeLastRune(b)
182 var buf bytes.Buffer
183
184
185 pad := isJSIdentPart(first) || isJSIdentPart(last)
186 if pad {
187 buf.WriteByte(' ')
188 }
189 written := 0
190
191
192 for i := 0; i < len(b); {
193 rune, n := utf8.DecodeRune(b[i:])
194 repl := ""
195 if rune == 0x2028 {
196 repl = `\u2028`
197 } else if rune == 0x2029 {
198 repl = `\u2029`
199 }
200 if repl != "" {
201 buf.Write(b[written:i])
202 buf.WriteString(repl)
203 written = i + n
204 }
205 i += n
206 }
207 if buf.Len() != 0 {
208 buf.Write(b[written:])
209 if pad {
210 buf.WriteByte(' ')
211 }
212 b = buf.Bytes()
213 }
214 return string(b)
215 }
216
217
218
219
220 func jsStrEscaper(args ...interface{}) string {
221 s, t := stringify(args...)
222 if t == contentTypeJSStr {
223 return replace(s, jsStrNormReplacementTable)
224 }
225 return replace(s, jsStrReplacementTable)
226 }
227
228
229
230
231
232 func jsRegexpEscaper(args ...interface{}) string {
233 s, _ := stringify(args...)
234 s = replace(s, jsRegexpReplacementTable)
235 if s == "" {
236
237 return "(?:)"
238 }
239 return s
240 }
241
242
243
244
245
246
247 func replace(s string, replacementTable []string) string {
248 var b bytes.Buffer
249 r, w, written := rune(0), 0, 0
250 for i := 0; i < len(s); i += w {
251
252 r, w = utf8.DecodeRuneInString(s[i:])
253 var repl string
254 switch {
255 case int(r) < len(replacementTable) && replacementTable[r] != "":
256 repl = replacementTable[r]
257 case r == '\u2028':
258 repl = `\u2028`
259 case r == '\u2029':
260 repl = `\u2029`
261 default:
262 continue
263 }
264 b.WriteString(s[written:i])
265 b.WriteString(repl)
266 written = i + w
267 }
268 if written == 0 {
269 return s
270 }
271 b.WriteString(s[written:])
272 return b.String()
273 }
274
275 var jsStrReplacementTable = []string{
276 0: `\0`,
277 '\t': `\t`,
278 '\n': `\n`,
279 '\v': `\x0b`,
280 '\f': `\f`,
281 '\r': `\r`,
282
283
284 '"': `\x22`,
285 '&': `\x26`,
286 '\'': `\x27`,
287 '+': `\x2b`,
288 '/': `\/`,
289 '<': `\x3c`,
290 '>': `\x3e`,
291 '\\': `\\`,
292 }
293
294
295
296 var jsStrNormReplacementTable = []string{
297 0: `\0`,
298 '\t': `\t`,
299 '\n': `\n`,
300 '\v': `\x0b`,
301 '\f': `\f`,
302 '\r': `\r`,
303
304
305 '"': `\x22`,
306 '&': `\x26`,
307 '\'': `\x27`,
308 '+': `\x2b`,
309 '/': `\/`,
310 '<': `\x3c`,
311 '>': `\x3e`,
312 }
313
314 var jsRegexpReplacementTable = []string{
315 0: `\0`,
316 '\t': `\t`,
317 '\n': `\n`,
318 '\v': `\x0b`,
319 '\f': `\f`,
320 '\r': `\r`,
321
322
323 '"': `\x22`,
324 '$': `\$`,
325 '&': `\x26`,
326 '\'': `\x27`,
327 '(': `\(`,
328 ')': `\)`,
329 '*': `\*`,
330 '+': `\x2b`,
331 '-': `\-`,
332 '.': `\.`,
333 '/': `\/`,
334 '<': `\x3c`,
335 '>': `\x3e`,
336 '?': `\?`,
337 '[': `\[`,
338 '\\': `\\`,
339 ']': `\]`,
340 '^': `\^`,
341 '{': `\{`,
342 '|': `\|`,
343 '}': `\}`,
344 }
345
346
347
348
349
350 func isJSIdentPart(r rune) bool {
351 switch {
352 case r == '$':
353 return true
354 case '0' <= r && r <= '9':
355 return true
356 case 'A' <= r && r <= 'Z':
357 return true
358 case r == '_':
359 return true
360 case 'a' <= r && r <= 'z':
361 return true
362 }
363 return false
364 }
365
366
367
368
369 func isJSType(mimeType string) bool {
370
371
372
373
374
375 mimeType = strings.ToLower(mimeType)
376
377 if i := strings.Index(mimeType, ";"); i >= 0 {
378 mimeType = mimeType[:i]
379 }
380 mimeType = strings.TrimSpace(mimeType)
381 switch mimeType {
382 case
383 "application/ecmascript",
384 "application/javascript",
385 "application/json",
386 "application/x-ecmascript",
387 "application/x-javascript",
388 "text/ecmascript",
389 "text/javascript",
390 "text/javascript1.0",
391 "text/javascript1.1",
392 "text/javascript1.2",
393 "text/javascript1.3",
394 "text/javascript1.4",
395 "text/javascript1.5",
396 "text/jscript",
397 "text/livescript",
398 "text/x-ecmascript",
399 "text/x-javascript":
400 return true
401 default:
402 return false
403 }
404 }
405
View as plain text