Source file src/pkg/html/template/escape.go
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.
4
5 package template
6
7 import (
8 "bytes"
9 "fmt"
10 "html"
11 "io"
12 "text/template"
13 "text/template/parse"
14 )
15
16 // escapeTemplates rewrites the named templates, which must be
17 // associated with t, to guarantee that the output of any of the named
18 // templates is properly escaped. Names should include the names of
19 // all templates that might be Executed but need not include helper
20 // templates. If no error is returned, then the named templates have
21 // been modified. Otherwise the named templates have been rendered
22 // unusable.
23 func escapeTemplates(tmpl *Template, names ...string) error {
24 e := newEscaper(tmpl)
25 for _, name := range names {
26 c, _ := e.escapeTree(context{}, name, 0)
27 var err error
28 if c.err != nil {
29 err, c.err.Name = c.err, name
30 } else if c.state != stateText {
31 err = &Error{ErrEndContext, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)}
32 }
33 if err != nil {
34 // Prevent execution of unsafe templates.
35 for _, name := range names {
36 if t := tmpl.set[name]; t != nil {
37 t.text.Tree = nil
38 }
39 }
40 return err
41 }
42 tmpl.escaped = true
43 }
44 e.commit()
45 return nil
46 }
47
48 // funcMap maps command names to functions that render their inputs safe.
49 var funcMap = template.FuncMap{
50 "html_template_attrescaper": attrEscaper,
51 "html_template_commentescaper": commentEscaper,
52 "html_template_cssescaper": cssEscaper,
53 "html_template_cssvaluefilter": cssValueFilter,
54 "html_template_htmlnamefilter": htmlNameFilter,
55 "html_template_htmlescaper": htmlEscaper,
56 "html_template_jsregexpescaper": jsRegexpEscaper,
57 "html_template_jsstrescaper": jsStrEscaper,
58 "html_template_jsvalescaper": jsValEscaper,
59 "html_template_nospaceescaper": htmlNospaceEscaper,
60 "html_template_rcdataescaper": rcdataEscaper,
61 "html_template_urlescaper": urlEscaper,
62 "html_template_urlfilter": urlFilter,
63 "html_template_urlnormalizer": urlNormalizer,
64 }
65
66 // equivEscapers matches contextual escapers to equivalent template builtins.
67 var equivEscapers = map[string]string{
68 "html_template_attrescaper": "html",
69 "html_template_htmlescaper": "html",
70 "html_template_nospaceescaper": "html",
71 "html_template_rcdataescaper": "html",
72 "html_template_urlescaper": "urlquery",
73 "html_template_urlnormalizer": "urlquery",
74 }
75
76 // escaper collects type inferences about templates and changes needed to make
77 // templates injection safe.
78 type escaper struct {
79 tmpl *Template
80 // output[templateName] is the output context for a templateName that
81 // has been mangled to include its input context.
82 output map[string]context
83 // derived[c.mangle(name)] maps to a template derived from the template
84 // named name templateName for the start context c.
85 derived map[string]*template.Template
86 // called[templateName] is a set of called mangled template names.
87 called map[string]bool
88 // xxxNodeEdits are the accumulated edits to apply during commit.
89 // Such edits are not applied immediately in case a template set
90 // executes a given template in different escaping contexts.
91 actionNodeEdits map[*parse.ActionNode][]string
92 templateNodeEdits map[*parse.TemplateNode]string
93 textNodeEdits map[*parse.TextNode][]byte
94 }
95
96 // newEscaper creates a blank escaper for the given set.
97 func newEscaper(t *Template) *escaper {
98 return &escaper{
99 t,
100 map[string]context{},
101 map[string]*template.Template{},
102 map[string]bool{},
103 map[*parse.ActionNode][]string{},
104 map[*parse.TemplateNode]string{},
105 map[*parse.TextNode][]byte{},
106 }
107 }
108
109 // filterFailsafe is an innocuous word that is emitted in place of unsafe values
110 // by sanitizer functions. It is not a keyword in any programming language,
111 // contains no special characters, is not empty, and when it appears in output
112 // it is distinct enough that a developer can find the source of the problem
113 // via a search engine.
114 const filterFailsafe = "ZgotmplZ"
115
116 // escape escapes a template node.
117 func (e *escaper) escape(c context, n parse.Node) context {
118 switch n := n.(type) {
119 case *parse.ActionNode:
120 return e.escapeAction(c, n)
121 case *parse.IfNode:
122 return e.escapeBranch(c, &n.BranchNode, "if")
123 case *parse.ListNode:
124 return e.escapeList(c, n)
125 case *parse.RangeNode:
126 return e.escapeBranch(c, &n.BranchNode, "range")
127 case *parse.TemplateNode:
128 return e.escapeTemplate(c, n)
129 case *parse.TextNode:
130 return e.escapeText(c, n)
131 case *parse.WithNode:
132 return e.escapeBranch(c, &n.BranchNode, "with")
133 }
134 panic("escaping " + n.String() + " is unimplemented")
135 }
136
137 // escapeAction escapes an action template node.
138 func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
139 if len(n.Pipe.Decl) != 0 {
140 // A local variable assignment, not an interpolation.
141 return c
142 }
143 c = nudge(c)
144 s := make([]string, 0, 3)
145 switch c.state {
146 case stateError:
147 return c
148 case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
149 switch c.urlPart {
150 case urlPartNone:
151 s = append(s, "html_template_urlfilter")
152 fallthrough
153 case urlPartPreQuery:
154 switch c.state {
155 case stateCSSDqStr, stateCSSSqStr:
156 s = append(s, "html_template_cssescaper")
157 default:
158 s = append(s, "html_template_urlnormalizer")
159 }
160 case urlPartQueryOrFrag:
161 s = append(s, "html_template_urlescaper")
162 case urlPartUnknown:
163 return context{
164 state: stateError,
165 err: errorf(ErrAmbigContext, n.Line, "%s appears in an ambiguous URL context", n),
166 }
167 default:
168 panic(c.urlPart.String())
169 }
170 case stateJS:
171 s = append(s, "html_template_jsvalescaper")
172 // A slash after a value starts a div operator.
173 c.jsCtx = jsCtxDivOp
174 case stateJSDqStr, stateJSSqStr:
175 s = append(s, "html_template_jsstrescaper")
176 case stateJSRegexp:
177 s = append(s, "html_template_jsregexpescaper")
178 case stateCSS:
179 s = append(s, "html_template_cssvaluefilter")
180 case stateText:
181 s = append(s, "html_template_htmlescaper")
182 case stateRCDATA:
183 s = append(s, "html_template_rcdataescaper")
184 case stateAttr:
185 // Handled below in delim check.
186 case stateAttrName, stateTag:
187 c.state = stateAttrName
188 s = append(s, "html_template_htmlnamefilter")
189 default:
190 if isComment(c.state) {
191 s = append(s, "html_template_commentescaper")
192 } else {
193 panic("unexpected state " + c.state.String())
194 }
195 }
196 switch c.delim {
197 case delimNone:
198 // No extra-escaping needed for raw text content.
199 case delimSpaceOrTagEnd:
200 s = append(s, "html_template_nospaceescaper")
201 default:
202 s = append(s, "html_template_attrescaper")
203 }
204 e.editActionNode(n, s)
205 return c
206 }
207
208 // ensurePipelineContains ensures that the pipeline has commands with
209 // the identifiers in s in order.
210 // If the pipeline already has some of the sanitizers, do not interfere.
211 // For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it
212 // has one matching, "html", and one to insert, "escapeJSVal", to produce
213 // (.X | escapeJSVal | html).
214 func ensurePipelineContains(p *parse.PipeNode, s []string) {
215 if len(s) == 0 {
216 return
217 }
218 n := len(p.Cmds)
219 // Find the identifiers at the end of the command chain.
220 idents := p.Cmds
221 for i := n - 1; i >= 0; i-- {
222 if cmd := p.Cmds[i]; len(cmd.Args) != 0 {
223 if id, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
224 if id.Ident == "noescape" {
225 return
226 }
227 continue
228 }
229 }
230 idents = p.Cmds[i+1:]
231 }
232 dups := 0
233 for _, id := range idents {
234 if escFnsEq(s[dups], (id.Args[0].(*parse.IdentifierNode)).Ident) {
235 dups++
236 if dups == len(s) {
237 return
238 }
239 }
240 }
241 newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups)
242 copy(newCmds, p.Cmds)
243 // Merge existing identifier commands with the sanitizers needed.
244 for _, id := range idents {
245 i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq)
246 if i != -1 {
247 for _, name := range s[:i] {
248 newCmds = appendCmd(newCmds, newIdentCmd(name))
249 }
250 s = s[i+1:]
251 }
252 newCmds = appendCmd(newCmds, id)
253 }
254 // Create any remaining sanitizers.
255 for _, name := range s {
256 newCmds = appendCmd(newCmds, newIdentCmd(name))
257 }
258 p.Cmds = newCmds
259 }
260
261 // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
262 // for all x.
263 var redundantFuncs = map[string]map[string]bool{
264 "html_template_commentescaper": {
265 "html_template_attrescaper": true,
266 "html_template_nospaceescaper": true,
267 "html_template_htmlescaper": true,
268 },
269 "html_template_cssescaper": {
270 "html_template_attrescaper": true,
271 },
272 "html_template_jsregexpescaper": {
273 "html_template_attrescaper": true,
274 },
275 "html_template_jsstrescaper": {
276 "html_template_attrescaper": true,
277 },
278 "html_template_urlescaper": {
279 "html_template_urlnormalizer": true,
280 },
281 }
282
283 // appendCmd appends the given command to the end of the command pipeline
284 // unless it is redundant with the last command.
285 func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode {
286 if n := len(cmds); n != 0 {
287 last, ok := cmds[n-1].Args[0].(*parse.IdentifierNode)
288 next, _ := cmd.Args[0].(*parse.IdentifierNode)
289 if ok && redundantFuncs[last.Ident][next.Ident] {
290 return cmds
291 }
292 }
293 return append(cmds, cmd)
294 }
295
296 // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found.
297 func indexOfStr(s string, strs []string, eq func(a, b string) bool) int {
298 for i, t := range strs {
299 if eq(s, t) {
300 return i
301 }
302 }
303 return -1
304 }
305
306 // escFnsEq returns whether the two escaping functions are equivalent.
307 func escFnsEq(a, b string) bool {
308 if e := equivEscapers[a]; e != "" {
309 a = e
310 }
311 if e := equivEscapers[b]; e != "" {
312 b = e
313 }
314 return a == b
315 }
316
317 // newIdentCmd produces a command containing a single identifier node.
318 func newIdentCmd(identifier string) *parse.CommandNode {
319 return &parse.CommandNode{
320 NodeType: parse.NodeCommand,
321 Args: []parse.Node{parse.NewIdentifier(identifier)},
322 }
323 }
324
325 // nudge returns the context that would result from following empty string
326 // transitions from the input context.
327 // For example, parsing:
328 // `<a href=`
329 // will end in context{stateBeforeValue, attrURL}, but parsing one extra rune:
330 // `<a href=x`
331 // will end in context{stateURL, delimSpaceOrTagEnd, ...}.
332 // There are two transitions that happen when the 'x' is seen:
333 // (1) Transition from a before-value state to a start-of-value state without
334 // consuming any character.
335 // (2) Consume 'x' and transition past the first value character.
336 // In this case, nudging produces the context after (1) happens.
337 func nudge(c context) context {
338 switch c.state {
339 case stateTag:
340 // In `<foo {{.}}`, the action should emit an attribute.
341 c.state = stateAttrName
342 case stateBeforeValue:
343 // In `<foo bar={{.}}`, the action is an undelimited value.
344 c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone
345 case stateAfterName:
346 // In `<foo bar {{.}}`, the action is an attribute name.
347 c.state, c.attr = stateAttrName, attrNone
348 }
349 return c
350 }
351
352 // join joins the two contexts of a branch template node. The result is an
353 // error context if either of the input contexts are error contexts, or if the
354 // the input contexts differ.
355 func join(a, b context, line int, nodeName string) context {
356 if a.state == stateError {
357 return a
358 }
359 if b.state == stateError {
360 return b
361 }
362 if a.eq(b) {
363 return a
364 }
365
366 c := a
367 c.urlPart = b.urlPart
368 if c.eq(b) {
369 // The contexts differ only by urlPart.
370 c.urlPart = urlPartUnknown
371 return c
372 }
373
374 c = a
375 c.jsCtx = b.jsCtx
376 if c.eq(b) {
377 // The contexts differ only by jsCtx.
378 c.jsCtx = jsCtxUnknown
379 return c
380 }
381
382 // Allow a nudged context to join with an unnudged one.
383 // This means that
384 // <p title={{if .C}}{{.}}{{end}}
385 // ends in an unquoted value state even though the else branch
386 // ends in stateBeforeValue.
387 if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) {
388 if e := join(c, d, line, nodeName); e.state != stateError {
389 return e
390 }
391 }
392
393 return context{
394 state: stateError,
395 err: errorf(ErrBranchEnd, line, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b),
396 }
397 }
398
399 // escapeBranch escapes a branch template node: "if", "range" and "with".
400 func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
401 c0 := e.escapeList(c, n.List)
402 if nodeName == "range" && c0.state != stateError {
403 // The "true" branch of a "range" node can execute multiple times.
404 // We check that executing n.List once results in the same context
405 // as executing n.List twice.
406 c1, _ := e.escapeListConditionally(c0, n.List, nil)
407 c0 = join(c0, c1, n.Line, nodeName)
408 if c0.state == stateError {
409 // Make clear that this is a problem on loop re-entry
410 // since developers tend to overlook that branch when
411 // debugging templates.
412 c0.err.Line = n.Line
413 c0.err.Description = "on range loop re-entry: " + c0.err.Description
414 return c0
415 }
416 }
417 c1 := e.escapeList(c, n.ElseList)
418 return join(c0, c1, n.Line, nodeName)
419 }
420
421 // escapeList escapes a list template node.
422 func (e *escaper) escapeList(c context, n *parse.ListNode) context {
423 if n == nil {
424 return c
425 }
426 for _, m := range n.Nodes {
427 c = e.escape(c, m)
428 }
429 return c
430 }
431
432 // escapeListConditionally escapes a list node but only preserves edits and
433 // inferences in e if the inferences and output context satisfy filter.
434 // It returns the best guess at an output context, and the result of the filter
435 // which is the same as whether e was updated.
436 func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
437 e1 := newEscaper(e.tmpl)
438 // Make type inferences available to f.
439 for k, v := range e.output {
440 e1.output[k] = v
441 }
442 c = e1.escapeList(c, n)
443 ok := filter != nil && filter(e1, c)
444 if ok {
445 // Copy inferences and edits from e1 back into e.
446 for k, v := range e1.output {
447 e.output[k] = v
448 }
449 for k, v := range e1.derived {
450 e.derived[k] = v
451 }
452 for k, v := range e1.called {
453 e.called[k] = v
454 }
455 for k, v := range e1.actionNodeEdits {
456 e.editActionNode(k, v)
457 }
458 for k, v := range e1.templateNodeEdits {
459 e.editTemplateNode(k, v)
460 }
461 for k, v := range e1.textNodeEdits {
462 e.editTextNode(k, v)
463 }
464 }
465 return c, ok
466 }
467
468 // escapeTemplate escapes a {{template}} call node.
469 func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context {
470 c, name := e.escapeTree(c, n.Name, n.Line)
471 if name != n.Name {
472 e.editTemplateNode(n, name)
473 }
474 return c
475 }
476
477 // escapeTree escapes the named template starting in the given context as
478 // necessary and returns its output context.
479 func (e *escaper) escapeTree(c context, name string, line int) (context, string) {
480 // Mangle the template name with the input context to produce a reliable
481 // identifier.
482 dname := c.mangle(name)
483 e.called[dname] = true
484 if out, ok := e.output[dname]; ok {
485 // Already escaped.
486 return out, dname
487 }
488 t := e.template(name)
489 if t == nil {
490 // Two cases: The template exists but is empty, or has never been mentioned at
491 // all. Distinguish the cases in the error messages.
492 if e.tmpl.set[name] != nil {
493 return context{
494 state: stateError,
495 err: errorf(ErrNoSuchTemplate, line, "%q is an incomplete or empty template", name),
496 }, dname
497 }
498 return context{
499 state: stateError,
500 err: errorf(ErrNoSuchTemplate, line, "no such template %q", name),
501 }, dname
502 }
503 if dname != name {
504 // Use any template derived during an earlier call to escapeTemplate
505 // with different top level templates, or clone if necessary.
506 dt := e.template(dname)
507 if dt == nil {
508 dt = template.New(dname)
509 dt.Tree = &parse.Tree{Name: dname, Root: t.Root.CopyList()}
510 e.derived[dname] = dt
511 }
512 t = dt
513 }
514 return e.computeOutCtx(c, t), dname
515 }
516
517 // computeOutCtx takes a template and its start context and computes the output
518 // context while storing any inferences in e.
519 func (e *escaper) computeOutCtx(c context, t *template.Template) context {
520 // Propagate context over the body.
521 c1, ok := e.escapeTemplateBody(c, t)
522 if !ok {
523 // Look for a fixed point by assuming c1 as the output context.
524 if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 {
525 c1, ok = c2, true
526 }
527 // Use c1 as the error context if neither assumption worked.
528 }
529 if !ok && c1.state != stateError {
530 return context{
531 state: stateError,
532 // TODO: Find the first node with a line in t.text.Tree.Root
533 err: errorf(ErrOutputContext, 0, "cannot compute output context for template %s", t.Name()),
534 }
535 }
536 return c1
537 }
538
539 // escapeTemplateBody escapes the given template assuming the given output
540 // context, and returns the best guess at the output context and whether the
541 // assumption was correct.
542 func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) {
543 filter := func(e1 *escaper, c1 context) bool {
544 if c1.state == stateError {
545 // Do not update the input escaper, e.
546 return false
547 }
548 if !e1.called[t.Name()] {
549 // If t is not recursively called, then c1 is an
550 // accurate output context.
551 return true
552 }
553 // c1 is accurate if it matches our assumed output context.
554 return c.eq(c1)
555 }
556 // We need to assume an output context so that recursive template calls
557 // take the fast path out of escapeTree instead of infinitely recursing.
558 // Naively assuming that the input context is the same as the output
559 // works >90% of the time.
560 e.output[t.Name()] = c
561 return e.escapeListConditionally(c, t.Tree.Root, filter)
562 }
563
564 // delimEnds maps each delim to a string of characters that terminate it.
565 var delimEnds = [...]string{
566 delimDoubleQuote: `"`,
567 delimSingleQuote: "'",
568 // Determined empirically by running the below in various browsers.
569 // var div = document.createElement("DIV");
570 // for (var i = 0; i < 0x10000; ++i) {
571 // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>";
572 // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0)
573 // document.write("<p>U+" + i.toString(16));
574 // }
575 delimSpaceOrTagEnd: " \t\n\f\r>",
576 }
577
578 var doctypeBytes = []byte("<!DOCTYPE")
579
580 // escapeText escapes a text template node.
581 func (e *escaper) escapeText(c context, n *parse.TextNode) context {
582 s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
583 for i != len(s) {
584 c1, nread := contextAfterText(c, s[i:])
585 i1 := i + nread
586 if c.state == stateText || c.state == stateRCDATA {
587 end := i1
588 if c1.state != c.state {
589 for j := end - 1; j >= i; j-- {
590 if s[j] == '<' {
591 end = j
592 break
593 }
594 }
595 }
596 for j := i; j < end; j++ {
597 if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) {
598 b.Write(s[written:j])
599 b.WriteString("<")
600 written = j + 1
601 }
602 }
603 } else if isComment(c.state) && c.delim == delimNone {
604 switch c.state {
605 case stateJSBlockCmt:
606 // http://es5.github.com/#x7.4:
607 // "Comments behave like white space and are
608 // discarded except that, if a MultiLineComment
609 // contains a line terminator character, then
610 // the entire comment is considered to be a
611 // LineTerminator for purposes of parsing by
612 // the syntactic grammar."
613 if bytes.IndexAny(s[written:i1], "\n\r\u2028\u2029") != -1 {
614 b.WriteByte('\n')
615 } else {
616 b.WriteByte(' ')
617 }
618 case stateCSSBlockCmt:
619 b.WriteByte(' ')
620 }
621 written = i1
622 }
623 if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone {
624 // Preserve the portion between written and the comment start.
625 cs := i1 - 2
626 if c1.state == stateHTMLCmt {
627 // "<!--" instead of "/*" or "//"
628 cs -= 2
629 }
630 b.Write(s[written:cs])
631 written = i1
632 }
633 if i == i1 && c.state == c1.state {
634 panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:]))
635 }
636 c, i = c1, i1
637 }
638
639 if written != 0 && c.state != stateError {
640 if !isComment(c.state) || c.delim != delimNone {
641 b.Write(n.Text[written:])
642 }
643 e.editTextNode(n, b.Bytes())
644 }
645 return c
646 }
647
648 // contextAfterText starts in context c, consumes some tokens from the front of
649 // s, then returns the context after those tokens and the unprocessed suffix.
650 func contextAfterText(c context, s []byte) (context, int) {
651 if c.delim == delimNone {
652 c1, i := tSpecialTagEnd(c, s)
653 if i == 0 {
654 // A special end tag (`</script>`) has been seen and
655 // all content preceding it has been consumed.
656 return c1, 0
657 }
658 // Consider all content up to any end tag.
659 return transitionFunc[c.state](c, s[:i])
660 }
661
662 i := bytes.IndexAny(s, delimEnds[c.delim])
663 if i == -1 {
664 i = len(s)
665 }
666 if c.delim == delimSpaceOrTagEnd {
667 // http://www.w3.org/TR/html5/tokenization.html#attribute-value-unquoted-state
668 // lists the runes below as error characters.
669 // Error out because HTML parsers may differ on whether
670 // "<a id= onclick=f(" ends inside id's or onclick's value,
671 // "<a class=`foo " ends inside a value,
672 // "<a style=font:'Arial'" needs open-quote fixup.
673 // IE treats '`' as a quotation character.
674 if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 {
675 return context{
676 state: stateError,
677 err: errorf(ErrBadHTML, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]),
678 }, len(s)
679 }
680 }
681 if i == len(s) {
682 // Remain inside the attribute.
683 // Decode the value so non-HTML rules can easily handle
684 // <button onclick="alert("Hi!")">
685 // without having to entity decode token boundaries.
686 for u := []byte(html.UnescapeString(string(s))); len(u) != 0; {
687 c1, i1 := transitionFunc[c.state](c, u)
688 c, u = c1, u[i1:]
689 }
690 return c, len(s)
691 }
692 if c.delim != delimSpaceOrTagEnd {
693 // Consume any quote.
694 i++
695 }
696 // On exiting an attribute, we discard all state information
697 // except the state and element.
698 return context{state: stateTag, element: c.element}, i
699 }
700
701 // editActionNode records a change to an action pipeline for later commit.
702 func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) {
703 if _, ok := e.actionNodeEdits[n]; ok {
704 panic(fmt.Sprintf("node %s shared between templates", n))
705 }
706 e.actionNodeEdits[n] = cmds
707 }
708
709 // editTemplateNode records a change to a {{template}} callee for later commit.
710 func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) {
711 if _, ok := e.templateNodeEdits[n]; ok {
712 panic(fmt.Sprintf("node %s shared between templates", n))
713 }
714 e.templateNodeEdits[n] = callee
715 }
716
717 // editTextNode records a change to a text node for later commit.
718 func (e *escaper) editTextNode(n *parse.TextNode, text []byte) {
719 if _, ok := e.textNodeEdits[n]; ok {
720 panic(fmt.Sprintf("node %s shared between templates", n))
721 }
722 e.textNodeEdits[n] = text
723 }
724
725 // commit applies changes to actions and template calls needed to contextually
726 // autoescape content and adds any derived templates to the set.
727 func (e *escaper) commit() {
728 for name := range e.output {
729 e.template(name).Funcs(funcMap)
730 }
731 for _, t := range e.derived {
732 if _, err := e.tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
733 panic("error adding derived template")
734 }
735 }
736 for n, s := range e.actionNodeEdits {
737 ensurePipelineContains(n.Pipe, s)
738 }
739 for n, name := range e.templateNodeEdits {
740 n.Name = name
741 }
742 for n, s := range e.textNodeEdits {
743 n.Text = s
744 }
745 }
746
747 // template returns the named template given a mangled template name.
748 func (e *escaper) template(name string) *template.Template {
749 t := e.tmpl.text.Lookup(name)
750 if t == nil {
751 t = e.derived[name]
752 }
753 return t
754 }
755
756 // Forwarding functions so that clients need only import this package
757 // to reach the general escaping functions of text/template.
758
759 // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
760 func HTMLEscape(w io.Writer, b []byte) {
761 template.HTMLEscape(w, b)
762 }
763
764 // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
765 func HTMLEscapeString(s string) string {
766 return template.HTMLEscapeString(s)
767 }
768
769 // HTMLEscaper returns the escaped HTML equivalent of the textual
770 // representation of its arguments.
771 func HTMLEscaper(args ...interface{}) string {
772 return template.HTMLEscaper(args...)
773 }
774
775 // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
776 func JSEscape(w io.Writer, b []byte) {
777 template.JSEscape(w, b)
778 }
779
780 // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
781 func JSEscapeString(s string) string {
782 return template.JSEscapeString(s)
783 }
784
785 // JSEscaper returns the escaped JavaScript equivalent of the textual
786 // representation of its arguments.
787 func JSEscaper(args ...interface{}) string {
788 return template.JSEscaper(args...)
789 }
790
791 // URLQueryEscaper returns the escaped value of the textual representation of
792 // its arguments in a form suitable for embedding in a URL query.
793 func URLQueryEscaper(args ...interface{}) string {
794 return template.URLQueryEscaper(args...)
795 }