...

Source file src/html/template/escape.go

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

View as plain text