// 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 ()// escapeTemplate rewrites the named template, which must be// associated with t, to guarantee that the output of any of the named// templates is properly escaped. If no error is returned, then the named templates have// been modified. Otherwise the named templates have been rendered// unusable.func escapeTemplate( *Template, parse.Node, string) error { , := .esc.escapeTree(context{}, , , 0)varerrorif .err != nil { , .err.Name = .err, } elseif .state != stateText { = &Error{ErrEndContext, nil, , 0, fmt.Sprintf("ends in a non-text context: %v", )} }if != nil {// Prevent execution of unsafe templates.if := .set[]; != nil { .escapeErr = .text.Tree = nil .Tree = nil }return } .esc.commit()if := .set[]; != nil { .escapeErr = escapeOK .Tree = .text.Tree }returnnil}// evalArgs formats the list of arguments into a string. It is equivalent to// fmt.Sprint(args...), except that it dereferences all pointers.func evalArgs( ...any) string {// Optimization for simple common case of a single string argument.iflen() == 1 {if , := [0].(string); {return } }for , := range { [] = indirectToStringerOrError() }returnfmt.Sprint(...)}// funcMap maps command names to functions that render their inputs safe.var funcMap = template.FuncMap{"_html_template_attrescaper": attrEscaper,"_html_template_commentescaper": commentEscaper,"_html_template_cssescaper": cssEscaper,"_html_template_cssvaluefilter": cssValueFilter,"_html_template_htmlnamefilter": htmlNameFilter,"_html_template_htmlescaper": htmlEscaper,"_html_template_jsregexpescaper": jsRegexpEscaper,"_html_template_jsstrescaper": jsStrEscaper,"_html_template_jstmpllitescaper": jsTmplLitEscaper,"_html_template_jsvalescaper": jsValEscaper,"_html_template_nospaceescaper": htmlNospaceEscaper,"_html_template_rcdataescaper": rcdataEscaper,"_html_template_srcsetescaper": srcsetFilterAndEscaper,"_html_template_urlescaper": urlEscaper,"_html_template_urlfilter": urlFilter,"_html_template_urlnormalizer": urlNormalizer,"_eval_args_": evalArgs,}// escaper collects type inferences about templates and changes needed to make// templates injection safe.type escaper struct {// ns is the nameSpace that this escaper is associated with. ns *nameSpace// output[templateName] is the output context for a templateName that // has been mangled to include its input context. output map[string]context// derived[c.mangle(name)] maps to a template derived from the template // named name templateName for the start context c. derived map[string]*template.Template// called[templateName] is a set of called mangled template names. called map[string]bool// xxxNodeEdits are the accumulated edits to apply during commit. // Such edits are not applied immediately in case a template set // executes a given template in different escaping contexts. actionNodeEdits map[*parse.ActionNode][]string templateNodeEdits map[*parse.TemplateNode]string textNodeEdits map[*parse.TextNode][]byte// rangeContext holds context about the current range loop. rangeContext *rangeContext}// rangeContext holds information about the current range loop.type rangeContext struct { outer *rangeContext// outer loop breaks []context// context at each break action continues []context// context at each continue action}// makeEscaper creates a blank escaper for the given set.func makeEscaper( *nameSpace) escaper {returnescaper{ ,map[string]context{},map[string]*template.Template{},map[string]bool{},map[*parse.ActionNode][]string{},map[*parse.TemplateNode]string{},map[*parse.TextNode][]byte{},nil, }}// filterFailsafe is an innocuous word that is emitted in place of unsafe values// by sanitizer functions. It is not a keyword in any programming language,// contains no special characters, is not empty, and when it appears in output// it is distinct enough that a developer can find the source of the problem// via a search engine.const filterFailsafe = "ZgotmplZ"// escape escapes a template node.func ( *escaper) ( context, parse.Node) context {switch n := .(type) {case *parse.ActionNode:return .escapeAction(, )case *parse.BreakNode: .n = .rangeContext.breaks = append(.rangeContext.breaks, )returncontext{state: stateDead}case *parse.CommentNode:returncase *parse.ContinueNode: .n = .rangeContext.continues = append(.rangeContext.breaks, )returncontext{state: stateDead}case *parse.IfNode:return .escapeBranch(, &.BranchNode, "if")case *parse.ListNode:return .escapeList(, )case *parse.RangeNode:return .escapeBranch(, &.BranchNode, "range")case *parse.TemplateNode:return .escapeTemplate(, )case *parse.TextNode:return .escapeText(, )case *parse.WithNode:return .escapeBranch(, &.BranchNode, "with") }panic("escaping " + .String() + " is unimplemented")}var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")// escapeAction escapes an action template node.func ( *escaper) ( context, *parse.ActionNode) context {iflen(.Pipe.Decl) != 0 {// A local variable assignment, not an interpolation.return } = nudge()// Check for disallowed use of predefined escapers in the pipeline.for , := range .Pipe.Cmds { , := .Args[0].(*parse.IdentifierNode)if ! {// A predefined escaper "esc" will never be found as an identifier in a // Chain or Field node, since: // - "esc.x ..." is invalid, since predefined escapers return strings, and // strings do not have methods, keys or fields. // - "... .esc" is invalid, since predefined escapers are global functions, // not methods or fields of any types. // Therefore, it is safe to ignore these two node types.continue } := .Identif , := predefinedEscapers[]; {if < len(.Pipe.Cmds)-1 || .state == stateAttr && .delim == delimSpaceOrTagEnd && == "html" {returncontext{state: stateError,err: errorf(ErrPredefinedEscaper, , .Line, "predefined escaper %q disallowed in template", ), } } } } := make([]string, 0, 3)switch .state {casestateError:returncasestateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:switch .urlPart {caseurlPartNone: = append(, "_html_template_urlfilter")fallthroughcaseurlPartPreQuery:switch .state {casestateCSSDqStr, stateCSSSqStr: = append(, "_html_template_cssescaper")default: = append(, "_html_template_urlnormalizer") }caseurlPartQueryOrFrag: = append(, "_html_template_urlescaper")caseurlPartUnknown:returncontext{state: stateError,err: errorf(ErrAmbigContext, , .Line, "%s appears in an ambiguous context within a URL", ), }default:panic(.urlPart.String()) }casestateJS: = append(, "_html_template_jsvalescaper")// A slash after a value starts a div operator. .jsCtx = jsCtxDivOpcasestateJSDqStr, stateJSSqStr: = append(, "_html_template_jsstrescaper")casestateJSTmplLit: = append(, "_html_template_jstmpllitescaper")casestateJSRegexp: = append(, "_html_template_jsregexpescaper")casestateCSS: = append(, "_html_template_cssvaluefilter")casestateText: = append(, "_html_template_htmlescaper")casestateRCDATA: = append(, "_html_template_rcdataescaper")casestateAttr:// Handled below in delim check.casestateAttrName, stateTag: .state = stateAttrName = append(, "_html_template_htmlnamefilter")casestateSrcset: = append(, "_html_template_srcsetescaper")default:ifisComment(.state) { = append(, "_html_template_commentescaper") } else {panic("unexpected state " + .state.String()) } }switch .delim {casedelimNone:// No extra-escaping needed for raw text content.casedelimSpaceOrTagEnd: = append(, "_html_template_nospaceescaper")default: = append(, "_html_template_attrescaper") } .editActionNode(, )return}// ensurePipelineContains ensures that the pipeline ends with the commands with// the identifiers in s in order. If the pipeline ends with a predefined escaper// (i.e. "html" or "urlquery"), merge it with the identifiers in s.func ensurePipelineContains( *parse.PipeNode, []string) {iflen() == 0 {// Do not rewrite pipeline if we have no escapers to insert.return }// Precondition: p.Cmds contains at most one predefined escaper and the // escaper will be present at p.Cmds[len(p.Cmds)-1]. This precondition is // always true because of the checks in escapeAction. := len(.Cmds)if > 0 { := .Cmds[-1]if , := .Args[0].(*parse.IdentifierNode); {if := .Ident; predefinedEscapers[] {// Pipeline ends with a predefined escaper.iflen(.Cmds) == 1 && len(.Args) > 1 {// Special case: pipeline is of the form {{ esc arg1 arg2 ... argN }}, // where esc is the predefined escaper, and arg1...argN are its arguments. // Convert this into the equivalent form // {{ _eval_args_ arg1 arg2 ... argN | esc }}, so that esc can be easily // merged with the escapers in s. .Args[0] = parse.NewIdentifier("_eval_args_").SetTree(nil).SetPos(.Args[0].Position()) .Cmds = appendCmd(.Cmds, newIdentCmd(, .Position())) ++ }// If any of the commands in s that we are about to insert is equivalent // to the predefined escaper, use the predefined escaper instead. := falsefor , := range {ifescFnsEq(, ) { [] = .Ident = true } }if {// The predefined escaper will already be inserted along with the // escapers in s, so do not copy it to the rewritten pipeline. -- } } } }// Rewrite the pipeline, creating the escapers in s at the end of the pipeline. := make([]*parse.CommandNode, , +len()) := make(map[string]bool)for := 0; < ; ++ { := .Cmds[] [] = if , := .Args[0].(*parse.IdentifierNode); { [normalizeEscFn(.Ident)] = true } }for , := range {if ![normalizeEscFn()] {// When two templates share an underlying parse tree via the use of // AddParseTree and one template is executed after the other, this check // ensures that escapers that were already inserted into the pipeline on // the first escaping pass do not get inserted again. = appendCmd(, newIdentCmd(, .Position())) } } .Cmds = }// predefinedEscapers contains template predefined escapers that are equivalent// to some contextual escapers. Keep in sync with equivEscapers.var predefinedEscapers = map[string]bool{"html": true,"urlquery": true,}// equivEscapers matches contextual escapers to equivalent predefined// template escapers.var equivEscapers = map[string]string{// The following pairs of HTML escapers provide equivalent security // guarantees, since they all escape '\000', '\'', '"', '&', '<', and '>'."_html_template_attrescaper": "html","_html_template_htmlescaper": "html","_html_template_rcdataescaper": "html",// These two URL escapers produce URLs safe for embedding in a URL query by // percent-encoding all the reserved characters specified in RFC 3986 Section // 2.2"_html_template_urlescaper": "urlquery",// These two functions are not actually equivalent; urlquery is stricter as it // escapes reserved characters (e.g. '#'), while _html_template_urlnormalizer // does not. It is therefore only safe to replace _html_template_urlnormalizer // with urlquery (this happens in ensurePipelineContains), but not the otherI've // way around. We keep this entry around to preserve the behavior of templates // written before Go 1.9, which might depend on this substitution taking place."_html_template_urlnormalizer": "urlquery",}// escFnsEq reports whether the two escaping functions are equivalent.func escFnsEq(, string) bool {returnnormalizeEscFn() == normalizeEscFn()}// normalizeEscFn(a) is equal to normalizeEscFn(b) for any pair of names of// escaper functions a and b that are equivalent.func normalizeEscFn( string) string {if := equivEscapers[]; != "" {return }return}// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)// for all x.var redundantFuncs = map[string]map[string]bool{"_html_template_commentescaper": {"_html_template_attrescaper": true,"_html_template_htmlescaper": true, },"_html_template_cssescaper": {"_html_template_attrescaper": true, },"_html_template_jsregexpescaper": {"_html_template_attrescaper": true, },"_html_template_jsstrescaper": {"_html_template_attrescaper": true, },"_html_template_jstmpllitescaper": {"_html_template_attrescaper": true, },"_html_template_urlescaper": {"_html_template_urlnormalizer": true, },}// appendCmd appends the given command to the end of the command pipeline// unless it is redundant with the last command.func appendCmd( []*parse.CommandNode, *parse.CommandNode) []*parse.CommandNode {if := len(); != 0 { , := [-1].Args[0].(*parse.IdentifierNode) , := .Args[0].(*parse.IdentifierNode)if && && redundantFuncs[.Ident][.Ident] {return } }returnappend(, )}// newIdentCmd produces a command containing a single identifier node.func newIdentCmd( string, parse.Pos) *parse.CommandNode {return &parse.CommandNode{NodeType: parse.NodeCommand,Args: []parse.Node{parse.NewIdentifier().SetTree(nil).SetPos()}, // TODO: SetTree. }}// nudge returns the context that would result from following empty string// transitions from the input context.// For example, parsing://// `<a href=`//// will end in context{stateBeforeValue, attrURL}, but parsing one extra rune://// `<a href=x`//// will end in context{stateURL, delimSpaceOrTagEnd, ...}.// There are two transitions that happen when the 'x' is seen:// (1) Transition from a before-value state to a start-of-value state without//// consuming any character.//// (2) Consume 'x' and transition past the first value character.// In this case, nudging produces the context after (1) happens.func nudge( context) context {switch .state {casestateTag:// In `<foo {{.}}`, the action should emit an attribute. .state = stateAttrNamecasestateBeforeValue:// In `<foo bar={{.}}`, the action is an undelimited value. .state, .delim, .attr = attrStartStates[.attr], delimSpaceOrTagEnd, attrNonecasestateAfterName:// In `<foo bar {{.}}`, the action is an attribute name. .state, .attr = stateAttrName, attrNone }return}// join joins the two contexts of a branch template node. The result is an// error context if either of the input contexts are error contexts, or if the// input contexts differ.func join(, context, parse.Node, string) context {if .state == stateError {return }if .state == stateError {return }if .state == stateDead {return }if .state == stateDead {return }if .eq() {return } := .urlPart = .urlPartif .eq() {// The contexts differ only by urlPart. .urlPart = urlPartUnknownreturn } = .jsCtx = .jsCtxif .eq() {// The contexts differ only by jsCtx. .jsCtx = jsCtxUnknownreturn }// Allow a nudged context to join with an unnudged one. // This means that // <p title={{if .C}}{{.}}{{end}} // ends in an unquoted value state even though the else branch // ends in stateBeforeValue.if , := nudge(), nudge(); !(.eq() && .eq()) {if := (, , , ); .state != stateError {return } }returncontext{state: stateError,err: errorf(ErrBranchEnd, , 0, "{{%s}} branches end in different contexts: %v, %v", , , ), }}// escapeBranch escapes a branch template node: "if", "range" and "with".func ( *escaper) ( context, *parse.BranchNode, string) context {if == "range" { .rangeContext = &rangeContext{outer: .rangeContext} } := .escapeList(, .List)if == "range" {if .state != stateError { = joinRange(, .rangeContext) } .rangeContext = .rangeContext.outerif .state == stateError {return }// The "true" branch of a "range" node can execute multiple times. // We check that executing n.List once results in the same context // as executing n.List twice. .rangeContext = &rangeContext{outer: .rangeContext} , := .escapeListConditionally(, .List, nil) = join(, , , )if .state == stateError { .rangeContext = .rangeContext.outer// Make clear that this is a problem on loop re-entry // since developers tend to overlook that branch when // debugging templates. .err.Line = .Line .err.Description = "on range loop re-entry: " + .err.Descriptionreturn } = joinRange(, .rangeContext) .rangeContext = .rangeContext.outerif .state == stateError {return } } := .escapeList(, .ElseList)returnjoin(, , , )}func joinRange( context, *rangeContext) context {// Merge contexts at break and continue statements into overall body context. // In theory we could treat breaks differently from continues, but for now it is // enough to treat them both as going back to the start of the loop (which may then stop).for , := range .breaks { = join(, , .n, "range")if .state == stateError { .err.Line = .n.(*parse.BreakNode).Line .err.Description = "at range loop break: " + .err.Descriptionreturn } }for , := range .continues { = join(, , .n, "range")if .state == stateError { .err.Line = .n.(*parse.ContinueNode).Line .err.Description = "at range loop continue: " + .err.Descriptionreturn } }return}// escapeList escapes a list template node.func ( *escaper) ( context, *parse.ListNode) context {if == nil {return }for , := range .Nodes { = .escape(, )if .state == stateDead {break } }return}// escapeListConditionally escapes a list node but only preserves edits and// inferences in e if the inferences and output context satisfy filter.// It returns the best guess at an output context, and the result of the filter// which is the same as whether e was updated.func ( *escaper) ( context, *parse.ListNode, func(*escaper, context) bool) (context, bool) { := makeEscaper(.ns) .rangeContext = .rangeContext// Make type inferences available to f.for , := range .output { .output[] = } = .escapeList(, ) := != nil && (&, )if {// Copy inferences and edits from e1 back into e.for , := range .output { .output[] = }for , := range .derived { .derived[] = }for , := range .called { .called[] = }for , := range .actionNodeEdits { .editActionNode(, ) }for , := range .templateNodeEdits { .editTemplateNode(, ) }for , := range .textNodeEdits { .editTextNode(, ) } }return , }// escapeTemplate escapes a {{template}} call node.func ( *escaper) ( context, *parse.TemplateNode) context { , := .escapeTree(, , .Name, .Line)if != .Name { .editTemplateNode(, ) }return}// escapeTree escapes the named template starting in the given context as// necessary and returns its output context.func ( *escaper) ( context, parse.Node, string, int) (context, string) {// Mangle the template name with the input context to produce a reliable // identifier. := .mangle() .called[] = trueif , := .output[]; {// Already escaped.return , } := .template()if == nil {// Two cases: The template exists but is empty, or has never been mentioned at // all. Distinguish the cases in the error messages.if .ns.set[] != nil {returncontext{state: stateError,err: errorf(ErrNoSuchTemplate, , , "%q is an incomplete or empty template", ), }, }returncontext{state: stateError,err: errorf(ErrNoSuchTemplate, , , "no such template %q", ), }, }if != {// Use any template derived during an earlier call to escapeTemplate // with different top level templates, or clone if necessary. := .template()if == nil { = template.New() .Tree = &parse.Tree{Name: , Root: .Root.CopyList()} .derived[] = } = }return .computeOutCtx(, ), }// computeOutCtx takes a template and its start context and computes the output// context while storing any inferences in e.func ( *escaper) ( context, *template.Template) context {// Propagate context over the body. , := .escapeTemplateBody(, )if ! {// Look for a fixed point by assuming c1 as the output context.if , := .escapeTemplateBody(, ); { , = , true }// Use c1 as the error context if neither assumption worked. }if ! && .state != stateError {returncontext{state: stateError,err: errorf(ErrOutputContext, .Tree.Root, 0, "cannot compute output context for template %s", .Name()), } }return}// escapeTemplateBody escapes the given template assuming the given output// context, and returns the best guess at the output context and whether the// assumption was correct.func ( *escaper) ( context, *template.Template) (context, bool) { := func( *escaper, context) bool {if .state == stateError {// Do not update the input escaper, e.returnfalse }if !.called[.Name()] {// If t is not recursively called, then c1 is an // accurate output context.returntrue }// c1 is accurate if it matches our assumed output context.return .eq() }// We need to assume an output context so that recursive template calls // take the fast path out of escapeTree instead of infinitely recurring. // Naively assuming that the input context is the same as the output // works >90% of the time. .output[.Name()] = return .escapeListConditionally(, .Tree.Root, )}// delimEnds maps each delim to a string of characters that terminate it.var delimEnds = [...]string{delimDoubleQuote: `"`,delimSingleQuote: "'",// Determined empirically by running the below in various browsers. // var div = document.createElement("DIV"); // for (var i = 0; i < 0x10000; ++i) { // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>"; // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0) // document.write("<p>U+" + i.toString(16)); // }delimSpaceOrTagEnd: " \t\n\f\r>",}var (// Per WHATWG HTML specification, section 4.12.1.3, there are extremely // complicated rules for how to handle the set of opening tags <!--, // <script, and </script when they appear in JS literals (i.e. strings, // regexs, and comments). The specification suggests a simple solution, // rather than implementing the arcane ABNF, which involves simply escaping // the opening bracket with \x3C. We use the below regex for this, since it // makes doing the case-insensitive find-replace much simpler. specialScriptTagRE = regexp.MustCompile("(?i)<(script|/script|!--)") specialScriptTagReplacement = []byte("\\x3C$1"))func containsSpecialScriptTag( []byte) bool {returnspecialScriptTagRE.Match()}func escapeSpecialScriptTags( []byte) []byte {returnspecialScriptTagRE.ReplaceAll(, specialScriptTagReplacement)}var doctypeBytes = []byte("<!DOCTYPE")// escapeText escapes a text template node.func ( *escaper) ( context, *parse.TextNode) context { , , , := .Text, 0, 0, new(bytes.Buffer)for != len() { , := contextAfterText(, [:]) := + if .state == stateText || .state == stateRCDATA { := if .state != .state {for := - 1; >= ; -- {if [] == '<' { = break } } }for := ; < ; ++ {if [] == '<' && !bytes.HasPrefix(bytes.ToUpper([:]), doctypeBytes) { .Write([:]) .WriteString("<") = + 1 } } } elseifisComment(.state) && .delim == delimNone {switch .state {casestateJSBlockCmt:// https://es5.github.io/#x7.4: // "Comments behave like white space and are // discarded except that, if a MultiLineComment // contains a line terminator character, then // the entire comment is considered to be a // LineTerminator for purposes of parsing by // the syntactic grammar."ifbytes.ContainsAny([:], "\n\r\u2028\u2029") { .WriteByte('\n') } else { .WriteByte(' ') }casestateCSSBlockCmt: .WriteByte(' ') } = }if .state != .state && isComment(.state) && .delim == delimNone {// Preserve the portion between written and the comment start. := - 2if .state == stateHTMLCmt || .state == stateJSHTMLOpenCmt {// "<!--" instead of "/*" or "//" -= 2 } elseif .state == stateJSHTMLCloseCmt {// "-->" instead of "/*" or "//" -= 1 } .Write([:]) = }ifisInScriptLiteral(.state) && containsSpecialScriptTag([:]) { .Write([:]) .Write(escapeSpecialScriptTags([:])) = }if == && .state == .state {panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", , , [:], [:])) } , = , }if != 0 && .state != stateError {if !isComment(.state) || .delim != delimNone { .Write(.Text[:]) } .editTextNode(, .Bytes()) }return}// contextAfterText starts in context c, consumes some tokens from the front of// s, then returns the context after those tokens and the unprocessed suffix.func contextAfterText( context, []byte) (context, int) {if .delim == delimNone { , := tSpecialTagEnd(, )if == 0 {// A special end tag (`</script>`) has been seen and // all content preceding it has been consumed.return , 0 }// Consider all content up to any end tag.returntransitionFunc[.state](, [:]) }// We are at the beginning of an attribute value. := bytes.IndexAny(, delimEnds[.delim])if == -1 { = len() }if .delim == delimSpaceOrTagEnd {// https://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state // lists the runes below as error characters. // Error out because HTML parsers may differ on whether // "<a id= onclick=f(" ends inside id's or onclick's value, // "<a class=`foo " ends inside a value, // "<a style=font:'Arial'" needs open-quote fixup. // IE treats '`' as a quotation character.if := bytes.IndexAny([:], "\"'<=`"); >= 0 {returncontext{state: stateError,err: errorf(ErrBadHTML, nil, 0, "%q in unquoted attr: %q", [:+1], [:]), }, len() } }if == len() {// Remain inside the attribute. // Decode the value so non-HTML rules can easily handle // <button onclick="alert("Hi!")"> // without having to entity decode token boundaries.for := []byte(html.UnescapeString(string())); len() != 0; { , := transitionFunc[.state](, ) , = , [:] }return , len() } := .element// If this is a non-JS "type" attribute inside "script" tag, do not treat the contents as JS.if .state == stateAttr && .element == elementScript && .attr == attrScriptType && !isJSType(string([:])) { = elementNone }if .delim != delimSpaceOrTagEnd {// Consume any quote. ++ }// On exiting an attribute, we discard all state information // except the state and element.returncontext{state: stateTag, element: }, }// editActionNode records a change to an action pipeline for later commit.func ( *escaper) ( *parse.ActionNode, []string) {if , := .actionNodeEdits[]; {panic(fmt.Sprintf("node %s shared between templates", )) } .actionNodeEdits[] = }// editTemplateNode records a change to a {{template}} callee for later commit.func ( *escaper) ( *parse.TemplateNode, string) {if , := .templateNodeEdits[]; {panic(fmt.Sprintf("node %s shared between templates", )) } .templateNodeEdits[] = }// editTextNode records a change to a text node for later commit.func ( *escaper) ( *parse.TextNode, []byte) {if , := .textNodeEdits[]; {panic(fmt.Sprintf("node %s shared between templates", )) } .textNodeEdits[] = }// commit applies changes to actions and template calls needed to contextually// autoescape content and adds any derived templates to the set.func ( *escaper) () {for := range .output { .template().Funcs(funcMap) }// Any template from the name space associated with this escaper can be used // to add derived templates to the underlying text/template name space. := .arbitraryTemplate()for , := range .derived {if , := .text.AddParseTree(.Name(), .Tree); != nil {panic("error adding derived template") } }for , := range .actionNodeEdits {ensurePipelineContains(.Pipe, ) }for , := range .templateNodeEdits { .Name = }for , := range .textNodeEdits { .Text = }// Reset state that is specific to this commit so that the same changes are // not re-applied to the template on subsequent calls to commit. .called = make(map[string]bool) .actionNodeEdits = make(map[*parse.ActionNode][]string) .templateNodeEdits = make(map[*parse.TemplateNode]string) .textNodeEdits = make(map[*parse.TextNode][]byte)}// template returns the named template given a mangled template name.func ( *escaper) ( string) *template.Template {// Any template from the name space associated with this escaper can be used // to look up templates in the underlying text/template name space. := .arbitraryTemplate().text.Lookup()if == nil { = .derived[] }return}// arbitraryTemplate returns an arbitrary template from the name space// associated with e and panics if no templates are found.func ( *escaper) () *Template {for , := range .ns.set {return }panic("no templates in name space")}// Forwarding functions so that clients need only import this package// to reach the general escaping functions of text/template.// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.func ( io.Writer, []byte) {template.HTMLEscape(, )}// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.func ( string) string {returntemplate.HTMLEscapeString()}// HTMLEscaper returns the escaped HTML equivalent of the textual// representation of its arguments.func ( ...any) string {returntemplate.HTMLEscaper(...)}// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.func ( io.Writer, []byte) {template.JSEscape(, )}// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.func ( string) string {returntemplate.JSEscapeString()}// JSEscaper returns the escaped JavaScript equivalent of the textual// representation of its arguments.func ( ...any) string {returntemplate.JSEscaper(...)}// URLQueryEscaper returns the escaped value of the textual representation of// its arguments in a form suitable for embedding in a URL query.func ( ...any) string {returntemplate.URLQueryEscaper(...)}
The pages are generated with Goldsv0.7.0-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.