OSDN Git Service

libgo: Update to weekly 2011-11-09.
[pf3gnuchains/gcc-fork.git] / libgo / go / 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         "text/template"
12         "text/template/parse"
13 )
14
15 // escape rewrites each action in the template to guarantee that the output is
16 // properly escaped.
17 func escape(t *template.Template) error {
18         var s template.Set
19         s.Add(t)
20         return escapeSet(&s, t.Name())
21         // TODO: if s contains cloned dependencies due to self-recursion
22         // cross-context, error out.
23 }
24
25 // escapeSet rewrites the template set to guarantee that the output of any of
26 // the named templates is properly escaped.
27 // Names should include the names of all templates that might be Executed but
28 // need not include helper templates.
29 // If no error is returned, then the named templates have been modified. 
30 // Otherwise the named templates have been rendered unusable.
31 func escapeSet(s *template.Set, names ...string) error {
32         e := newEscaper(s)
33         for _, name := range names {
34                 c, _ := e.escapeTree(context{}, name, 0)
35                 var err error
36                 if c.err != nil {
37                         err, c.err.Name = c.err, name
38                 } else if c.state != stateText {
39                         err = &Error{ErrEndContext, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)}
40                 }
41                 if err != nil {
42                         // Prevent execution of unsafe templates.
43                         for _, name := range names {
44                                 if t := s.Template(name); t != nil {
45                                         t.Tree = nil
46                                 }
47                         }
48                         return err
49                 }
50         }
51         e.commit()
52         return nil
53 }
54
55 // funcMap maps command names to functions that render their inputs safe.
56 var funcMap = template.FuncMap{
57         "exp_template_html_attrescaper":     attrEscaper,
58         "exp_template_html_commentescaper":  commentEscaper,
59         "exp_template_html_cssescaper":      cssEscaper,
60         "exp_template_html_cssvaluefilter":  cssValueFilter,
61         "exp_template_html_htmlnamefilter":  htmlNameFilter,
62         "exp_template_html_htmlescaper":     htmlEscaper,
63         "exp_template_html_jsregexpescaper": jsRegexpEscaper,
64         "exp_template_html_jsstrescaper":    jsStrEscaper,
65         "exp_template_html_jsvalescaper":    jsValEscaper,
66         "exp_template_html_nospaceescaper":  htmlNospaceEscaper,
67         "exp_template_html_rcdataescaper":   rcdataEscaper,
68         "exp_template_html_urlescaper":      urlEscaper,
69         "exp_template_html_urlfilter":       urlFilter,
70         "exp_template_html_urlnormalizer":   urlNormalizer,
71 }
72
73 // equivEscapers matches contextual escapers to equivalent template builtins.
74 var equivEscapers = map[string]string{
75         "exp_template_html_attrescaper":    "html",
76         "exp_template_html_htmlescaper":    "html",
77         "exp_template_html_nospaceescaper": "html",
78         "exp_template_html_rcdataescaper":  "html",
79         "exp_template_html_urlescaper":     "urlquery",
80         "exp_template_html_urlnormalizer":  "urlquery",
81 }
82
83 // escaper collects type inferences about templates and changes needed to make
84 // templates injection safe.
85 type escaper struct {
86         // set is the template set being escaped.
87         set *template.Set
88         // output[templateName] is the output context for a templateName that
89         // has been mangled to include its input context.
90         output map[string]context
91         // derived[c.mangle(name)] maps to a template derived from the template
92         // named name templateName for the start context c.
93         derived map[string]*template.Template
94         // called[templateName] is a set of called mangled template names.
95         called map[string]bool
96         // xxxNodeEdits are the accumulated edits to apply during commit.
97         // Such edits are not applied immediately in case a template set
98         // executes a given template in different escaping contexts.
99         actionNodeEdits   map[*parse.ActionNode][]string
100         templateNodeEdits map[*parse.TemplateNode]string
101         textNodeEdits     map[*parse.TextNode][]byte
102 }
103
104 // newEscaper creates a blank escaper for the given set.
105 func newEscaper(s *template.Set) *escaper {
106         return &escaper{
107                 s,
108                 map[string]context{},
109                 map[string]*template.Template{},
110                 map[string]bool{},
111                 map[*parse.ActionNode][]string{},
112                 map[*parse.TemplateNode]string{},
113                 map[*parse.TextNode][]byte{},
114         }
115 }
116
117 // filterFailsafe is an innocuous word that is emitted in place of unsafe values
118 // by sanitizer functions. It is not a keyword in any programming language,
119 // contains no special characters, is not empty, and when it appears in output
120 // it is distinct enough that a developer can find the source of the problem
121 // via a search engine.
122 const filterFailsafe = "ZgotmplZ"
123
124 // escape escapes a template node.
125 func (e *escaper) escape(c context, n parse.Node) context {
126         switch n := n.(type) {
127         case *parse.ActionNode:
128                 return e.escapeAction(c, n)
129         case *parse.IfNode:
130                 return e.escapeBranch(c, &n.BranchNode, "if")
131         case *parse.ListNode:
132                 return e.escapeList(c, n)
133         case *parse.RangeNode:
134                 return e.escapeBranch(c, &n.BranchNode, "range")
135         case *parse.TemplateNode:
136                 return e.escapeTemplate(c, n)
137         case *parse.TextNode:
138                 return e.escapeText(c, n)
139         case *parse.WithNode:
140                 return e.escapeBranch(c, &n.BranchNode, "with")
141         }
142         panic("escaping " + n.String() + " is unimplemented")
143 }
144
145 // escapeAction escapes an action template node.
146 func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
147         if len(n.Pipe.Decl) != 0 {
148                 // A local variable assignment, not an interpolation.
149                 return c
150         }
151         c = nudge(c)
152         s := make([]string, 0, 3)
153         switch c.state {
154         case stateError:
155                 return c
156         case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
157                 switch c.urlPart {
158                 case urlPartNone:
159                         s = append(s, "exp_template_html_urlfilter")
160                         fallthrough
161                 case urlPartPreQuery:
162                         switch c.state {
163                         case stateCSSDqStr, stateCSSSqStr:
164                                 s = append(s, "exp_template_html_cssescaper")
165                         default:
166                                 s = append(s, "exp_template_html_urlnormalizer")
167                         }
168                 case urlPartQueryOrFrag:
169                         s = append(s, "exp_template_html_urlescaper")
170                 case urlPartUnknown:
171                         return context{
172                                 state: stateError,
173                                 err:   errorf(ErrAmbigContext, n.Line, "%s appears in an ambiguous URL context", n),
174                         }
175                 default:
176                         panic(c.urlPart.String())
177                 }
178         case stateJS:
179                 s = append(s, "exp_template_html_jsvalescaper")
180                 // A slash after a value starts a div operator.
181                 c.jsCtx = jsCtxDivOp
182         case stateJSDqStr, stateJSSqStr:
183                 s = append(s, "exp_template_html_jsstrescaper")
184         case stateJSRegexp:
185                 s = append(s, "exp_template_html_jsregexpescaper")
186         case stateCSS:
187                 s = append(s, "exp_template_html_cssvaluefilter")
188         case stateText:
189                 s = append(s, "exp_template_html_htmlescaper")
190         case stateRCDATA:
191                 s = append(s, "exp_template_html_rcdataescaper")
192         case stateAttr:
193                 // Handled below in delim check.
194         case stateAttrName, stateTag:
195                 c.state = stateAttrName
196                 s = append(s, "exp_template_html_htmlnamefilter")
197         default:
198                 if isComment(c.state) {
199                         s = append(s, "exp_template_html_commentescaper")
200                 } else {
201                         panic("unexpected state " + c.state.String())
202                 }
203         }
204         switch c.delim {
205         case delimNone:
206                 // No extra-escaping needed for raw text content.
207         case delimSpaceOrTagEnd:
208                 s = append(s, "exp_template_html_nospaceescaper")
209         default:
210                 s = append(s, "exp_template_html_attrescaper")
211         }
212         e.editActionNode(n, s)
213         return c
214 }
215
216 // ensurePipelineContains ensures that the pipeline has commands with
217 // the identifiers in s in order.
218 // If the pipeline already has some of the sanitizers, do not interfere.
219 // For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it
220 // has one matching, "html", and one to insert, "escapeJSVal", to produce
221 // (.X | escapeJSVal | html).
222 func ensurePipelineContains(p *parse.PipeNode, s []string) {
223         if len(s) == 0 {
224                 return
225         }
226         n := len(p.Cmds)
227         // Find the identifiers at the end of the command chain.
228         idents := p.Cmds
229         for i := n - 1; i >= 0; i-- {
230                 if cmd := p.Cmds[i]; len(cmd.Args) != 0 {
231                         if id, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
232                                 if id.Ident == "noescape" {
233                                         return
234                                 }
235                                 continue
236                         }
237                 }
238                 idents = p.Cmds[i+1:]
239         }
240         dups := 0
241         for _, id := range idents {
242                 if escFnsEq(s[dups], (id.Args[0].(*parse.IdentifierNode)).Ident) {
243                         dups++
244                         if dups == len(s) {
245                                 return
246                         }
247                 }
248         }
249         newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups)
250         copy(newCmds, p.Cmds)
251         // Merge existing identifier commands with the sanitizers needed.
252         for _, id := range idents {
253                 i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq)
254                 if i != -1 {
255                         for _, name := range s[:i] {
256                                 newCmds = appendCmd(newCmds, newIdentCmd(name))
257                         }
258                         s = s[i+1:]
259                 }
260                 newCmds = appendCmd(newCmds, id)
261         }
262         // Create any remaining sanitizers.
263         for _, name := range s {
264                 newCmds = appendCmd(newCmds, newIdentCmd(name))
265         }
266         p.Cmds = newCmds
267 }
268
269 // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
270 // for all x.
271 var redundantFuncs = map[string]map[string]bool{
272         "exp_template_html_commentescaper": {
273                 "exp_template_html_attrescaper":    true,
274                 "exp_template_html_nospaceescaper": true,
275                 "exp_template_html_htmlescaper":    true,
276         },
277         "exp_template_html_cssescaper": {
278                 "exp_template_html_attrescaper": true,
279         },
280         "exp_template_html_jsregexpescaper": {
281                 "exp_template_html_attrescaper": true,
282         },
283         "exp_template_html_jsstrescaper": {
284                 "exp_template_html_attrescaper": true,
285         },
286         "exp_template_html_urlescaper": {
287                 "exp_template_html_urlnormalizer": true,
288         },
289 }
290
291 // appendCmd appends the given command to the end of the command pipeline
292 // unless it is redundant with the last command.
293 func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode {
294         if n := len(cmds); n != 0 {
295                 last, ok := cmds[n-1].Args[0].(*parse.IdentifierNode)
296                 next, _ := cmd.Args[0].(*parse.IdentifierNode)
297                 if ok && redundantFuncs[last.Ident][next.Ident] {
298                         return cmds
299                 }
300         }
301         return append(cmds, cmd)
302 }
303
304 // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found.
305 func indexOfStr(s string, strs []string, eq func(a, b string) bool) int {
306         for i, t := range strs {
307                 if eq(s, t) {
308                         return i
309                 }
310         }
311         return -1
312 }
313
314 // escFnsEq returns whether the two escaping functions are equivalent.
315 func escFnsEq(a, b string) bool {
316         if e := equivEscapers[a]; e != "" {
317                 a = e
318         }
319         if e := equivEscapers[b]; e != "" {
320                 b = e
321         }
322         return a == b
323 }
324
325 // newIdentCmd produces a command containing a single identifier node.
326 func newIdentCmd(identifier string) *parse.CommandNode {
327         return &parse.CommandNode{
328                 NodeType: parse.NodeCommand,
329                 Args:     []parse.Node{parse.NewIdentifier(identifier)},
330         }
331 }
332
333 // nudge returns the context that would result from following empty string
334 // transitions from the input context.
335 // For example, parsing:
336 //     `<a href=`
337 // will end in context{stateBeforeValue, attrURL}, but parsing one extra rune:
338 //     `<a href=x`
339 // will end in context{stateURL, delimSpaceOrTagEnd, ...}.
340 // There are two transitions that happen when the 'x' is seen:
341 // (1) Transition from a before-value state to a start-of-value state without
342 //     consuming any character.
343 // (2) Consume 'x' and transition past the first value character.
344 // In this case, nudging produces the context after (1) happens.
345 func nudge(c context) context {
346         switch c.state {
347         case stateTag:
348                 // In `<foo {{.}}`, the action should emit an attribute.
349                 c.state = stateAttrName
350         case stateBeforeValue:
351                 // In `<foo bar={{.}}`, the action is an undelimited value.
352                 c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone
353         case stateAfterName:
354                 // In `<foo bar {{.}}`, the action is an attribute name.
355                 c.state, c.attr = stateAttrName, attrNone
356         }
357         return c
358 }
359
360 // join joins the two contexts of a branch template node. The result is an
361 // error context if either of the input contexts are error contexts, or if the
362 // the input contexts differ.
363 func join(a, b context, line int, nodeName string) context {
364         if a.state == stateError {
365                 return a
366         }
367         if b.state == stateError {
368                 return b
369         }
370         if a.eq(b) {
371                 return a
372         }
373
374         c := a
375         c.urlPart = b.urlPart
376         if c.eq(b) {
377                 // The contexts differ only by urlPart.
378                 c.urlPart = urlPartUnknown
379                 return c
380         }
381
382         c = a
383         c.jsCtx = b.jsCtx
384         if c.eq(b) {
385                 // The contexts differ only by jsCtx.
386                 c.jsCtx = jsCtxUnknown
387                 return c
388         }
389
390         // Allow a nudged context to join with an unnudged one.
391         // This means that
392         //   <p title={{if .C}}{{.}}{{end}}
393         // ends in an unquoted value state even though the else branch
394         // ends in stateBeforeValue.
395         if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) {
396                 if e := join(c, d, line, nodeName); e.state != stateError {
397                         return e
398                 }
399         }
400
401         return context{
402                 state: stateError,
403                 err:   errorf(ErrBranchEnd, line, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b),
404         }
405 }
406
407 // escapeBranch escapes a branch template node: "if", "range" and "with".
408 func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
409         c0 := e.escapeList(c, n.List)
410         if nodeName == "range" && c0.state != stateError {
411                 // The "true" branch of a "range" node can execute multiple times.
412                 // We check that executing n.List once results in the same context
413                 // as executing n.List twice.
414                 c1, _ := e.escapeListConditionally(c0, n.List, nil)
415                 c0 = join(c0, c1, n.Line, nodeName)
416                 if c0.state == stateError {
417                         // Make clear that this is a problem on loop re-entry
418                         // since developers tend to overlook that branch when
419                         // debugging templates.
420                         c0.err.Line = n.Line
421                         c0.err.Description = "on range loop re-entry: " + c0.err.Description
422                         return c0
423                 }
424         }
425         c1 := e.escapeList(c, n.ElseList)
426         return join(c0, c1, n.Line, nodeName)
427 }
428
429 // escapeList escapes a list template node.
430 func (e *escaper) escapeList(c context, n *parse.ListNode) context {
431         if n == nil {
432                 return c
433         }
434         for _, m := range n.Nodes {
435                 c = e.escape(c, m)
436         }
437         return c
438 }
439
440 // escapeListConditionally escapes a list node but only preserves edits and
441 // inferences in e if the inferences and output context satisfy filter.
442 // It returns the best guess at an output context, and the result of the filter
443 // which is the same as whether e was updated.
444 func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
445         e1 := newEscaper(e.set)
446         // Make type inferences available to f.
447         for k, v := range e.output {
448                 e1.output[k] = v
449         }
450         c = e1.escapeList(c, n)
451         ok := filter != nil && filter(e1, c)
452         if ok {
453                 // Copy inferences and edits from e1 back into e.
454                 for k, v := range e1.output {
455                         e.output[k] = v
456                 }
457                 for k, v := range e1.derived {
458                         e.derived[k] = v
459                 }
460                 for k, v := range e1.called {
461                         e.called[k] = v
462                 }
463                 for k, v := range e1.actionNodeEdits {
464                         e.editActionNode(k, v)
465                 }
466                 for k, v := range e1.templateNodeEdits {
467                         e.editTemplateNode(k, v)
468                 }
469                 for k, v := range e1.textNodeEdits {
470                         e.editTextNode(k, v)
471                 }
472         }
473         return c, ok
474 }
475
476 // escapeTemplate escapes a {{template}} call node.
477 func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context {
478         c, name := e.escapeTree(c, n.Name, n.Line)
479         if name != n.Name {
480                 e.editTemplateNode(n, name)
481         }
482         return c
483 }
484
485 // escapeTree escapes the named template starting in the given context as
486 // necessary and returns its output context.
487 func (e *escaper) escapeTree(c context, name string, line int) (context, string) {
488         // Mangle the template name with the input context to produce a reliable
489         // identifier.
490         dname := c.mangle(name)
491         e.called[dname] = true
492         if out, ok := e.output[dname]; ok {
493                 // Already escaped.
494                 return out, dname
495         }
496         t := e.template(name)
497         if t == nil {
498                 return context{
499                         state: stateError,
500                         err:   errorf(ErrNoSuchTemplate, line, "no such template %s", name),
501                 }, dname
502         }
503         if dname != name {
504                 // Use any template derived during an earlier call to escapeSet
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: cloneList(t.Root)}
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.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(s[j:], doctypeBytes) {
598                                         b.Write(s[written:j])
599                                         b.WriteString("&lt;")
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(&quot;Hi!&quot;)">
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                 e.set.Add(t)
733         }
734         for n, s := range e.actionNodeEdits {
735                 ensurePipelineContains(n.Pipe, s)
736         }
737         for n, name := range e.templateNodeEdits {
738                 n.Name = name
739         }
740         for n, s := range e.textNodeEdits {
741                 n.Text = s
742         }
743 }
744
745 // template returns the named template given a mangled template name.
746 func (e *escaper) template(name string) *template.Template {
747         t := e.set.Template(name)
748         if t == nil {
749                 t = e.derived[name]
750         }
751         return t
752 }