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