github.com/shijuvar/go@v0.0.0-20141209052335-e8f13700b70c/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 // 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 URL context", 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). 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 } 216 panic("unidentified node type in allIdents") 217 } 218 219 // ensurePipelineContains ensures that the pipeline has commands with 220 // the identifiers in s in order. 221 // If the pipeline already has some of the sanitizers, do not interfere. 222 // For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it 223 // has one matching, "html", and one to insert, "escapeJSVal", to produce 224 // (.X | escapeJSVal | html). 225 func ensurePipelineContains(p *parse.PipeNode, s []string) { 226 if len(s) == 0 { 227 return 228 } 229 n := len(p.Cmds) 230 // Find the identifiers at the end of the command chain. 231 idents := p.Cmds 232 for i := n - 1; i >= 0; i-- { 233 if cmd := p.Cmds[i]; len(cmd.Args) != 0 { 234 if _, ok := cmd.Args[0].(*parse.IdentifierNode); ok { 235 continue 236 } 237 } 238 idents = p.Cmds[i+1:] 239 } 240 dups := 0 241 for _, idNode := range idents { 242 for _, ident := range allIdents(idNode.Args[0]) { 243 if escFnsEq(s[dups], ident) { 244 dups++ 245 if dups == len(s) { 246 return 247 } 248 } 249 } 250 } 251 newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups) 252 copy(newCmds, p.Cmds) 253 // Merge existing identifier commands with the sanitizers needed. 254 for _, idNode := range idents { 255 pos := idNode.Args[0].Position() 256 for _, ident := range allIdents(idNode.Args[0]) { 257 i := indexOfStr(ident, s, escFnsEq) 258 if i != -1 { 259 for _, name := range s[:i] { 260 newCmds = appendCmd(newCmds, newIdentCmd(name, pos)) 261 } 262 s = s[i+1:] 263 } 264 } 265 newCmds = appendCmd(newCmds, idNode) 266 } 267 // Create any remaining sanitizers. 268 for _, name := range s { 269 newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position())) 270 } 271 p.Cmds = newCmds 272 } 273 274 // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x) 275 // for all x. 276 var redundantFuncs = map[string]map[string]bool{ 277 "html_template_commentescaper": { 278 "html_template_attrescaper": true, 279 "html_template_nospaceescaper": true, 280 "html_template_htmlescaper": true, 281 }, 282 "html_template_cssescaper": { 283 "html_template_attrescaper": true, 284 }, 285 "html_template_jsregexpescaper": { 286 "html_template_attrescaper": true, 287 }, 288 "html_template_jsstrescaper": { 289 "html_template_attrescaper": true, 290 }, 291 "html_template_urlescaper": { 292 "html_template_urlnormalizer": true, 293 }, 294 } 295 296 // appendCmd appends the given command to the end of the command pipeline 297 // unless it is redundant with the last command. 298 func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode { 299 if n := len(cmds); n != 0 { 300 last, ok := cmds[n-1].Args[0].(*parse.IdentifierNode) 301 next, _ := cmd.Args[0].(*parse.IdentifierNode) 302 if ok && redundantFuncs[last.Ident][next.Ident] { 303 return cmds 304 } 305 } 306 return append(cmds, cmd) 307 } 308 309 // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found. 310 func indexOfStr(s string, strs []string, eq func(a, b string) bool) int { 311 for i, t := range strs { 312 if eq(s, t) { 313 return i 314 } 315 } 316 return -1 317 } 318 319 // escFnsEq reports whether the two escaping functions are equivalent. 320 func escFnsEq(a, b string) bool { 321 if e := equivEscapers[a]; e != "" { 322 a = e 323 } 324 if e := equivEscapers[b]; e != "" { 325 b = e 326 } 327 return a == b 328 } 329 330 // newIdentCmd produces a command containing a single identifier node. 331 func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode { 332 return &parse.CommandNode{ 333 NodeType: parse.NodeCommand, 334 Args: []parse.Node{parse.NewIdentifier(identifier).SetTree(nil).SetPos(pos)}, // TODO: SetTree. 335 } 336 } 337 338 // nudge returns the context that would result from following empty string 339 // transitions from the input context. 340 // For example, parsing: 341 // `<a href=` 342 // will end in context{stateBeforeValue, attrURL}, but parsing one extra rune: 343 // `<a href=x` 344 // will end in context{stateURL, delimSpaceOrTagEnd, ...}. 345 // There are two transitions that happen when the 'x' is seen: 346 // (1) Transition from a before-value state to a start-of-value state without 347 // consuming any character. 348 // (2) Consume 'x' and transition past the first value character. 349 // In this case, nudging produces the context after (1) happens. 350 func nudge(c context) context { 351 switch c.state { 352 case stateTag: 353 // In `<foo {{.}}`, the action should emit an attribute. 354 c.state = stateAttrName 355 case stateBeforeValue: 356 // In `<foo bar={{.}}`, the action is an undelimited value. 357 c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone 358 case stateAfterName: 359 // In `<foo bar {{.}}`, the action is an attribute name. 360 c.state, c.attr = stateAttrName, attrNone 361 } 362 return c 363 } 364 365 // join joins the two contexts of a branch template node. The result is an 366 // error context if either of the input contexts are error contexts, or if the 367 // the input contexts differ. 368 func join(a, b context, node parse.Node, nodeName string) context { 369 if a.state == stateError { 370 return a 371 } 372 if b.state == stateError { 373 return b 374 } 375 if a.eq(b) { 376 return a 377 } 378 379 c := a 380 c.urlPart = b.urlPart 381 if c.eq(b) { 382 // The contexts differ only by urlPart. 383 c.urlPart = urlPartUnknown 384 return c 385 } 386 387 c = a 388 c.jsCtx = b.jsCtx 389 if c.eq(b) { 390 // The contexts differ only by jsCtx. 391 c.jsCtx = jsCtxUnknown 392 return c 393 } 394 395 // Allow a nudged context to join with an unnudged one. 396 // This means that 397 // <p title={{if .C}}{{.}}{{end}} 398 // ends in an unquoted value state even though the else branch 399 // ends in stateBeforeValue. 400 if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) { 401 if e := join(c, d, node, nodeName); e.state != stateError { 402 return e 403 } 404 } 405 406 return context{ 407 state: stateError, 408 err: errorf(ErrBranchEnd, node, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b), 409 } 410 } 411 412 // escapeBranch escapes a branch template node: "if", "range" and "with". 413 func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context { 414 c0 := e.escapeList(c, n.List) 415 if nodeName == "range" && c0.state != stateError { 416 // The "true" branch of a "range" node can execute multiple times. 417 // We check that executing n.List once results in the same context 418 // as executing n.List twice. 419 c1, _ := e.escapeListConditionally(c0, n.List, nil) 420 c0 = join(c0, c1, n, nodeName) 421 if c0.state == stateError { 422 // Make clear that this is a problem on loop re-entry 423 // since developers tend to overlook that branch when 424 // debugging templates. 425 c0.err.Line = n.Line 426 c0.err.Description = "on range loop re-entry: " + c0.err.Description 427 return c0 428 } 429 } 430 c1 := e.escapeList(c, n.ElseList) 431 return join(c0, c1, n, nodeName) 432 } 433 434 // escapeList escapes a list template node. 435 func (e *escaper) escapeList(c context, n *parse.ListNode) context { 436 if n == nil { 437 return c 438 } 439 for _, m := range n.Nodes { 440 c = e.escape(c, m) 441 } 442 return c 443 } 444 445 // escapeListConditionally escapes a list node but only preserves edits and 446 // inferences in e if the inferences and output context satisfy filter. 447 // It returns the best guess at an output context, and the result of the filter 448 // which is the same as whether e was updated. 449 func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) { 450 e1 := newEscaper(e.tmpl) 451 // Make type inferences available to f. 452 for k, v := range e.output { 453 e1.output[k] = v 454 } 455 c = e1.escapeList(c, n) 456 ok := filter != nil && filter(e1, c) 457 if ok { 458 // Copy inferences and edits from e1 back into e. 459 for k, v := range e1.output { 460 e.output[k] = v 461 } 462 for k, v := range e1.derived { 463 e.derived[k] = v 464 } 465 for k, v := range e1.called { 466 e.called[k] = v 467 } 468 for k, v := range e1.actionNodeEdits { 469 e.editActionNode(k, v) 470 } 471 for k, v := range e1.templateNodeEdits { 472 e.editTemplateNode(k, v) 473 } 474 for k, v := range e1.textNodeEdits { 475 e.editTextNode(k, v) 476 } 477 } 478 return c, ok 479 } 480 481 // escapeTemplate escapes a {{template}} call node. 482 func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context { 483 c, name := e.escapeTree(c, n, n.Name, n.Line) 484 if name != n.Name { 485 e.editTemplateNode(n, name) 486 } 487 return c 488 } 489 490 // escapeTree escapes the named template starting in the given context as 491 // necessary and returns its output context. 492 func (e *escaper) escapeTree(c context, node parse.Node, name string, line int) (context, string) { 493 // Mangle the template name with the input context to produce a reliable 494 // identifier. 495 dname := c.mangle(name) 496 e.called[dname] = true 497 if out, ok := e.output[dname]; ok { 498 // Already escaped. 499 return out, dname 500 } 501 t := e.template(name) 502 if t == nil { 503 // Two cases: The template exists but is empty, or has never been mentioned at 504 // all. Distinguish the cases in the error messages. 505 if e.tmpl.set[name] != nil { 506 return context{ 507 state: stateError, 508 err: errorf(ErrNoSuchTemplate, node, line, "%q is an incomplete or empty template", name), 509 }, dname 510 } 511 return context{ 512 state: stateError, 513 err: errorf(ErrNoSuchTemplate, node, line, "no such template %q", name), 514 }, dname 515 } 516 if dname != name { 517 // Use any template derived during an earlier call to escapeTemplate 518 // with different top level templates, or clone if necessary. 519 dt := e.template(dname) 520 if dt == nil { 521 dt = template.New(dname) 522 dt.Tree = &parse.Tree{Name: dname, Root: t.Root.CopyList()} 523 e.derived[dname] = dt 524 } 525 t = dt 526 } 527 return e.computeOutCtx(c, t), dname 528 } 529 530 // computeOutCtx takes a template and its start context and computes the output 531 // context while storing any inferences in e. 532 func (e *escaper) computeOutCtx(c context, t *template.Template) context { 533 // Propagate context over the body. 534 c1, ok := e.escapeTemplateBody(c, t) 535 if !ok { 536 // Look for a fixed point by assuming c1 as the output context. 537 if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 { 538 c1, ok = c2, true 539 } 540 // Use c1 as the error context if neither assumption worked. 541 } 542 if !ok && c1.state != stateError { 543 return context{ 544 state: stateError, 545 err: errorf(ErrOutputContext, t.Tree.Root, 0, "cannot compute output context for template %s", t.Name()), 546 } 547 } 548 return c1 549 } 550 551 // escapeTemplateBody escapes the given template assuming the given output 552 // context, and returns the best guess at the output context and whether the 553 // assumption was correct. 554 func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) { 555 filter := func(e1 *escaper, c1 context) bool { 556 if c1.state == stateError { 557 // Do not update the input escaper, e. 558 return false 559 } 560 if !e1.called[t.Name()] { 561 // If t is not recursively called, then c1 is an 562 // accurate output context. 563 return true 564 } 565 // c1 is accurate if it matches our assumed output context. 566 return c.eq(c1) 567 } 568 // We need to assume an output context so that recursive template calls 569 // take the fast path out of escapeTree instead of infinitely recursing. 570 // Naively assuming that the input context is the same as the output 571 // works >90% of the time. 572 e.output[t.Name()] = c 573 return e.escapeListConditionally(c, t.Tree.Root, filter) 574 } 575 576 // delimEnds maps each delim to a string of characters that terminate it. 577 var delimEnds = [...]string{ 578 delimDoubleQuote: `"`, 579 delimSingleQuote: "'", 580 // Determined empirically by running the below in various browsers. 581 // var div = document.createElement("DIV"); 582 // for (var i = 0; i < 0x10000; ++i) { 583 // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>"; 584 // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0) 585 // document.write("<p>U+" + i.toString(16)); 586 // } 587 delimSpaceOrTagEnd: " \t\n\f\r>", 588 } 589 590 var doctypeBytes = []byte("<!DOCTYPE") 591 592 // escapeText escapes a text template node. 593 func (e *escaper) escapeText(c context, n *parse.TextNode) context { 594 s, written, i, b := n.Text, 0, 0, new(bytes.Buffer) 595 for i != len(s) { 596 c1, nread := contextAfterText(c, s[i:]) 597 i1 := i + nread 598 if c.state == stateText || c.state == stateRCDATA { 599 end := i1 600 if c1.state != c.state { 601 for j := end - 1; j >= i; j-- { 602 if s[j] == '<' { 603 end = j 604 break 605 } 606 } 607 } 608 for j := i; j < end; j++ { 609 if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) { 610 b.Write(s[written:j]) 611 b.WriteString("<") 612 written = j + 1 613 } 614 } 615 } else if isComment(c.state) && c.delim == delimNone { 616 switch c.state { 617 case stateJSBlockCmt: 618 // http://es5.github.com/#x7.4: 619 // "Comments behave like white space and are 620 // discarded except that, if a MultiLineComment 621 // contains a line terminator character, then 622 // the entire comment is considered to be a 623 // LineTerminator for purposes of parsing by 624 // the syntactic grammar." 625 if bytes.IndexAny(s[written:i1], "\n\r\u2028\u2029") != -1 { 626 b.WriteByte('\n') 627 } else { 628 b.WriteByte(' ') 629 } 630 case stateCSSBlockCmt: 631 b.WriteByte(' ') 632 } 633 written = i1 634 } 635 if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone { 636 // Preserve the portion between written and the comment start. 637 cs := i1 - 2 638 if c1.state == stateHTMLCmt { 639 // "<!--" instead of "/*" or "//" 640 cs -= 2 641 } 642 b.Write(s[written:cs]) 643 written = i1 644 } 645 if i == i1 && c.state == c1.state { 646 panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:])) 647 } 648 c, i = c1, i1 649 } 650 651 if written != 0 && c.state != stateError { 652 if !isComment(c.state) || c.delim != delimNone { 653 b.Write(n.Text[written:]) 654 } 655 e.editTextNode(n, b.Bytes()) 656 } 657 return c 658 } 659 660 // contextAfterText starts in context c, consumes some tokens from the front of 661 // s, then returns the context after those tokens and the unprocessed suffix. 662 func contextAfterText(c context, s []byte) (context, int) { 663 if c.delim == delimNone { 664 c1, i := tSpecialTagEnd(c, s) 665 if i == 0 { 666 // A special end tag (`</script>`) has been seen and 667 // all content preceding it has been consumed. 668 return c1, 0 669 } 670 // Consider all content up to any end tag. 671 return transitionFunc[c.state](c, s[:i]) 672 } 673 674 i := bytes.IndexAny(s, delimEnds[c.delim]) 675 if i == -1 { 676 i = len(s) 677 } 678 if c.delim == delimSpaceOrTagEnd { 679 // http://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state 680 // lists the runes below as error characters. 681 // Error out because HTML parsers may differ on whether 682 // "<a id= onclick=f(" ends inside id's or onclick's value, 683 // "<a class=`foo " ends inside a value, 684 // "<a style=font:'Arial'" needs open-quote fixup. 685 // IE treats '`' as a quotation character. 686 if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 { 687 return context{ 688 state: stateError, 689 err: errorf(ErrBadHTML, nil, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]), 690 }, len(s) 691 } 692 } 693 if i == len(s) { 694 // Remain inside the attribute. 695 // Decode the value so non-HTML rules can easily handle 696 // <button onclick="alert("Hi!")"> 697 // without having to entity decode token boundaries. 698 for u := []byte(html.UnescapeString(string(s))); len(u) != 0; { 699 c1, i1 := transitionFunc[c.state](c, u) 700 c, u = c1, u[i1:] 701 } 702 return c, len(s) 703 } 704 if c.delim != delimSpaceOrTagEnd { 705 // Consume any quote. 706 i++ 707 } 708 // On exiting an attribute, we discard all state information 709 // except the state and element. 710 return context{state: stateTag, element: c.element}, i 711 } 712 713 // editActionNode records a change to an action pipeline for later commit. 714 func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) { 715 if _, ok := e.actionNodeEdits[n]; ok { 716 panic(fmt.Sprintf("node %s shared between templates", n)) 717 } 718 e.actionNodeEdits[n] = cmds 719 } 720 721 // editTemplateNode records a change to a {{template}} callee for later commit. 722 func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) { 723 if _, ok := e.templateNodeEdits[n]; ok { 724 panic(fmt.Sprintf("node %s shared between templates", n)) 725 } 726 e.templateNodeEdits[n] = callee 727 } 728 729 // editTextNode records a change to a text node for later commit. 730 func (e *escaper) editTextNode(n *parse.TextNode, text []byte) { 731 if _, ok := e.textNodeEdits[n]; ok { 732 panic(fmt.Sprintf("node %s shared between templates", n)) 733 } 734 e.textNodeEdits[n] = text 735 } 736 737 // commit applies changes to actions and template calls needed to contextually 738 // autoescape content and adds any derived templates to the set. 739 func (e *escaper) commit() { 740 for name := range e.output { 741 e.template(name).Funcs(funcMap) 742 } 743 for _, t := range e.derived { 744 if _, err := e.tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil { 745 panic("error adding derived template") 746 } 747 } 748 for n, s := range e.actionNodeEdits { 749 ensurePipelineContains(n.Pipe, s) 750 } 751 for n, name := range e.templateNodeEdits { 752 n.Name = name 753 } 754 for n, s := range e.textNodeEdits { 755 n.Text = s 756 } 757 } 758 759 // template returns the named template given a mangled template name. 760 func (e *escaper) template(name string) *template.Template { 761 t := e.tmpl.text.Lookup(name) 762 if t == nil { 763 t = e.derived[name] 764 } 765 return t 766 } 767 768 // Forwarding functions so that clients need only import this package 769 // to reach the general escaping functions of text/template. 770 771 // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. 772 func HTMLEscape(w io.Writer, b []byte) { 773 template.HTMLEscape(w, b) 774 } 775 776 // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. 777 func HTMLEscapeString(s string) string { 778 return template.HTMLEscapeString(s) 779 } 780 781 // HTMLEscaper returns the escaped HTML equivalent of the textual 782 // representation of its arguments. 783 func HTMLEscaper(args ...interface{}) string { 784 return template.HTMLEscaper(args...) 785 } 786 787 // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. 788 func JSEscape(w io.Writer, b []byte) { 789 template.JSEscape(w, b) 790 } 791 792 // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. 793 func JSEscapeString(s string) string { 794 return template.JSEscapeString(s) 795 } 796 797 // JSEscaper returns the escaped JavaScript equivalent of the textual 798 // representation of its arguments. 799 func JSEscaper(args ...interface{}) string { 800 return template.JSEscaper(args...) 801 } 802 803 // URLQueryEscaper returns the escaped value of the textual representation of 804 // its arguments in a form suitable for embedding in a URL query. 805 func URLQueryEscaper(args ...interface{}) string { 806 return template.URLQueryEscaper(args...) 807 }