1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
16 // nextJSCtx returns the context that determines whether a slash after the
17 // given run of tokens starts a regular expression instead of a division
20 // This assumes that the token run does not include any string tokens, comment
21 // tokens, regular expression literal tokens, or division operators.
23 // This fails on some valid but nonsensical JavaScript programs like
24 // "x = ++/foo/i" which is quite different than "x++/foo/i", but is not known to
25 // fail on any known useful programs. It is based on the draft
26 // JavaScript 2.0 lexical grammar and requires one token of lookbehind:
27 // http://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html
28 func nextJSCtx(s
[]byte, preceding jsCtx
) jsCtx
{
29 s
= bytes
.TrimRight(s
, "\t\n\f\r \u2028\u2029")
34 // All cases below are in the single-byte UTF-8 group.
35 switch c
, n
:= s
[len(s
)-1], len(s
); c
{
37 // ++ and -- are not regexp preceders, but + and - are whether
38 // they are used as infix or prefix operators.
40 // Count the number of adjacent dashes or pluses.
41 for start
> 0 && s
[start
-1] == c
{
45 // Reached for trailing minus signs since "---" is the
52 if n
!= 1 && '0' <= s
[n
-2] && s
[n
-2] <= '9' {
56 // Suffixes for all punctuators from section 7.7 of the language spec
57 // that only end binary operators not handled above.
58 case ',', '<', '>', '=', '*', '%', '&', '|', '^', '?':
60 // Suffixes for all punctuators from section 7.7 of the language spec
61 // that are prefix operators not handled above.
64 // Matches all the punctuators from section 7.7 of the language spec
65 // that are open brackets not handled above.
68 // Matches all the punctuators from section 7.7 of the language spec
69 // that precede expression starts.
72 // CAVEAT: the close punctuators ('}', ']', ')') precede div ops and
73 // are handled in the default except for '}' which can precede a
75 // ({ valueOf: function () { return 42 } } / 2
76 // which is valid, but, in practice, developers don't divide object
77 // literals, so our heuristic works well for code like
78 // function () { ... } /foo/.test(x) && sideEffect();
79 // The ')' punctuator can precede a regular expression as in
80 // if (b) /foo/.test(x) && ...
81 // but this is much less likely than
86 // Look for an IdentifierName and see if it is a keyword that
87 // can precede a regular expression.
89 for j
> 0 && isJSIdentPart(rune(s
[j
-1])) {
92 if regexpPrecederKeywords
[string(s
[j
:])] {
96 // Otherwise is a punctuator not listed above, or
97 // a string which precedes a div op, or an identifier
98 // which precedes a div op.
102 // regexpPrecederKeywords is a set of reserved JS keywords that can precede a
103 // regular expression in JS source.
104 var regexpPrecederKeywords
= map[string]bool{
121 var jsonMarshalType
= reflect
.TypeOf((*json
.Marshaler
)(nil)).Elem()
123 // indirectToJSONMarshaler returns the value, after dereferencing as many times
124 // as necessary to reach the base type (or nil) or an implementation of json.Marshal.
125 func indirectToJSONMarshaler(a
interface{}) interface{} {
126 v
:= reflect
.ValueOf(a
)
127 for !v
.Type().Implements(jsonMarshalType
) && v
.Kind() == reflect
.Ptr
&& !v
.IsNil() {
133 // jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
134 // neither side-effects nor free variables outside (NaN, Infinity).
135 func jsValEscaper(args
...interface{}) string {
138 a
= indirectToJSONMarshaler(args
[0])
139 switch t
:= a
.(type) {
143 // TODO: normalize quotes.
144 return `"` + string(t
) + `"`
146 // Do not treat as a Stringer.
151 for i
, arg
:= range args
{
152 args
[i
] = indirectToJSONMarshaler(arg
)
154 a
= fmt
.Sprint(args
...)
156 // TODO: detect cycles before calling Marshal which loops infinitely on
157 // cyclic data. This may be an unacceptable DoS risk.
159 b
, err
:= json
.Marshal(a
)
161 // Put a space before comment so that if it is flush against
162 // a division operator it is not turned into a line comment:
165 // x//* error marshaling y:
166 // second line of error message */null
167 return fmt
.Sprintf(" /* %s */null ", strings
.Replace(err
.Error(), "*/", "* /", -1))
170 // TODO: maybe post-process output to prevent it from containing
171 // "<!--", "-->", "<![CDATA[", "]]>", or "</script"
172 // in case custom marshalers produce output containing those.
174 // TODO: Maybe abbreviate \u00ab to \xab to produce more compact output.
176 // In, `x=y/{{.}}*z` a json.Marshaler that produces "" should
177 // not cause the output `x=y/*z`.
180 first
, _
:= utf8
.DecodeRune(b
)
181 last
, _
:= utf8
.DecodeLastRune(b
)
183 // Prevent IdentifierNames and NumericLiterals from running into
184 // keywords: in, instanceof, typeof, void
185 pad
:= isJSIdentPart(first
) ||
isJSIdentPart(last
)
190 // Make sure that json.Marshal escapes codepoints U+2028 & U+2029
191 // so it falls within the subset of JSON which is valid JS.
192 for i
:= 0; i
< len(b
); {
193 rune
, n
:= utf8
.DecodeRune(b
[i
:])
197 } else if rune
== 0x2029 {
201 buf
.Write(b
[written
:i
])
202 buf
.WriteString(repl
)
208 buf
.Write(b
[written
:])
217 // jsStrEscaper produces a string that can be included between quotes in
218 // JavaScript source, in JavaScript embedded in an HTML5 <script> element,
219 // or in an HTML5 event handler attribute such as onclick.
220 func jsStrEscaper(args
...interface{}) string {
221 s
, t
:= stringify(args
...)
222 if t
== contentTypeJSStr
{
223 return replace(s
, jsStrNormReplacementTable
)
225 return replace(s
, jsStrReplacementTable
)
228 // jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
229 // specials so the result is treated literally when included in a regular
230 // expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
231 // the literal text of {{.X}} followed by the string "bar".
232 func jsRegexpEscaper(args
...interface{}) string {
233 s
, _
:= stringify(args
...)
234 s
= replace(s
, jsRegexpReplacementTable
)
236 // /{{.X}}/ should not produce a line comment when .X == "".
242 // replace replaces each rune r of s with replacementTable[r], provided that
243 // r < len(replacementTable). If replacementTable[r] is the empty string then
244 // no replacement is made.
245 // It also replaces runes U+2028 and U+2029 with the raw strings `\u2028` and
247 func replace(s
string, replacementTable
[]string) string {
249 r
, w
, written
:= rune(0), 0, 0
250 for i
:= 0; i
< len(s
); i
+= w
{
251 // See comment in htmlEscaper.
252 r
, w
= utf8
.DecodeRuneInString(s
[i
:])
255 case int(r
) < len(replacementTable
) && replacementTable
[r
] != "":
256 repl
= replacementTable
[r
]
264 b
.WriteString(s
[written
:i
])
271 b
.WriteString(s
[written
:])
275 var jsStrReplacementTable
= []string{
279 '\v': `\x0b`, // "\v" == "v" on IE 6.
282 // Encode HTML specials as hex so the output can be embedded
283 // in HTML attributes without further encoding.
294 // jsStrNormReplacementTable is like jsStrReplacementTable but does not
295 // overencode existing escapes since this table has no entry for `\`.
296 var jsStrNormReplacementTable
= []string{
300 '\v': `\x0b`, // "\v" == "v" on IE 6.
303 // Encode HTML specials as hex so the output can be embedded
304 // in HTML attributes without further encoding.
314 var jsRegexpReplacementTable
= []string{
318 '\v': `\x0b`, // "\v" == "v" on IE 6.
321 // Encode HTML specials as hex so the output can be embedded
322 // in HTML attributes without further encoding.
346 // isJSIdentPart reports whether the given rune is a JS identifier part.
347 // It does not handle all the non-Latin letters, joiners, and combining marks,
348 // but it does handle every codepoint that can occur in a numeric literal or
350 func isJSIdentPart(r rune
) bool {
354 case '0' <= r
&& r
<= '9':
356 case 'A' <= r
&& r
<= 'Z':
360 case 'a' <= r
&& r
<= 'z':
366 // isJSType returns true if the given MIME type should be considered JavaScript.
368 // It is used to determine whether a script tag with a type attribute is a javascript container.
369 func isJSType(mimeType
string) bool {
371 // https://www.w3.org/TR/html5/scripting-1.html#attr-script-type
372 // https://tools.ietf.org/html/rfc7231#section-3.1.1
373 // https://tools.ietf.org/html/rfc4329#section-3
374 // https://www.ietf.org/rfc/rfc4627.txt
375 mimeType
= strings
.ToLower(mimeType
)
376 // discard parameters
377 if i
:= strings
.Index(mimeType
, ";"); i
>= 0 {
378 mimeType
= mimeType
[:i
]
380 mimeType
= strings
.TrimSpace(mimeType
)
383 "application/ecmascript",
384 "application/javascript",
386 "application/x-ecmascript",
387 "application/x-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",