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