Source File
js.go
Belonging Package
html/template
// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package templateimport ()// jsWhitespace contains all of the JS whitespace characters, as defined// by the \s character class.// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes.const jsWhitespace = "\f\n\r\t\v\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff"// nextJSCtx returns the context that determines whether a slash after the// given run of tokens starts a regular expression instead of a division// operator: / or /=.//// This assumes that the token run does not include any string tokens, comment// tokens, regular expression literal tokens, or division operators.//// This fails on some valid but nonsensical JavaScript programs like// "x = ++/foo/i" which is quite different than "x++/foo/i", but is not known to// fail on any known useful programs. It is based on the draft// JavaScript 2.0 lexical grammar and requires one token of lookbehind:// https://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.htmlfunc ( []byte, jsCtx) jsCtx {// Trim all JS whitespace characters= bytes.TrimRight(, jsWhitespace)if len() == 0 {return}// All cases below are in the single-byte UTF-8 group.switch , := [len()-1], len(); {case '+', '-':// ++ and -- are not regexp preceders, but + and - are whether// they are used as infix or prefix operators.:= - 1// Count the number of adjacent dashes or pluses.for > 0 && [-1] == {--}if (-)&1 == 1 {// Reached for trailing minus signs since "---" is the// same as "-- -".return jsCtxRegexp}return jsCtxDivOpcase '.':// Handle "42."if != 1 && '0' <= [-2] && [-2] <= '9' {return jsCtxDivOp}return jsCtxRegexp// Suffixes for all punctuators from section 7.7 of the language spec// that only end binary operators not handled above.case ',', '<', '>', '=', '*', '%', '&', '|', '^', '?':return jsCtxRegexp// Suffixes for all punctuators from section 7.7 of the language spec// that are prefix operators not handled above.case '!', '~':return jsCtxRegexp// Matches all the punctuators from section 7.7 of the language spec// that are open brackets not handled above.case '(', '[':return jsCtxRegexp// Matches all the punctuators from section 7.7 of the language spec// that precede expression starts.case ':', ';', '{':return jsCtxRegexp// CAVEAT: the close punctuators ('}', ']', ')') precede div ops and// are handled in the default except for '}' which can precede a// division op as in// ({ valueOf: function () { return 42 } } / 2// which is valid, but, in practice, developers don't divide object// literals, so our heuristic works well for code like// function () { ... } /foo/.test(x) && sideEffect();// The ')' punctuator can precede a regular expression as in// if (b) /foo/.test(x) && ...// but this is much less likely than// (a + b) / ccase '}':return jsCtxRegexpdefault:// Look for an IdentifierName and see if it is a keyword that// can precede a regular expression.:=for > 0 && isJSIdentPart(rune([-1])) {--}if regexpPrecederKeywords[string([:])] {return jsCtxRegexp}}// Otherwise is a punctuator not listed above, or// a string which precedes a div op, or an identifier// which precedes a div op.return jsCtxDivOp}// regexpPrecederKeywords is a set of reserved JS keywords that can precede a// regular expression in JS source.var regexpPrecederKeywords = map[string]bool{"break": true,"case": true,"continue": true,"delete": true,"do": true,"else": true,"finally": true,"in": true,"instanceof": true,"return": true,"throw": true,"try": true,"typeof": true,"void": true,}var jsonMarshalType = reflect.TypeFor[json.Marshaler]()// indirectToJSONMarshaler returns the value, after dereferencing as many times// as necessary to reach the base type (or nil) or an implementation of json.Marshal.func ( any) any {// text/template now supports passing untyped nil as a func call// argument, so we must support it. Otherwise we'd panic below, as one// cannot call the Type or Interface methods on an invalid// reflect.Value. See golang.org/issue/18716.if == nil {return nil}:= reflect.ValueOf()for !.Type().Implements(jsonMarshalType) && .Kind() == reflect.Pointer && !.IsNil() {= .Elem()}return .Interface()}var scriptTagRe = regexp.MustCompile("(?i)<(/?)script")// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has// neither side-effects nor free variables outside (NaN, Infinity).func ( ...any) string {var anyif len() == 1 {= indirectToJSONMarshaler([0])switch t := .(type) {case JS:return string()case JSStr:// TODO: normalize quotes.return `"` + string() + `"`case json.Marshaler:// Do not treat as a Stringer.case fmt.Stringer:= .String()}} else {for , := range {[] = indirectToJSONMarshaler()}= fmt.Sprint(...)}// TODO: detect cycles before calling Marshal which loops infinitely on// cyclic data. This may be an unacceptable DoS risk., := json.Marshal()if != nil {// While the standard JSON marshaler does not include user controlled// information in the error message, if a type has a MarshalJSON method,// the content of the error message is not guaranteed. Since we insert// the error into the template, as part of a comment, we attempt to// prevent the error from either terminating the comment, or the script// block itself.//// In particular we:// * replace "*/" comment end tokens with "* /", which does not// terminate the comment// * replace "<script" and "</script" with "\x3Cscript" and "\x3C/script"// (case insensitively), and "<!--" with "\x3C!--", which prevents// confusing script block termination semantics//// We also put a space before the comment so that if it is flush against// a division operator it is not turned into a line comment:// x/{{y}}// turning into// x//* error marshaling y:// second line of error message */null:= .Error()= string(scriptTagRe.ReplaceAll([]byte(), []byte(`\x3C${1}script`)))= strings.ReplaceAll(, "*/", "* /")= strings.ReplaceAll(, "<!--", `\x3C!--`)return fmt.Sprintf(" /* %s */null ", )}// TODO: maybe post-process output to prevent it from containing// "<!--", "-->", "<![CDATA[", "]]>", or "</script"// in case custom marshalers produce output containing those.// Note: Do not use \x escaping to save bytes because it is not JSON compatible and this escaper// supports ld+json content-type.if len() == 0 {// In, `x=y/{{.}}*z` a json.Marshaler that produces "" should// not cause the output `x=y/*z`.return " null "}, := utf8.DecodeRune(), := utf8.DecodeLastRune()var strings.Builder// Prevent IdentifierNames and NumericLiterals from running into// keywords: in, instanceof, typeof, void:= isJSIdentPart() || isJSIdentPart()if {.WriteByte(' ')}:= 0// Make sure that json.Marshal escapes codepoints U+2028 & U+2029// so it falls within the subset of JSON which is valid JS.for := 0; < len(); {, := utf8.DecodeRune([:]):= ""if == 0x2028 {= `\u2028`} else if == 0x2029 {= `\u2029`}if != "" {.Write([:]).WriteString()= +}+=}if .Len() != 0 {.Write([:])if {.WriteByte(' ')}return .String()}return string()}// jsStrEscaper produces a string that can be included between quotes in// JavaScript source, in JavaScript embedded in an HTML5 <script> element,// or in an HTML5 event handler attribute such as onclick.func ( ...any) string {, := stringify(...)if == contentTypeJSStr {return replace(, jsStrNormReplacementTable)}return replace(, jsStrReplacementTable)}func ( ...any) string {, := stringify(...)return replace(, jsBqStrReplacementTable)}// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression// specials so the result is treated literally when included in a regular// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by// the literal text of {{.X}} followed by the string "bar".func ( ...any) string {, := stringify(...)= replace(, jsRegexpReplacementTable)if == "" {// /{{.X}}/ should not produce a line comment when .X == "".return "(?:)"}return}// replace replaces each rune r of s with replacementTable[r], provided that// r < len(replacementTable). If replacementTable[r] is the empty string then// no replacement is made.// It also replaces runes U+2028 and U+2029 with the raw strings `\u2028` and// `\u2029`.func ( string, []string) string {var strings.Builder, , := rune(0), 0, 0for := 0; < len(); += {// See comment in htmlEscaper., = utf8.DecodeRuneInString([:])var stringswitch {case int() < len(lowUnicodeReplacementTable):= lowUnicodeReplacementTable[]case int() < len() && [] != "":= []case == '\u2028':= `\u2028`case == '\u2029':= `\u2029`default:continue}if == 0 {.Grow(len())}.WriteString([:]).WriteString()= +}if == 0 {return}.WriteString([:])return .String()}var lowUnicodeReplacementTable = []string{0: `\u0000`, 1: `\u0001`, 2: `\u0002`, 3: `\u0003`, 4: `\u0004`, 5: `\u0005`, 6: `\u0006`,'\a': `\u0007`,'\b': `\u0008`,'\t': `\t`,'\n': `\n`,'\v': `\u000b`, // "\v" == "v" on IE 6.'\f': `\f`,'\r': `\r`,0xe: `\u000e`, 0xf: `\u000f`, 0x10: `\u0010`, 0x11: `\u0011`, 0x12: `\u0012`, 0x13: `\u0013`,0x14: `\u0014`, 0x15: `\u0015`, 0x16: `\u0016`, 0x17: `\u0017`, 0x18: `\u0018`, 0x19: `\u0019`,0x1a: `\u001a`, 0x1b: `\u001b`, 0x1c: `\u001c`, 0x1d: `\u001d`, 0x1e: `\u001e`, 0x1f: `\u001f`,}var jsStrReplacementTable = []string{0: `\u0000`,'\t': `\t`,'\n': `\n`,'\v': `\u000b`, // "\v" == "v" on IE 6.'\f': `\f`,'\r': `\r`,// Encode HTML specials as hex so the output can be embedded// in HTML attributes without further encoding.'"': `\u0022`,'`': `\u0060`,'&': `\u0026`,'\'': `\u0027`,'+': `\u002b`,'/': `\/`,'<': `\u003c`,'>': `\u003e`,'\\': `\\`,}// jsBqStrReplacementTable is like jsStrReplacementTable except it also contains// the special characters for JS template literals: $, {, and }.var jsBqStrReplacementTable = []string{0: `\u0000`,'\t': `\t`,'\n': `\n`,'\v': `\u000b`, // "\v" == "v" on IE 6.'\f': `\f`,'\r': `\r`,// Encode HTML specials as hex so the output can be embedded// in HTML attributes without further encoding.'"': `\u0022`,'`': `\u0060`,'&': `\u0026`,'\'': `\u0027`,'+': `\u002b`,'/': `\/`,'<': `\u003c`,'>': `\u003e`,'\\': `\\`,'$': `\u0024`,'{': `\u007b`,'}': `\u007d`,}// jsStrNormReplacementTable is like jsStrReplacementTable but does not// overencode existing escapes since this table has no entry for `\`.var jsStrNormReplacementTable = []string{0: `\u0000`,'\t': `\t`,'\n': `\n`,'\v': `\u000b`, // "\v" == "v" on IE 6.'\f': `\f`,'\r': `\r`,// Encode HTML specials as hex so the output can be embedded// in HTML attributes without further encoding.'"': `\u0022`,'&': `\u0026`,'\'': `\u0027`,'`': `\u0060`,'+': `\u002b`,'/': `\/`,'<': `\u003c`,'>': `\u003e`,}var jsRegexpReplacementTable = []string{0: `\u0000`,'\t': `\t`,'\n': `\n`,'\v': `\u000b`, // "\v" == "v" on IE 6.'\f': `\f`,'\r': `\r`,// Encode HTML specials as hex so the output can be embedded// in HTML attributes without further encoding.'"': `\u0022`,'$': `\$`,'&': `\u0026`,'\'': `\u0027`,'(': `\(`,')': `\)`,'*': `\*`,'+': `\u002b`,'-': `\-`,'.': `\.`,'/': `\/`,'<': `\u003c`,'>': `\u003e`,'?': `\?`,'[': `\[`,'\\': `\\`,']': `\]`,'^': `\^`,'{': `\{`,'|': `\|`,'}': `\}`,}// isJSIdentPart reports whether the given rune is a JS identifier part.// It does not handle all the non-Latin letters, joiners, and combining marks,// but it does handle every codepoint that can occur in a numeric literal or// a keyword.func ( rune) bool {switch {case == '$':return truecase '0' <= && <= '9':return truecase 'A' <= && <= 'Z':return truecase == '_':return truecase 'a' <= && <= 'z':return true}return false}// isJSType reports whether the given MIME type should be considered JavaScript.//// It is used to determine whether a script tag with a type attribute is a javascript container.func ( string) bool {// per// https://www.w3.org/TR/html5/scripting-1.html#attr-script-type// https://tools.ietf.org/html/rfc7231#section-3.1.1// https://tools.ietf.org/html/rfc4329#section-3// https://www.ietf.org/rfc/rfc4627.txt// discard parameters, _, _ = strings.Cut(, ";")= strings.ToLower()= strings.TrimSpace()switch {case"application/ecmascript","application/javascript","application/json","application/ld+json","application/x-ecmascript","application/x-javascript","module","text/ecmascript","text/javascript","text/javascript1.0","text/javascript1.1","text/javascript1.2","text/javascript1.3","text/javascript1.4","text/javascript1.5","text/jscript","text/livescript","text/x-ecmascript","text/x-javascript":return truedefault:return false}}
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64)