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