github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/src/pkg/html/template/escape.go (about)

     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  					t.Tree = nil
    39  				}
    40  			}
    41  			return err
    42  		}
    43  		tmpl.escaped = true
    44  		tmpl.Tree = tmpl.text.Tree
    45  	}
    46  	e.commit()
    47  	return nil
    48  }
    49  
    50  // funcMap maps command names to functions that render their inputs safe.
    51  var funcMap = template.FuncMap{
    52  	"html_template_attrescaper":     attrEscaper,
    53  	"html_template_commentescaper":  commentEscaper,
    54  	"html_template_cssescaper":      cssEscaper,
    55  	"html_template_cssvaluefilter":  cssValueFilter,
    56  	"html_template_htmlnamefilter":  htmlNameFilter,
    57  	"html_template_htmlescaper":     htmlEscaper,
    58  	"html_template_jsregexpescaper": jsRegexpEscaper,
    59  	"html_template_jsstrescaper":    jsStrEscaper,
    60  	"html_template_jsvalescaper":    jsValEscaper,
    61  	"html_template_nospaceescaper":  htmlNospaceEscaper,
    62  	"html_template_rcdataescaper":   rcdataEscaper,
    63  	"html_template_urlescaper":      urlEscaper,
    64  	"html_template_urlfilter":       urlFilter,
    65  	"html_template_urlnormalizer":   urlNormalizer,
    66  }
    67  
    68  // equivEscapers matches contextual escapers to equivalent template builtins.
    69  var equivEscapers = map[string]string{
    70  	"html_template_attrescaper":    "html",
    71  	"html_template_htmlescaper":    "html",
    72  	"html_template_nospaceescaper": "html",
    73  	"html_template_rcdataescaper":  "html",
    74  	"html_template_urlescaper":     "urlquery",
    75  	"html_template_urlnormalizer":  "urlquery",
    76  }
    77  
    78  // escaper collects type inferences about templates and changes needed to make
    79  // templates injection safe.
    80  type escaper struct {
    81  	tmpl *Template
    82  	// output[templateName] is the output context for a templateName that
    83  	// has been mangled to include its input context.
    84  	output map[string]context
    85  	// derived[c.mangle(name)] maps to a template derived from the template
    86  	// named name templateName for the start context c.
    87  	derived map[string]*template.Template
    88  	// called[templateName] is a set of called mangled template names.
    89  	called map[string]bool
    90  	// xxxNodeEdits are the accumulated edits to apply during commit.
    91  	// Such edits are not applied immediately in case a template set
    92  	// executes a given template in different escaping contexts.
    93  	actionNodeEdits   map[*parse.ActionNode][]string
    94  	templateNodeEdits map[*parse.TemplateNode]string
    95  	textNodeEdits     map[*parse.TextNode][]byte
    96  }
    97  
    98  // newEscaper creates a blank escaper for the given set.
    99  func newEscaper(t *Template) *escaper {
   100  	return &escaper{
   101  		t,
   102  		map[string]context{},
   103  		map[string]*template.Template{},
   104  		map[string]bool{},
   105  		map[*parse.ActionNode][]string{},
   106  		map[*parse.TemplateNode]string{},
   107  		map[*parse.TextNode][]byte{},
   108  	}
   109  }
   110  
   111  // filterFailsafe is an innocuous word that is emitted in place of unsafe values
   112  // by sanitizer functions. It is not a keyword in any programming language,
   113  // contains no special characters, is not empty, and when it appears in output
   114  // it is distinct enough that a developer can find the source of the problem
   115  // via a search engine.
   116  const filterFailsafe = "ZgotmplZ"
   117  
   118  // escape escapes a template node.
   119  func (e *escaper) escape(c context, n parse.Node) context {
   120  	switch n := n.(type) {
   121  	case *parse.ActionNode:
   122  		return e.escapeAction(c, n)
   123  	case *parse.IfNode:
   124  		return e.escapeBranch(c, &n.BranchNode, "if")
   125  	case *parse.ListNode:
   126  		return e.escapeList(c, n)
   127  	case *parse.RangeNode:
   128  		return e.escapeBranch(c, &n.BranchNode, "range")
   129  	case *parse.TemplateNode:
   130  		return e.escapeTemplate(c, n)
   131  	case *parse.TextNode:
   132  		return e.escapeText(c, n)
   133  	case *parse.WithNode:
   134  		return e.escapeBranch(c, &n.BranchNode, "with")
   135  	}
   136  	panic("escaping " + n.String() + " is unimplemented")
   137  }
   138  
   139  // escapeAction escapes an action template node.
   140  func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
   141  	if len(n.Pipe.Decl) != 0 {
   142  		// A local variable assignment, not an interpolation.
   143  		return c
   144  	}
   145  	c = nudge(c)
   146  	s := make([]string, 0, 3)
   147  	switch c.state {
   148  	case stateError:
   149  		return c
   150  	case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
   151  		switch c.urlPart {
   152  		case urlPartNone:
   153  			s = append(s, "html_template_urlfilter")
   154  			fallthrough
   155  		case urlPartPreQuery:
   156  			switch c.state {
   157  			case stateCSSDqStr, stateCSSSqStr:
   158  				s = append(s, "html_template_cssescaper")
   159  			default:
   160  				s = append(s, "html_template_urlnormalizer")
   161  			}
   162  		case urlPartQueryOrFrag:
   163  			s = append(s, "html_template_urlescaper")
   164  		case urlPartUnknown:
   165  			return context{
   166  				state: stateError,
   167  				err:   errorf(ErrAmbigContext, n.Line, "%s appears in an ambiguous URL context", n),
   168  			}
   169  		default:
   170  			panic(c.urlPart.String())
   171  		}
   172  	case stateJS:
   173  		s = append(s, "html_template_jsvalescaper")
   174  		// A slash after a value starts a div operator.
   175  		c.jsCtx = jsCtxDivOp
   176  	case stateJSDqStr, stateJSSqStr:
   177  		s = append(s, "html_template_jsstrescaper")
   178  	case stateJSRegexp:
   179  		s = append(s, "html_template_jsregexpescaper")
   180  	case stateCSS:
   181  		s = append(s, "html_template_cssvaluefilter")
   182  	case stateText:
   183  		s = append(s, "html_template_htmlescaper")
   184  	case stateRCDATA:
   185  		s = append(s, "html_template_rcdataescaper")
   186  	case stateAttr:
   187  		// Handled below in delim check.
   188  	case stateAttrName, stateTag:
   189  		c.state = stateAttrName
   190  		s = append(s, "html_template_htmlnamefilter")
   191  	default:
   192  		if isComment(c.state) {
   193  			s = append(s, "html_template_commentescaper")
   194  		} else {
   195  			panic("unexpected state " + c.state.String())
   196  		}
   197  	}
   198  	switch c.delim {
   199  	case delimNone:
   200  		// No extra-escaping needed for raw text content.
   201  	case delimSpaceOrTagEnd:
   202  		s = append(s, "html_template_nospaceescaper")
   203  	default:
   204  		s = append(s, "html_template_attrescaper")
   205  	}
   206  	e.editActionNode(n, s)
   207  	return c
   208  }
   209  
   210  // ensurePipelineContains ensures that the pipeline has commands with
   211  // the identifiers in s in order.
   212  // If the pipeline already has some of the sanitizers, do not interfere.
   213  // For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it
   214  // has one matching, "html", and one to insert, "escapeJSVal", to produce
   215  // (.X | escapeJSVal | html).
   216  func ensurePipelineContains(p *parse.PipeNode, s []string) {
   217  	if len(s) == 0 {
   218  		return
   219  	}
   220  	n := len(p.Cmds)
   221  	// Find the identifiers at the end of the command chain.
   222  	idents := p.Cmds
   223  	for i := n - 1; i >= 0; i-- {
   224  		if cmd := p.Cmds[i]; len(cmd.Args) != 0 {
   225  			if _, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
   226  				continue
   227  			}
   228  		}
   229  		idents = p.Cmds[i+1:]
   230  	}
   231  	dups := 0
   232  	for _, id := range idents {
   233  		if escFnsEq(s[dups], (id.Args[0].(*parse.IdentifierNode)).Ident) {
   234  			dups++
   235  			if dups == len(s) {
   236  				return
   237  			}
   238  		}
   239  	}
   240  	newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups)
   241  	copy(newCmds, p.Cmds)
   242  	// Merge existing identifier commands with the sanitizers needed.
   243  	for _, id := range idents {
   244  		pos := id.Args[0].Position()
   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, pos))
   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, p.Position()))
   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 reports 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, pos parse.Pos) *parse.CommandNode {
   319  	return &parse.CommandNode{
   320  		NodeType: parse.NodeCommand,
   321  		Args:     []parse.Node{parse.NewIdentifier(identifier).SetPos(pos)},
   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("&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  		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  }