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 // escapeTemplate rewrites the named template, which must be
17 // associated with t, to guarantee that the output of any of the named
18 // templates is properly escaped. If no error is returned, then the named templates have
19 // been modified. Otherwise the named templates have been rendered
21 func escapeTemplate(tmpl
*Template
, node parse
.Node
, name
string) error
{
22 c
, _
:= tmpl
.esc
.escapeTree(context
{}, node
, name
, 0)
25 err
, c
.err
.Name
= c
.err
, name
26 } else if c
.state
!= stateText
{
27 err
= &Error
{ErrEndContext
, nil, name
, 0, fmt
.Sprintf("ends in a non-text context: %v", c
)}
30 // Prevent execution of unsafe templates.
31 if t
:= tmpl
.set
[name
]; t
!= nil {
39 if t
:= tmpl
.set
[name
]; t
!= nil {
40 t
.escapeErr
= escapeOK
46 // evalArgs formats the list of arguments into a string. It is equivalent to
47 // fmt.Sprint(args...), except that it deferences all pointers.
48 func evalArgs(args
...interface{}) string {
49 // Optimization for simple common case of a single string argument.
51 if s
, ok
:= args
[0].(string); ok
{
55 for i
, arg
:= range args
{
56 args
[i
] = indirectToStringerOrError(arg
)
58 return fmt
.Sprint(args
...)
61 // funcMap maps command names to functions that render their inputs safe.
62 var funcMap
= template
.FuncMap
{
63 "_html_template_attrescaper": attrEscaper
,
64 "_html_template_commentescaper": commentEscaper
,
65 "_html_template_cssescaper": cssEscaper
,
66 "_html_template_cssvaluefilter": cssValueFilter
,
67 "_html_template_htmlnamefilter": htmlNameFilter
,
68 "_html_template_htmlescaper": htmlEscaper
,
69 "_html_template_jsregexpescaper": jsRegexpEscaper
,
70 "_html_template_jsstrescaper": jsStrEscaper
,
71 "_html_template_jsvalescaper": jsValEscaper
,
72 "_html_template_nospaceescaper": htmlNospaceEscaper
,
73 "_html_template_rcdataescaper": rcdataEscaper
,
74 "_html_template_urlescaper": urlEscaper
,
75 "_html_template_urlfilter": urlFilter
,
76 "_html_template_urlnormalizer": urlNormalizer
,
77 "_eval_args_": evalArgs
,
80 // escaper collects type inferences about templates and changes needed to make
81 // templates injection safe.
83 // ns is the nameSpace that this escaper is associated with.
85 // output[templateName] is the output context for a templateName that
86 // has been mangled to include its input context.
87 output
map[string]context
88 // derived[c.mangle(name)] maps to a template derived from the template
89 // named name templateName for the start context c.
90 derived
map[string]*template
.Template
91 // called[templateName] is a set of called mangled template names.
92 called
map[string]bool
93 // xxxNodeEdits are the accumulated edits to apply during commit.
94 // Such edits are not applied immediately in case a template set
95 // executes a given template in different escaping contexts.
96 actionNodeEdits
map[*parse
.ActionNode
][]string
97 templateNodeEdits
map[*parse
.TemplateNode
]string
98 textNodeEdits
map[*parse
.TextNode
][]byte
101 // makeEscaper creates a blank escaper for the given set.
102 func makeEscaper(n
*nameSpace
) escaper
{
105 map[string]context
{},
106 map[string]*template
.Template
{},
108 map[*parse
.ActionNode
][]string{},
109 map[*parse
.TemplateNode
]string{},
110 map[*parse
.TextNode
][]byte{},
114 // filterFailsafe is an innocuous word that is emitted in place of unsafe values
115 // by sanitizer functions. It is not a keyword in any programming language,
116 // contains no special characters, is not empty, and when it appears in output
117 // it is distinct enough that a developer can find the source of the problem
118 // via a search engine.
119 const filterFailsafe
= "ZgotmplZ"
121 // escape escapes a template node.
122 func (e
*escaper
) escape(c context
, n parse
.Node
) context
{
123 switch n
:= n
.(type) {
124 case *parse
.ActionNode
:
125 return e
.escapeAction(c
, n
)
127 return e
.escapeBranch(c
, &n
.BranchNode
, "if")
128 case *parse
.ListNode
:
129 return e
.escapeList(c
, n
)
130 case *parse
.RangeNode
:
131 return e
.escapeBranch(c
, &n
.BranchNode
, "range")
132 case *parse
.TemplateNode
:
133 return e
.escapeTemplate(c
, n
)
134 case *parse
.TextNode
:
135 return e
.escapeText(c
, n
)
136 case *parse
.WithNode
:
137 return e
.escapeBranch(c
, &n
.BranchNode
, "with")
139 panic("escaping " + n
.String() + " is unimplemented")
142 // escapeAction escapes an action template node.
143 func (e
*escaper
) escapeAction(c context
, n
*parse
.ActionNode
) context
{
144 if len(n
.Pipe
.Decl
) != 0 {
145 // A local variable assignment, not an interpolation.
149 // Check for disallowed use of predefined escapers in the pipeline.
150 for pos
, idNode
:= range n
.Pipe
.Cmds
{
151 node
, ok
:= idNode
.Args
[0].(*parse
.IdentifierNode
)
153 // A predefined escaper "esc" will never be found as an identifier in a
154 // Chain or Field node, since:
155 // - "esc.x ..." is invalid, since predefined escapers return strings, and
156 // strings do not have methods, keys or fields.
157 // - "... .esc" is invalid, since predefined escapers are global functions,
158 // not methods or fields of any types.
159 // Therefore, it is safe to ignore these two node types.
163 if _
, ok
:= predefinedEscapers
[ident
]; ok
{
164 if pos
< len(n
.Pipe
.Cmds
)-1 ||
165 c
.state
== stateAttr
&& c
.delim
== delimSpaceOrTagEnd
&& ident
== "html" {
168 err
: errorf(ErrPredefinedEscaper
, n
, n
.Line
, "predefined escaper %q disallowed in template", ident
),
173 s
:= make([]string, 0, 3)
177 case stateURL
, stateCSSDqStr
, stateCSSSqStr
, stateCSSDqURL
, stateCSSSqURL
, stateCSSURL
:
180 s
= append(s
, "_html_template_urlfilter")
182 case urlPartPreQuery
:
184 case stateCSSDqStr
, stateCSSSqStr
:
185 s
= append(s
, "_html_template_cssescaper")
187 s
= append(s
, "_html_template_urlnormalizer")
189 case urlPartQueryOrFrag
:
190 s
= append(s
, "_html_template_urlescaper")
194 err
: errorf(ErrAmbigContext
, n
, n
.Line
, "%s appears in an ambiguous context within a URL", n
),
197 panic(c
.urlPart
.String())
200 s
= append(s
, "_html_template_jsvalescaper")
201 // A slash after a value starts a div operator.
203 case stateJSDqStr
, stateJSSqStr
:
204 s
= append(s
, "_html_template_jsstrescaper")
206 s
= append(s
, "_html_template_jsregexpescaper")
208 s
= append(s
, "_html_template_cssvaluefilter")
210 s
= append(s
, "_html_template_htmlescaper")
212 s
= append(s
, "_html_template_rcdataescaper")
214 // Handled below in delim check.
215 case stateAttrName
, stateTag
:
216 c
.state
= stateAttrName
217 s
= append(s
, "_html_template_htmlnamefilter")
219 if isComment(c
.state
) {
220 s
= append(s
, "_html_template_commentescaper")
222 panic("unexpected state " + c
.state
.String())
227 // No extra-escaping needed for raw text content.
228 case delimSpaceOrTagEnd
:
229 s
= append(s
, "_html_template_nospaceescaper")
231 s
= append(s
, "_html_template_attrescaper")
233 e
.editActionNode(n
, s
)
237 // ensurePipelineContains ensures that the pipeline ends with the commands with
238 // the identifiers in s in order. If the pipeline ends with a predefined escaper
239 // (i.e. "html" or "urlquery"), merge it with the identifiers in s.
240 func ensurePipelineContains(p
*parse
.PipeNode
, s
[]string) {
242 // Do not rewrite pipeline if we have no escapers to insert.
245 // Precondition: p.Cmds contains at most one predefined escaper and the
246 // escaper will be present at p.Cmds[len(p.Cmds)-1]. This precondition is
247 // always true because of the checks in escapeAction.
248 pipelineLen
:= len(p
.Cmds
)
250 lastCmd
:= p
.Cmds
[pipelineLen
-1]
251 if idNode
, ok
:= lastCmd
.Args
[0].(*parse
.IdentifierNode
); ok
{
252 if esc
:= idNode
.Ident
; predefinedEscapers
[esc
] {
253 // Pipeline ends with a predefined escaper.
254 if len(p
.Cmds
) == 1 && len(lastCmd
.Args
) > 1 {
255 // Special case: pipeline is of the form {{ esc arg1 arg2 ... argN }},
256 // where esc is the predefined escaper, and arg1...argN are its arguments.
257 // Convert this into the equivalent form
258 // {{ _eval_args_ arg1 arg2 ... argN | esc }}, so that esc can be easily
259 // merged with the escapers in s.
260 lastCmd
.Args
[0] = parse
.NewIdentifier("_eval_args_").SetTree(nil).SetPos(lastCmd
.Args
[0].Position())
261 p
.Cmds
= appendCmd(p
.Cmds
, newIdentCmd(esc
, p
.Position()))
264 // If any of the commands in s that we are about to insert is equivalent
265 // to the predefined escaper, use the predefined escaper instead.
267 for i
, escaper
:= range s
{
268 if escFnsEq(esc
, escaper
) {
274 // The predefined escaper will already be inserted along with the
275 // escapers in s, so do not copy it to the rewritten pipeline.
281 // Rewrite the pipeline, creating the escapers in s at the end of the pipeline.
282 newCmds
:= make([]*parse
.CommandNode
, pipelineLen
, pipelineLen
+len(s
))
283 copy(newCmds
, p
.Cmds
)
284 for _
, name
:= range s
{
285 newCmds
= appendCmd(newCmds
, newIdentCmd(name
, p
.Position()))
290 // predefinedEscapers contains template predefined escapers that are equivalent
291 // to some contextual escapers. Keep in sync with equivEscapers.
292 var predefinedEscapers
= map[string]bool{
297 // equivEscapers matches contextual escapers to equivalent predefined
298 // template escapers.
299 var equivEscapers
= map[string]string{
300 // The following pairs of HTML escapers provide equivalent security
301 // guarantees, since they all escape '\000', '\'', '"', '&', '<', and '>'.
302 "_html_template_attrescaper": "html",
303 "_html_template_htmlescaper": "html",
304 "_html_template_rcdataescaper": "html",
305 // These two URL escapers produce URLs safe for embedding in a URL query by
306 // percent-encoding all the reserved characters specified in RFC 3986 Section
308 "_html_template_urlescaper": "urlquery",
309 // These two functions are not actually equivalent; urlquery is stricter as it
310 // escapes reserved characters (e.g. '#'), while _html_template_urlnormalizer
311 // does not. It is therefore only safe to replace _html_template_urlnormalizer
312 // with urlquery (this happens in ensurePipelineContains), but not the otherI've
313 // way around. We keep this entry around to preserve the behavior of templates
314 // written before Go 1.9, which might depend on this substitution taking place.
315 "_html_template_urlnormalizer": "urlquery",
318 // escFnsEq reports whether the two escaping functions are equivalent.
319 func escFnsEq(a
, b
string) bool {
320 if e
:= equivEscapers
[a
]; e
!= "" {
323 if e
:= equivEscapers
[b
]; e
!= "" {
329 // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
331 var redundantFuncs
= map[string]map[string]bool{
332 "_html_template_commentescaper": {
333 "_html_template_attrescaper": true,
334 "_html_template_nospaceescaper": true,
335 "_html_template_htmlescaper": true,
337 "_html_template_cssescaper": {
338 "_html_template_attrescaper": true,
340 "_html_template_jsregexpescaper": {
341 "_html_template_attrescaper": true,
343 "_html_template_jsstrescaper": {
344 "_html_template_attrescaper": true,
346 "_html_template_urlescaper": {
347 "_html_template_urlnormalizer": true,
351 // appendCmd appends the given command to the end of the command pipeline
352 // unless it is redundant with the last command.
353 func appendCmd(cmds
[]*parse
.CommandNode
, cmd
*parse
.CommandNode
) []*parse
.CommandNode
{
354 if n
:= len(cmds
); n
!= 0 {
355 last
, okLast
:= cmds
[n
-1].Args
[0].(*parse
.IdentifierNode
)
356 next
, okNext
:= cmd
.Args
[0].(*parse
.IdentifierNode
)
357 if okLast
&& okNext
&& redundantFuncs
[last
.Ident
][next
.Ident
] {
361 return append(cmds
, cmd
)
364 // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found.
365 func indexOfStr(s
string, strs
[]string, eq
func(a
, b
string) bool) int {
366 for i
, t
:= range strs
{
374 // newIdentCmd produces a command containing a single identifier node.
375 func newIdentCmd(identifier
string, pos parse
.Pos
) *parse
.CommandNode
{
376 return &parse
.CommandNode
{
377 NodeType
: parse
.NodeCommand
,
378 Args
: []parse
.Node
{parse
.NewIdentifier(identifier
).SetTree(nil).SetPos(pos
)}, // TODO: SetTree.
382 // nudge returns the context that would result from following empty string
383 // transitions from the input context.
384 // For example, parsing:
386 // will end in context{stateBeforeValue, attrURL}, but parsing one extra rune:
388 // will end in context{stateURL, delimSpaceOrTagEnd, ...}.
389 // There are two transitions that happen when the 'x' is seen:
390 // (1) Transition from a before-value state to a start-of-value state without
391 // consuming any character.
392 // (2) Consume 'x' and transition past the first value character.
393 // In this case, nudging produces the context after (1) happens.
394 func nudge(c context
) context
{
397 // In `<foo {{.}}`, the action should emit an attribute.
398 c
.state
= stateAttrName
399 case stateBeforeValue
:
400 // In `<foo bar={{.}}`, the action is an undelimited value.
401 c
.state
, c
.delim
, c
.attr
= attrStartStates
[c
.attr
], delimSpaceOrTagEnd
, attrNone
403 // In `<foo bar {{.}}`, the action is an attribute name.
404 c
.state
, c
.attr
= stateAttrName
, attrNone
409 // join joins the two contexts of a branch template node. The result is an
410 // error context if either of the input contexts are error contexts, or if the
411 // the input contexts differ.
412 func join(a
, b context
, node parse
.Node
, nodeName
string) context
{
413 if a
.state
== stateError
{
416 if b
.state
== stateError
{
424 c
.urlPart
= b
.urlPart
426 // The contexts differ only by urlPart.
427 c
.urlPart
= urlPartUnknown
434 // The contexts differ only by jsCtx.
435 c
.jsCtx
= jsCtxUnknown
439 // Allow a nudged context to join with an unnudged one.
441 // <p title={{if .C}}{{.}}{{end}}
442 // ends in an unquoted value state even though the else branch
443 // ends in stateBeforeValue.
444 if c
, d
:= nudge(a
), nudge(b
); !(c
.eq(a
) && d
.eq(b
)) {
445 if e
:= join(c
, d
, node
, nodeName
); e
.state
!= stateError
{
452 err
: errorf(ErrBranchEnd
, node
, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName
, a
, b
),
456 // escapeBranch escapes a branch template node: "if", "range" and "with".
457 func (e
*escaper
) escapeBranch(c context
, n
*parse
.BranchNode
, nodeName
string) context
{
458 c0
:= e
.escapeList(c
, n
.List
)
459 if nodeName
== "range" && c0
.state
!= stateError
{
460 // The "true" branch of a "range" node can execute multiple times.
461 // We check that executing n.List once results in the same context
462 // as executing n.List twice.
463 c1
, _
:= e
.escapeListConditionally(c0
, n
.List
, nil)
464 c0
= join(c0
, c1
, n
, nodeName
)
465 if c0
.state
== stateError
{
466 // Make clear that this is a problem on loop re-entry
467 // since developers tend to overlook that branch when
468 // debugging templates.
470 c0
.err
.Description
= "on range loop re-entry: " + c0
.err
.Description
474 c1
:= e
.escapeList(c
, n
.ElseList
)
475 return join(c0
, c1
, n
, nodeName
)
478 // escapeList escapes a list template node.
479 func (e
*escaper
) escapeList(c context
, n
*parse
.ListNode
) context
{
483 for _
, m
:= range n
.Nodes
{
489 // escapeListConditionally escapes a list node but only preserves edits and
490 // inferences in e if the inferences and output context satisfy filter.
491 // It returns the best guess at an output context, and the result of the filter
492 // which is the same as whether e was updated.
493 func (e
*escaper
) escapeListConditionally(c context
, n
*parse
.ListNode
, filter
func(*escaper
, context
) bool) (context
, bool) {
494 e1
:= makeEscaper(e
.ns
)
495 // Make type inferences available to f.
496 for k
, v
:= range e
.output
{
499 c
= e1
.escapeList(c
, n
)
500 ok
:= filter
!= nil && filter(&e1
, c
)
502 // Copy inferences and edits from e1 back into e.
503 for k
, v
:= range e1
.output
{
506 for k
, v
:= range e1
.derived
{
509 for k
, v
:= range e1
.called
{
512 for k
, v
:= range e1
.actionNodeEdits
{
513 e
.editActionNode(k
, v
)
515 for k
, v
:= range e1
.templateNodeEdits
{
516 e
.editTemplateNode(k
, v
)
518 for k
, v
:= range e1
.textNodeEdits
{
525 // escapeTemplate escapes a {{template}} call node.
526 func (e
*escaper
) escapeTemplate(c context
, n
*parse
.TemplateNode
) context
{
527 c
, name
:= e
.escapeTree(c
, n
, n
.Name
, n
.Line
)
529 e
.editTemplateNode(n
, name
)
534 // escapeTree escapes the named template starting in the given context as
535 // necessary and returns its output context.
536 func (e
*escaper
) escapeTree(c context
, node parse
.Node
, name
string, line
int) (context
, string) {
537 // Mangle the template name with the input context to produce a reliable
539 dname
:= c
.mangle(name
)
540 e
.called
[dname
] = true
541 if out
, ok
:= e
.output
[dname
]; ok
{
545 t
:= e
.template(name
)
547 // Two cases: The template exists but is empty, or has never been mentioned at
548 // all. Distinguish the cases in the error messages.
549 if e
.ns
.set
[name
] != nil {
552 err
: errorf(ErrNoSuchTemplate
, node
, line
, "%q is an incomplete or empty template", name
),
557 err
: errorf(ErrNoSuchTemplate
, node
, line
, "no such template %q", name
),
561 // Use any template derived during an earlier call to escapeTemplate
562 // with different top level templates, or clone if necessary.
563 dt
:= e
.template(dname
)
565 dt
= template
.New(dname
)
566 dt
.Tree
= &parse
.Tree
{Name
: dname
, Root
: t
.Root
.CopyList()}
567 e
.derived
[dname
] = dt
571 return e
.computeOutCtx(c
, t
), dname
574 // computeOutCtx takes a template and its start context and computes the output
575 // context while storing any inferences in e.
576 func (e
*escaper
) computeOutCtx(c context
, t
*template
.Template
) context
{
577 // Propagate context over the body.
578 c1
, ok
:= e
.escapeTemplateBody(c
, t
)
580 // Look for a fixed point by assuming c1 as the output context.
581 if c2
, ok2
:= e
.escapeTemplateBody(c1
, t
); ok2
{
584 // Use c1 as the error context if neither assumption worked.
586 if !ok
&& c1
.state
!= stateError
{
589 err
: errorf(ErrOutputContext
, t
.Tree
.Root
, 0, "cannot compute output context for template %s", t
.Name()),
595 // escapeTemplateBody escapes the given template assuming the given output
596 // context, and returns the best guess at the output context and whether the
597 // assumption was correct.
598 func (e
*escaper
) escapeTemplateBody(c context
, t
*template
.Template
) (context
, bool) {
599 filter
:= func(e1
*escaper
, c1 context
) bool {
600 if c1
.state
== stateError
{
601 // Do not update the input escaper, e.
604 if !e1
.called
[t
.Name()] {
605 // If t is not recursively called, then c1 is an
606 // accurate output context.
609 // c1 is accurate if it matches our assumed output context.
612 // We need to assume an output context so that recursive template calls
613 // take the fast path out of escapeTree instead of infinitely recursing.
614 // Naively assuming that the input context is the same as the output
615 // works >90% of the time.
616 e
.output
[t
.Name()] = c
617 return e
.escapeListConditionally(c
, t
.Tree
.Root
, filter
)
620 // delimEnds maps each delim to a string of characters that terminate it.
621 var delimEnds
= [...]string{
622 delimDoubleQuote
: `"`,
623 delimSingleQuote
: "'",
624 // Determined empirically by running the below in various browsers.
625 // var div = document.createElement("DIV");
626 // for (var i = 0; i < 0x10000; ++i) {
627 // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>";
628 // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0)
629 // document.write("<p>U+" + i.toString(16));
631 delimSpaceOrTagEnd
: " \t\n\f\r>",
634 var doctypeBytes
= []byte("<!DOCTYPE")
636 // escapeText escapes a text template node.
637 func (e
*escaper
) escapeText(c context
, n
*parse
.TextNode
) context
{
638 s
, written
, i
, b
:= n
.Text
, 0, 0, new(bytes
.Buffer
)
640 c1
, nread
:= contextAfterText(c
, s
[i
:])
642 if c
.state
== stateText || c
.state
== stateRCDATA
{
644 if c1
.state
!= c
.state
{
645 for j
:= end
- 1; j
>= i
; j
-- {
652 for j
:= i
; j
< end
; j
++ {
653 if s
[j
] == '<' && !bytes
.HasPrefix(bytes
.ToUpper(s
[j
:]), doctypeBytes
) {
654 b
.Write(s
[written
:j
])
655 b
.WriteString("<")
659 } else if isComment(c
.state
) && c
.delim
== delimNone
{
661 case stateJSBlockCmt
:
662 // http://es5.github.com/#x7.4:
663 // "Comments behave like white space and are
664 // discarded except that, if a MultiLineComment
665 // contains a line terminator character, then
666 // the entire comment is considered to be a
667 // LineTerminator for purposes of parsing by
668 // the syntactic grammar."
669 if bytes
.ContainsAny(s
[written
:i1
], "\n\r\u2028\u2029") {
674 case stateCSSBlockCmt
:
679 if c
.state
!= c1
.state
&& isComment(c1
.state
) && c1
.delim
== delimNone
{
680 // Preserve the portion between written and the comment start.
682 if c1
.state
== stateHTMLCmt
{
683 // "<!--" instead of "/*" or "//"
686 b
.Write(s
[written
:cs
])
689 if i
== i1
&& c
.state
== c1
.state
{
690 panic(fmt
.Sprintf("infinite loop from %v to %v on %q..%q", c
, c1
, s
[:i
], s
[i
:]))
695 if written
!= 0 && c
.state
!= stateError
{
696 if !isComment(c
.state
) || c
.delim
!= delimNone
{
697 b
.Write(n
.Text
[written
:])
699 e
.editTextNode(n
, b
.Bytes())
704 // contextAfterText starts in context c, consumes some tokens from the front of
705 // s, then returns the context after those tokens and the unprocessed suffix.
706 func contextAfterText(c context
, s
[]byte) (context
, int) {
707 if c
.delim
== delimNone
{
708 c1
, i
:= tSpecialTagEnd(c
, s
)
710 // A special end tag (`</script>`) has been seen and
711 // all content preceding it has been consumed.
714 // Consider all content up to any end tag.
715 return transitionFunc
[c
.state
](c
, s
[:i
])
718 // We are at the beginning of an attribute value.
720 i
:= bytes
.IndexAny(s
, delimEnds
[c
.delim
])
724 if c
.delim
== delimSpaceOrTagEnd
{
725 // http://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state
726 // lists the runes below as error characters.
727 // Error out because HTML parsers may differ on whether
728 // "<a id= onclick=f(" ends inside id's or onclick's value,
729 // "<a class=`foo " ends inside a value,
730 // "<a style=font:'Arial'" needs open-quote fixup.
731 // IE treats '`' as a quotation character.
732 if j
:= bytes
.IndexAny(s
[:i
], "\"'<=`"); j
>= 0 {
735 err
: errorf(ErrBadHTML
, nil, 0, "%q in unquoted attr: %q", s
[j
:j
+1], s
[:i
]),
740 // Remain inside the attribute.
741 // Decode the value so non-HTML rules can easily handle
742 // <button onclick="alert("Hi!")">
743 // without having to entity decode token boundaries.
744 for u
:= []byte(html
.UnescapeString(string(s
))); len(u
) != 0; {
745 c1
, i1
:= transitionFunc
[c
.state
](c
, u
)
753 // If this is a non-JS "type" attribute inside "script" tag, do not treat the contents as JS.
754 if c
.state
== stateAttr
&& c
.element
== elementScript
&& c
.attr
== attrScriptType
&& !isJSType(string(s
[:i
])) {
755 element
= elementNone
758 if c
.delim
!= delimSpaceOrTagEnd
{
759 // Consume any quote.
762 // On exiting an attribute, we discard all state information
763 // except the state and element.
764 return context
{state
: stateTag
, element
: element
}, i
767 // editActionNode records a change to an action pipeline for later commit.
768 func (e
*escaper
) editActionNode(n
*parse
.ActionNode
, cmds
[]string) {
769 if _
, ok
:= e
.actionNodeEdits
[n
]; ok
{
770 panic(fmt
.Sprintf("node %s shared between templates", n
))
772 e
.actionNodeEdits
[n
] = cmds
775 // editTemplateNode records a change to a {{template}} callee for later commit.
776 func (e
*escaper
) editTemplateNode(n
*parse
.TemplateNode
, callee
string) {
777 if _
, ok
:= e
.templateNodeEdits
[n
]; ok
{
778 panic(fmt
.Sprintf("node %s shared between templates", n
))
780 e
.templateNodeEdits
[n
] = callee
783 // editTextNode records a change to a text node for later commit.
784 func (e
*escaper
) editTextNode(n
*parse
.TextNode
, text
[]byte) {
785 if _
, ok
:= e
.textNodeEdits
[n
]; ok
{
786 panic(fmt
.Sprintf("node %s shared between templates", n
))
788 e
.textNodeEdits
[n
] = text
791 // commit applies changes to actions and template calls needed to contextually
792 // autoescape content and adds any derived templates to the set.
793 func (e
*escaper
) commit() {
794 for name
:= range e
.output
{
795 e
.template(name
).Funcs(funcMap
)
797 // Any template from the name space associated with this escaper can be used
798 // to add derived templates to the underlying text/template name space.
799 tmpl
:= e
.arbitraryTemplate()
800 for _
, t
:= range e
.derived
{
801 if _
, err
:= tmpl
.text
.AddParseTree(t
.Name(), t
.Tree
); err
!= nil {
802 panic("error adding derived template")
805 for n
, s
:= range e
.actionNodeEdits
{
806 ensurePipelineContains(n
.Pipe
, s
)
808 for n
, name
:= range e
.templateNodeEdits
{
811 for n
, s
:= range e
.textNodeEdits
{
814 // Reset state that is specific to this commit so that the same changes are
815 // not re-applied to the template on subsequent calls to commit.
816 e
.called
= make(map[string]bool)
817 e
.actionNodeEdits
= make(map[*parse
.ActionNode
][]string)
818 e
.templateNodeEdits
= make(map[*parse
.TemplateNode
]string)
819 e
.textNodeEdits
= make(map[*parse
.TextNode
][]byte)
822 // template returns the named template given a mangled template name.
823 func (e
*escaper
) template(name
string) *template
.Template
{
824 // Any template from the name space associated with this escaper can be used
825 // to look up templates in the underlying text/template name space.
826 t
:= e
.arbitraryTemplate().text
.Lookup(name
)
833 // arbitraryTemplate returns an arbitrary template from the name space
834 // associated with e and panics if no templates are found.
835 func (e
*escaper
) arbitraryTemplate() *Template
{
836 for _
, t
:= range e
.ns
.set
{
839 panic("no templates in name space")
842 // Forwarding functions so that clients need only import this package
843 // to reach the general escaping functions of text/template.
845 // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
846 func HTMLEscape(w io
.Writer
, b
[]byte) {
847 template
.HTMLEscape(w
, b
)
850 // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
851 func HTMLEscapeString(s
string) string {
852 return template
.HTMLEscapeString(s
)
855 // HTMLEscaper returns the escaped HTML equivalent of the textual
856 // representation of its arguments.
857 func HTMLEscaper(args
...interface{}) string {
858 return template
.HTMLEscaper(args
...)
861 // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
862 func JSEscape(w io
.Writer
, b
[]byte) {
863 template
.JSEscape(w
, b
)
866 // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
867 func JSEscapeString(s
string) string {
868 return template
.JSEscapeString(s
)
871 // JSEscaper returns the escaped JavaScript equivalent of the textual
872 // representation of its arguments.
873 func JSEscaper(args
...interface{}) string {
874 return template
.JSEscaper(args
...)
877 // URLQueryEscaper returns the escaped value of the textual representation of
878 // its arguments in a form suitable for embedding in a URL query.
879 func URLQueryEscaper(args
...interface{}) string {
880 return template
.URLQueryEscaper(args
...)