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