// 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 nextJSCtx( []byte, jsCtx) jsCtx {// Trim all JS whitespace characters = bytes.TrimRight(, jsWhitespace)iflen() == 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 "-- -".returnjsCtxRegexp }returnjsCtxDivOpcase'.':// Handle "42."if != 1 && '0' <= [-2] && [-2] <= '9' {returnjsCtxDivOp }returnjsCtxRegexp// Suffixes for all punctuators from section 7.7 of the language spec // that only end binary operators not handled above.case',', '<', '>', '=', '*', '%', '&', '|', '^', '?':returnjsCtxRegexp// Suffixes for all punctuators from section 7.7 of the language spec // that are prefix operators not handled above.case'!', '~':returnjsCtxRegexp// Matches all the punctuators from section 7.7 of the language spec // that are open brackets not handled above.case'(', '[':returnjsCtxRegexp// Matches all the punctuators from section 7.7 of the language spec // that precede expression starts.case':', ';', '{':returnjsCtxRegexp// 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'}':returnjsCtxRegexpdefault:// Look for an IdentifierName and see if it is a keyword that // can precede a regular expression. := for > 0 && isJSIdentPart(rune([-1])) { -- }ifregexpPrecederKeywords[string([:])] {returnjsCtxRegexp } }// Otherwise is a punctuator not listed above, or // a string which precedes a div op, or an identifier // which precedes a div op.returnjsCtxDivOp}// 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 indirectToJSONMarshaler( 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 {returnnil } := 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 jsValEscaper( ...any) string {varanyiflen() == 1 { = indirectToJSONMarshaler([0])switch t := .(type) {caseJS:returnstring()caseJSStr:// TODO: normalize quotes.return`"` + string() + `"`casejson.Marshaler:// Do not treat as a Stringer.casefmt.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!--`)returnfmt.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.iflen() == 0 {// In, `x=y/{{.}}*z` a json.Marshaler that produces "" should // not cause the output `x=y/*z`.return" null " } , := utf8.DecodeRune() , := utf8.DecodeLastRune()varstrings.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` } elseif == 0x2029 { = `\u2029` }if != "" { .Write([:]) .WriteString() = + } += }if .Len() != 0 { .Write([:])if { .WriteByte(' ') }return .String() }returnstring()}// 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 jsStrEscaper( ...any) string { , := stringify(...)if == contentTypeJSStr {returnreplace(, jsStrNormReplacementTable) }returnreplace(, jsStrReplacementTable)}func jsTmplLitEscaper( ...any) string { , := stringify(...)returnreplace(, 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 jsRegexpEscaper( ...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 replace( string, []string) string {varstrings.Builder , , := rune(0), 0, 0for := 0; < len(); += {// See comment in htmlEscaper. , = utf8.DecodeRuneInString([:])varstringswitch {caseint() < len(lowUnicodeReplacementTable): = lowUnicodeReplacementTable[]caseint() < 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 isJSIdentPart( rune) bool {switch {case == '$':returntruecase'0' <= && <= '9':returntruecase'A' <= && <= 'Z':returntruecase == '_':returntruecase'a' <= && <= 'z':returntrue }returnfalse}// 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 isJSType( 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":returntruedefault:returnfalse }}
The pages are generated with Goldsv0.7.9-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.