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_srcsetescaper": srcsetFilterAndEscaper
,
75 "_html_template_urlescaper": urlEscaper
,
76 "_html_template_urlfilter": urlFilter
,
77 "_html_template_urlnormalizer": urlNormalizer
,
78 "_eval_args_": evalArgs
,
81 // escaper collects type inferences about templates and changes needed to make
82 // templates injection safe.
84 // ns is the nameSpace that this escaper is associated with.
86 // output[templateName] is the output context for a templateName that
87 // has been mangled to include its input context.
88 output
map[string]context
89 // derived[c.mangle(name)] maps to a template derived from the template
90 // named name templateName for the start context c.
91 derived
map[string]*template
.Template
92 // called[templateName] is a set of called mangled template names.
93 called
map[string]bool
94 // xxxNodeEdits are the accumulated edits to apply during commit.
95 // Such edits are not applied immediately in case a template set
96 // executes a given template in different escaping contexts.
97 actionNodeEdits
map[*parse
.ActionNode
][]string
98 templateNodeEdits
map[*parse
.TemplateNode
]string
99 textNodeEdits
map[*parse
.TextNode
][]byte
102 // makeEscaper creates a blank escaper for the given set.
103 func makeEscaper(n
*nameSpace
) escaper
{
106 map[string]context
{},
107 map[string]*template
.Template
{},
109 map[*parse
.ActionNode
][]string{},
110 map[*parse
.TemplateNode
]string{},
111 map[*parse
.TextNode
][]byte{},
115 // filterFailsafe is an innocuous word that is emitted in place of unsafe values
116 // by sanitizer functions. It is not a keyword in any programming language,
117 // contains no special characters, is not empty, and when it appears in output
118 // it is distinct enough that a developer can find the source of the problem
119 // via a search engine.
120 const filterFailsafe
= "ZgotmplZ"
122 // escape escapes a template node.
123 func (e
*escaper
) escape(c context
, n parse
.Node
) context
{
124 switch n
:= n
.(type) {
125 case *parse
.ActionNode
:
126 return e
.escapeAction(c
, n
)
128 return e
.escapeBranch(c
, &n
.BranchNode
, "if")
129 case *parse
.ListNode
:
130 return e
.escapeList(c
, n
)
131 case *parse
.RangeNode
:
132 return e
.escapeBranch(c
, &n
.BranchNode
, "range")
133 case *parse
.TemplateNode
:
134 return e
.escapeTemplate(c
, n
)
135 case *parse
.TextNode
:
136 return e
.escapeText(c
, n
)
137 case *parse
.WithNode
:
138 return e
.escapeBranch(c
, &n
.BranchNode
, "with")
140 panic("escaping " + n
.String() + " is unimplemented")
143 // escapeAction escapes an action template node.
144 func (e
*escaper
) escapeAction(c context
, n
*parse
.ActionNode
) context
{
145 if len(n
.Pipe
.Decl
) != 0 {
146 // A local variable assignment, not an interpolation.
150 // Check for disallowed use of predefined escapers in the pipeline.
151 for pos
, idNode
:= range n
.Pipe
.Cmds
{
152 node
, ok
:= idNode
.Args
[0].(*parse
.IdentifierNode
)
154 // A predefined escaper "esc" will never be found as an identifier in a
155 // Chain or Field node, since:
156 // - "esc.x ..." is invalid, since predefined escapers return strings, and
157 // strings do not have methods, keys or fields.
158 // - "... .esc" is invalid, since predefined escapers are global functions,
159 // not methods or fields of any types.
160 // Therefore, it is safe to ignore these two node types.
164 if _
, ok
:= predefinedEscapers
[ident
]; ok
{
165 if pos
< len(n
.Pipe
.Cmds
)-1 ||
166 c
.state
== stateAttr
&& c
.delim
== delimSpaceOrTagEnd
&& ident
== "html" {
169 err
: errorf(ErrPredefinedEscaper
, n
, n
.Line
, "predefined escaper %q disallowed in template", ident
),
174 s
:= make([]string, 0, 3)
178 case stateURL
, stateCSSDqStr
, stateCSSSqStr
, stateCSSDqURL
, stateCSSSqURL
, stateCSSURL
:
181 s
= append(s
, "_html_template_urlfilter")
183 case urlPartPreQuery
:
185 case stateCSSDqStr
, stateCSSSqStr
:
186 s
= append(s
, "_html_template_cssescaper")
188 s
= append(s
, "_html_template_urlnormalizer")
190 case urlPartQueryOrFrag
:
191 s
= append(s
, "_html_template_urlescaper")
195 err
: errorf(ErrAmbigContext
, n
, n
.Line
, "%s appears in an ambiguous context within a URL", n
),
198 panic(c
.urlPart
.String())
201 s
= append(s
, "_html_template_jsvalescaper")
202 // A slash after a value starts a div operator.
204 case stateJSDqStr
, stateJSSqStr
:
205 s
= append(s
, "_html_template_jsstrescaper")
207 s
= append(s
, "_html_template_jsregexpescaper")
209 s
= append(s
, "_html_template_cssvaluefilter")
211 s
= append(s
, "_html_template_htmlescaper")
213 s
= append(s
, "_html_template_rcdataescaper")
215 // Handled below in delim check.
216 case stateAttrName
, stateTag
:
217 c
.state
= stateAttrName
218 s
= append(s
, "_html_template_htmlnamefilter")
220 s
= append(s
, "_html_template_srcsetescaper")
222 if isComment(c
.state
) {
223 s
= append(s
, "_html_template_commentescaper")
225 panic("unexpected state " + c
.state
.String())
230 // No extra-escaping needed for raw text content.
231 case delimSpaceOrTagEnd
:
232 s
= append(s
, "_html_template_nospaceescaper")
234 s
= append(s
, "_html_template_attrescaper")
236 e
.editActionNode(n
, s
)
240 // ensurePipelineContains ensures that the pipeline ends with the commands with
241 // the identifiers in s in order. If the pipeline ends with a predefined escaper
242 // (i.e. "html" or "urlquery"), merge it with the identifiers in s.
243 func ensurePipelineContains(p
*parse
.PipeNode
, s
[]string) {
245 // Do not rewrite pipeline if we have no escapers to insert.
248 // Precondition: p.Cmds contains at most one predefined escaper and the
249 // escaper will be present at p.Cmds[len(p.Cmds)-1]. This precondition is
250 // always true because of the checks in escapeAction.
251 pipelineLen
:= len(p
.Cmds
)
253 lastCmd
:= p
.Cmds
[pipelineLen
-1]
254 if idNode
, ok
:= lastCmd
.Args
[0].(*parse
.IdentifierNode
); ok
{
255 if esc
:= idNode
.Ident
; predefinedEscapers
[esc
] {
256 // Pipeline ends with a predefined escaper.
257 if len(p
.Cmds
) == 1 && len(lastCmd
.Args
) > 1 {
258 // Special case: pipeline is of the form {{ esc arg1 arg2 ... argN }},
259 // where esc is the predefined escaper, and arg1...argN are its arguments.
260 // Convert this into the equivalent form
261 // {{ _eval_args_ arg1 arg2 ... argN | esc }}, so that esc can be easily
262 // merged with the escapers in s.
263 lastCmd
.Args
[0] = parse
.NewIdentifier("_eval_args_").SetTree(nil).SetPos(lastCmd
.Args
[0].Position())
264 p
.Cmds
= appendCmd(p
.Cmds
, newIdentCmd(esc
, p
.Position()))
267 // If any of the commands in s that we are about to insert is equivalent
268 // to the predefined escaper, use the predefined escaper instead.
270 for i
, escaper
:= range s
{
271 if escFnsEq(esc
, escaper
) {
277 // The predefined escaper will already be inserted along with the
278 // escapers in s, so do not copy it to the rewritten pipeline.
284 // Rewrite the pipeline, creating the escapers in s at the end of the pipeline.
285 newCmds
:= make([]*parse
.CommandNode
, pipelineLen
, pipelineLen
+len(s
))
286 insertedIdents
:= make(map[string]bool)
287 for i
:= 0; i
< pipelineLen
; i
++ {
290 if idNode
, ok
:= cmd
.Args
[0].(*parse
.IdentifierNode
); ok
{
291 insertedIdents
[normalizeEscFn(idNode
.Ident
)] = true
294 for _
, name
:= range s
{
295 if !insertedIdents
[normalizeEscFn(name
)] {
296 // When two templates share an underlying parse tree via the use of
297 // AddParseTree and one template is executed after the other, this check
298 // ensures that escapers that were already inserted into the pipeline on
299 // the first escaping pass do not get inserted again.
300 newCmds
= appendCmd(newCmds
, newIdentCmd(name
, p
.Position()))
306 // predefinedEscapers contains template predefined escapers that are equivalent
307 // to some contextual escapers. Keep in sync with equivEscapers.
308 var predefinedEscapers
= map[string]bool{
313 // equivEscapers matches contextual escapers to equivalent predefined
314 // template escapers.
315 var equivEscapers
= map[string]string{
316 // The following pairs of HTML escapers provide equivalent security
317 // guarantees, since they all escape '\000', '\'', '"', '&', '<', and '>'.
318 "_html_template_attrescaper": "html",
319 "_html_template_htmlescaper": "html",
320 "_html_template_rcdataescaper": "html",
321 // These two URL escapers produce URLs safe for embedding in a URL query by
322 // percent-encoding all the reserved characters specified in RFC 3986 Section
324 "_html_template_urlescaper": "urlquery",
325 // These two functions are not actually equivalent; urlquery is stricter as it
326 // escapes reserved characters (e.g. '#'), while _html_template_urlnormalizer
327 // does not. It is therefore only safe to replace _html_template_urlnormalizer
328 // with urlquery (this happens in ensurePipelineContains), but not the otherI've
329 // way around. We keep this entry around to preserve the behavior of templates
330 // written before Go 1.9, which might depend on this substitution taking place.
331 "_html_template_urlnormalizer": "urlquery",
334 // escFnsEq reports whether the two escaping functions are equivalent.
335 func escFnsEq(a
, b
string) bool {
336 return normalizeEscFn(a
) == normalizeEscFn(b
)
339 // normalizeEscFn(a) is equal to normalizeEscFn(b) for any pair of names of
340 // escaper functions a and b that are equivalent.
341 func normalizeEscFn(e
string) string {
342 if norm
:= equivEscapers
[e
]; norm
!= "" {
348 // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
350 var redundantFuncs
= map[string]map[string]bool{
351 "_html_template_commentescaper": {
352 "_html_template_attrescaper": true,
353 "_html_template_nospaceescaper": true,
354 "_html_template_htmlescaper": true,
356 "_html_template_cssescaper": {
357 "_html_template_attrescaper": true,
359 "_html_template_jsregexpescaper": {
360 "_html_template_attrescaper": true,
362 "_html_template_jsstrescaper": {
363 "_html_template_attrescaper": true,
365 "_html_template_urlescaper": {
366 "_html_template_urlnormalizer": true,
370 // appendCmd appends the given command to the end of the command pipeline
371 // unless it is redundant with the last command.
372 func appendCmd(cmds
[]*parse
.CommandNode
, cmd
*parse
.CommandNode
) []*parse
.CommandNode
{
373 if n
:= len(cmds
); n
!= 0 {
374 last
, okLast
:= cmds
[n
-1].Args
[0].(*parse
.IdentifierNode
)
375 next
, okNext
:= cmd
.Args
[0].(*parse
.IdentifierNode
)
376 if okLast
&& okNext
&& redundantFuncs
[last
.Ident
][next
.Ident
] {
380 return append(cmds
, cmd
)
383 // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found.
384 func indexOfStr(s
string, strs
[]string, eq
func(a
, b
string) bool) int {
385 for i
, t
:= range strs
{
393 // newIdentCmd produces a command containing a single identifier node.
394 func newIdentCmd(identifier
string, pos parse
.Pos
) *parse
.CommandNode
{
395 return &parse
.CommandNode
{
396 NodeType
: parse
.NodeCommand
,
397 Args
: []parse
.Node
{parse
.NewIdentifier(identifier
).SetTree(nil).SetPos(pos
)}, // TODO: SetTree.
401 // nudge returns the context that would result from following empty string
402 // transitions from the input context.
403 // For example, parsing:
405 // will end in context{stateBeforeValue, attrURL}, but parsing one extra rune:
407 // will end in context{stateURL, delimSpaceOrTagEnd, ...}.
408 // There are two transitions that happen when the 'x' is seen:
409 // (1) Transition from a before-value state to a start-of-value state without
410 // consuming any character.
411 // (2) Consume 'x' and transition past the first value character.
412 // In this case, nudging produces the context after (1) happens.
413 func nudge(c context
) context
{
416 // In `<foo {{.}}`, the action should emit an attribute.
417 c
.state
= stateAttrName
418 case stateBeforeValue
:
419 // In `<foo bar={{.}}`, the action is an undelimited value.
420 c
.state
, c
.delim
, c
.attr
= attrStartStates
[c
.attr
], delimSpaceOrTagEnd
, attrNone
422 // In `<foo bar {{.}}`, the action is an attribute name.
423 c
.state
, c
.attr
= stateAttrName
, attrNone
428 // join joins the two contexts of a branch template node. The result is an
429 // error context if either of the input contexts are error contexts, or if the
430 // the input contexts differ.
431 func join(a
, b context
, node parse
.Node
, nodeName
string) context
{
432 if a
.state
== stateError
{
435 if b
.state
== stateError
{
443 c
.urlPart
= b
.urlPart
445 // The contexts differ only by urlPart.
446 c
.urlPart
= urlPartUnknown
453 // The contexts differ only by jsCtx.
454 c
.jsCtx
= jsCtxUnknown
458 // Allow a nudged context to join with an unnudged one.
460 // <p title={{if .C}}{{.}}{{end}}
461 // ends in an unquoted value state even though the else branch
462 // ends in stateBeforeValue.
463 if c
, d
:= nudge(a
), nudge(b
); !(c
.eq(a
) && d
.eq(b
)) {
464 if e
:= join(c
, d
, node
, nodeName
); e
.state
!= stateError
{
471 err
: errorf(ErrBranchEnd
, node
, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName
, a
, b
),
475 // escapeBranch escapes a branch template node: "if", "range" and "with".
476 func (e
*escaper
) escapeBranch(c context
, n
*parse
.BranchNode
, nodeName
string) context
{
477 c0
:= e
.escapeList(c
, n
.List
)
478 if nodeName
== "range" && c0
.state
!= stateError
{
479 // The "true" branch of a "range" node can execute multiple times.
480 // We check that executing n.List once results in the same context
481 // as executing n.List twice.
482 c1
, _
:= e
.escapeListConditionally(c0
, n
.List
, nil)
483 c0
= join(c0
, c1
, n
, nodeName
)
484 if c0
.state
== stateError
{
485 // Make clear that this is a problem on loop re-entry
486 // since developers tend to overlook that branch when
487 // debugging templates.
489 c0
.err
.Description
= "on range loop re-entry: " + c0
.err
.Description
493 c1
:= e
.escapeList(c
, n
.ElseList
)
494 return join(c0
, c1
, n
, nodeName
)
497 // escapeList escapes a list template node.
498 func (e
*escaper
) escapeList(c context
, n
*parse
.ListNode
) context
{
502 for _
, m
:= range n
.Nodes
{
508 // escapeListConditionally escapes a list node but only preserves edits and
509 // inferences in e if the inferences and output context satisfy filter.
510 // It returns the best guess at an output context, and the result of the filter
511 // which is the same as whether e was updated.
512 func (e
*escaper
) escapeListConditionally(c context
, n
*parse
.ListNode
, filter
func(*escaper
, context
) bool) (context
, bool) {
513 e1
:= makeEscaper(e
.ns
)
514 // Make type inferences available to f.
515 for k
, v
:= range e
.output
{
518 c
= e1
.escapeList(c
, n
)
519 ok
:= filter
!= nil && filter(&e1
, c
)
521 // Copy inferences and edits from e1 back into e.
522 for k
, v
:= range e1
.output
{
525 for k
, v
:= range e1
.derived
{
528 for k
, v
:= range e1
.called
{
531 for k
, v
:= range e1
.actionNodeEdits
{
532 e
.editActionNode(k
, v
)
534 for k
, v
:= range e1
.templateNodeEdits
{
535 e
.editTemplateNode(k
, v
)
537 for k
, v
:= range e1
.textNodeEdits
{
544 // escapeTemplate escapes a {{template}} call node.
545 func (e
*escaper
) escapeTemplate(c context
, n
*parse
.TemplateNode
) context
{
546 c
, name
:= e
.escapeTree(c
, n
, n
.Name
, n
.Line
)
548 e
.editTemplateNode(n
, name
)
553 // escapeTree escapes the named template starting in the given context as
554 // necessary and returns its output context.
555 func (e
*escaper
) escapeTree(c context
, node parse
.Node
, name
string, line
int) (context
, string) {
556 // Mangle the template name with the input context to produce a reliable
558 dname
:= c
.mangle(name
)
559 e
.called
[dname
] = true
560 if out
, ok
:= e
.output
[dname
]; ok
{
564 t
:= e
.template(name
)
566 // Two cases: The template exists but is empty, or has never been mentioned at
567 // all. Distinguish the cases in the error messages.
568 if e
.ns
.set
[name
] != nil {
571 err
: errorf(ErrNoSuchTemplate
, node
, line
, "%q is an incomplete or empty template", name
),
576 err
: errorf(ErrNoSuchTemplate
, node
, line
, "no such template %q", name
),
580 // Use any template derived during an earlier call to escapeTemplate
581 // with different top level templates, or clone if necessary.
582 dt
:= e
.template(dname
)
584 dt
= template
.New(dname
)
585 dt
.Tree
= &parse
.Tree
{Name
: dname
, Root
: t
.Root
.CopyList()}
586 e
.derived
[dname
] = dt
590 return e
.computeOutCtx(c
, t
), dname
593 // computeOutCtx takes a template and its start context and computes the output
594 // context while storing any inferences in e.
595 func (e
*escaper
) computeOutCtx(c context
, t
*template
.Template
) context
{
596 // Propagate context over the body.
597 c1
, ok
:= e
.escapeTemplateBody(c
, t
)
599 // Look for a fixed point by assuming c1 as the output context.
600 if c2
, ok2
:= e
.escapeTemplateBody(c1
, t
); ok2
{
603 // Use c1 as the error context if neither assumption worked.
605 if !ok
&& c1
.state
!= stateError
{
608 err
: errorf(ErrOutputContext
, t
.Tree
.Root
, 0, "cannot compute output context for template %s", t
.Name()),
614 // escapeTemplateBody escapes the given template assuming the given output
615 // context, and returns the best guess at the output context and whether the
616 // assumption was correct.
617 func (e
*escaper
) escapeTemplateBody(c context
, t
*template
.Template
) (context
, bool) {
618 filter
:= func(e1
*escaper
, c1 context
) bool {
619 if c1
.state
== stateError
{
620 // Do not update the input escaper, e.
623 if !e1
.called
[t
.Name()] {
624 // If t is not recursively called, then c1 is an
625 // accurate output context.
628 // c1 is accurate if it matches our assumed output context.
631 // We need to assume an output context so that recursive template calls
632 // take the fast path out of escapeTree instead of infinitely recursing.
633 // Naively assuming that the input context is the same as the output
634 // works >90% of the time.
635 e
.output
[t
.Name()] = c
636 return e
.escapeListConditionally(c
, t
.Tree
.Root
, filter
)
639 // delimEnds maps each delim to a string of characters that terminate it.
640 var delimEnds
= [...]string{
641 delimDoubleQuote
: `"`,
642 delimSingleQuote
: "'",
643 // Determined empirically by running the below in various browsers.
644 // var div = document.createElement("DIV");
645 // for (var i = 0; i < 0x10000; ++i) {
646 // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>";
647 // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0)
648 // document.write("<p>U+" + i.toString(16));
650 delimSpaceOrTagEnd
: " \t\n\f\r>",
653 var doctypeBytes
= []byte("<!DOCTYPE")
655 // escapeText escapes a text template node.
656 func (e
*escaper
) escapeText(c context
, n
*parse
.TextNode
) context
{
657 s
, written
, i
, b
:= n
.Text
, 0, 0, new(bytes
.Buffer
)
659 c1
, nread
:= contextAfterText(c
, s
[i
:])
661 if c
.state
== stateText || c
.state
== stateRCDATA
{
663 if c1
.state
!= c
.state
{
664 for j
:= end
- 1; j
>= i
; j
-- {
671 for j
:= i
; j
< end
; j
++ {
672 if s
[j
] == '<' && !bytes
.HasPrefix(bytes
.ToUpper(s
[j
:]), doctypeBytes
) {
673 b
.Write(s
[written
:j
])
674 b
.WriteString("<")
678 } else if isComment(c
.state
) && c
.delim
== delimNone
{
680 case stateJSBlockCmt
:
681 // http://es5.github.com/#x7.4:
682 // "Comments behave like white space and are
683 // discarded except that, if a MultiLineComment
684 // contains a line terminator character, then
685 // the entire comment is considered to be a
686 // LineTerminator for purposes of parsing by
687 // the syntactic grammar."
688 if bytes
.ContainsAny(s
[written
:i1
], "\n\r\u2028\u2029") {
693 case stateCSSBlockCmt
:
698 if c
.state
!= c1
.state
&& isComment(c1
.state
) && c1
.delim
== delimNone
{
699 // Preserve the portion between written and the comment start.
701 if c1
.state
== stateHTMLCmt
{
702 // "<!--" instead of "/*" or "//"
705 b
.Write(s
[written
:cs
])
708 if i
== i1
&& c
.state
== c1
.state
{
709 panic(fmt
.Sprintf("infinite loop from %v to %v on %q..%q", c
, c1
, s
[:i
], s
[i
:]))
714 if written
!= 0 && c
.state
!= stateError
{
715 if !isComment(c
.state
) || c
.delim
!= delimNone
{
716 b
.Write(n
.Text
[written
:])
718 e
.editTextNode(n
, b
.Bytes())
723 // contextAfterText starts in context c, consumes some tokens from the front of
724 // s, then returns the context after those tokens and the unprocessed suffix.
725 func contextAfterText(c context
, s
[]byte) (context
, int) {
726 if c
.delim
== delimNone
{
727 c1
, i
:= tSpecialTagEnd(c
, s
)
729 // A special end tag (`</script>`) has been seen and
730 // all content preceding it has been consumed.
733 // Consider all content up to any end tag.
734 return transitionFunc
[c
.state
](c
, s
[:i
])
737 // We are at the beginning of an attribute value.
739 i
:= bytes
.IndexAny(s
, delimEnds
[c
.delim
])
743 if c
.delim
== delimSpaceOrTagEnd
{
744 // http://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state
745 // lists the runes below as error characters.
746 // Error out because HTML parsers may differ on whether
747 // "<a id= onclick=f(" ends inside id's or onclick's value,
748 // "<a class=`foo " ends inside a value,
749 // "<a style=font:'Arial'" needs open-quote fixup.
750 // IE treats '`' as a quotation character.
751 if j
:= bytes
.IndexAny(s
[:i
], "\"'<=`"); j
>= 0 {
754 err
: errorf(ErrBadHTML
, nil, 0, "%q in unquoted attr: %q", s
[j
:j
+1], s
[:i
]),
759 // Remain inside the attribute.
760 // Decode the value so non-HTML rules can easily handle
761 // <button onclick="alert("Hi!")">
762 // without having to entity decode token boundaries.
763 for u
:= []byte(html
.UnescapeString(string(s
))); len(u
) != 0; {
764 c1
, i1
:= transitionFunc
[c
.state
](c
, u
)
772 // If this is a non-JS "type" attribute inside "script" tag, do not treat the contents as JS.
773 if c
.state
== stateAttr
&& c
.element
== elementScript
&& c
.attr
== attrScriptType
&& !isJSType(string(s
[:i
])) {
774 element
= elementNone
777 if c
.delim
!= delimSpaceOrTagEnd
{
778 // Consume any quote.
781 // On exiting an attribute, we discard all state information
782 // except the state and element.
783 return context
{state
: stateTag
, element
: element
}, i
786 // editActionNode records a change to an action pipeline for later commit.
787 func (e
*escaper
) editActionNode(n
*parse
.ActionNode
, cmds
[]string) {
788 if _
, ok
:= e
.actionNodeEdits
[n
]; ok
{
789 panic(fmt
.Sprintf("node %s shared between templates", n
))
791 e
.actionNodeEdits
[n
] = cmds
794 // editTemplateNode records a change to a {{template}} callee for later commit.
795 func (e
*escaper
) editTemplateNode(n
*parse
.TemplateNode
, callee
string) {
796 if _
, ok
:= e
.templateNodeEdits
[n
]; ok
{
797 panic(fmt
.Sprintf("node %s shared between templates", n
))
799 e
.templateNodeEdits
[n
] = callee
802 // editTextNode records a change to a text node for later commit.
803 func (e
*escaper
) editTextNode(n
*parse
.TextNode
, text
[]byte) {
804 if _
, ok
:= e
.textNodeEdits
[n
]; ok
{
805 panic(fmt
.Sprintf("node %s shared between templates", n
))
807 e
.textNodeEdits
[n
] = text
810 // commit applies changes to actions and template calls needed to contextually
811 // autoescape content and adds any derived templates to the set.
812 func (e
*escaper
) commit() {
813 for name
:= range e
.output
{
814 e
.template(name
).Funcs(funcMap
)
816 // Any template from the name space associated with this escaper can be used
817 // to add derived templates to the underlying text/template name space.
818 tmpl
:= e
.arbitraryTemplate()
819 for _
, t
:= range e
.derived
{
820 if _
, err
:= tmpl
.text
.AddParseTree(t
.Name(), t
.Tree
); err
!= nil {
821 panic("error adding derived template")
824 for n
, s
:= range e
.actionNodeEdits
{
825 ensurePipelineContains(n
.Pipe
, s
)
827 for n
, name
:= range e
.templateNodeEdits
{
830 for n
, s
:= range e
.textNodeEdits
{
833 // Reset state that is specific to this commit so that the same changes are
834 // not re-applied to the template on subsequent calls to commit.
835 e
.called
= make(map[string]bool)
836 e
.actionNodeEdits
= make(map[*parse
.ActionNode
][]string)
837 e
.templateNodeEdits
= make(map[*parse
.TemplateNode
]string)
838 e
.textNodeEdits
= make(map[*parse
.TextNode
][]byte)
841 // template returns the named template given a mangled template name.
842 func (e
*escaper
) template(name
string) *template
.Template
{
843 // Any template from the name space associated with this escaper can be used
844 // to look up templates in the underlying text/template name space.
845 t
:= e
.arbitraryTemplate().text
.Lookup(name
)
852 // arbitraryTemplate returns an arbitrary template from the name space
853 // associated with e and panics if no templates are found.
854 func (e
*escaper
) arbitraryTemplate() *Template
{
855 for _
, t
:= range e
.ns
.set
{
858 panic("no templates in name space")
861 // Forwarding functions so that clients need only import this package
862 // to reach the general escaping functions of text/template.
864 // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
865 func HTMLEscape(w io
.Writer
, b
[]byte) {
866 template
.HTMLEscape(w
, b
)
869 // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
870 func HTMLEscapeString(s
string) string {
871 return template
.HTMLEscapeString(s
)
874 // HTMLEscaper returns the escaped HTML equivalent of the textual
875 // representation of its arguments.
876 func HTMLEscaper(args
...interface{}) string {
877 return template
.HTMLEscaper(args
...)
880 // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
881 func JSEscape(w io
.Writer
, b
[]byte) {
882 template
.JSEscape(w
, b
)
885 // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
886 func JSEscapeString(s
string) string {
887 return template
.JSEscapeString(s
)
890 // JSEscaper returns the escaped JavaScript equivalent of the textual
891 // representation of its arguments.
892 func JSEscaper(args
...interface{}) string {
893 return template
.JSEscaper(args
...)
896 // URLQueryEscaper returns the escaped value of the textual representation of
897 // its arguments in a form suitable for embedding in a URL query.
898 func URLQueryEscaper(args
...interface{}) string {
899 return template
.URLQueryEscaper(args
...)