...
Run Format

Source file src/html/template/escape.go

     1	// Copyright 2011 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	package template
     6	
     7	import (
     8		"bytes"
     9		"fmt"
    10		"html"
    11		"io"
    12		"text/template"
    13		"text/template/parse"
    14	)
    15	
    16	// 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	

View as plain text