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.
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
23 func escapeTemplates(tmpl
*Template
, names
...string) error
{
25 for _
, name
:= range names
{
26 c
, _
:= e
.escapeTree(context
{}, name
, 0)
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
)}
34 // Prevent execution of unsafe templates.
35 for _
, name
:= range names
{
36 if t
:= tmpl
.set
[name
]; t
!= nil {
45 for _
, name
:= range names
{
46 if t
:= tmpl
.set
[name
]; t
!= nil {
54 // funcMap maps command names to functions that render their inputs safe.
55 var funcMap
= template
.FuncMap
{
56 "html_template_attrescaper": attrEscaper
,
57 "html_template_commentescaper": commentEscaper
,
58 "html_template_cssescaper": cssEscaper
,
59 "html_template_cssvaluefilter": cssValueFilter
,
60 "html_template_htmlnamefilter": htmlNameFilter
,
61 "html_template_htmlescaper": htmlEscaper
,
62 "html_template_jsregexpescaper": jsRegexpEscaper
,
63 "html_template_jsstrescaper": jsStrEscaper
,
64 "html_template_jsvalescaper": jsValEscaper
,
65 "html_template_nospaceescaper": htmlNospaceEscaper
,
66 "html_template_rcdataescaper": rcdataEscaper
,
67 "html_template_urlescaper": urlEscaper
,
68 "html_template_urlfilter": urlFilter
,
69 "html_template_urlnormalizer": urlNormalizer
,
72 // equivEscapers matches contextual escapers to equivalent template builtins.
73 var equivEscapers
= map[string]string{
74 "html_template_attrescaper": "html",
75 "html_template_htmlescaper": "html",
76 "html_template_nospaceescaper": "html",
77 "html_template_rcdataescaper": "html",
78 "html_template_urlescaper": "urlquery",
79 "html_template_urlnormalizer": "urlquery",
82 // escaper collects type inferences about templates and changes needed to make
83 // templates injection safe.
86 // output[templateName] is the output context for a templateName that
87 // has been mangled to include its input context.
88 output
map[string]context
89 // derived[c.mangle(name)] maps to a template derived from the template
90 // named name templateName for the start context c.
91 derived
map[string]*template
.Template
92 // called[templateName] is a set of called mangled template names.
93 called
map[string]bool
94 // xxxNodeEdits are the accumulated edits to apply during commit.
95 // Such edits are not applied immediately in case a template set
96 // executes a given template in different escaping contexts.
97 actionNodeEdits
map[*parse
.ActionNode
][]string
98 templateNodeEdits
map[*parse
.TemplateNode
]string
99 textNodeEdits
map[*parse
.TextNode
][]byte
102 // newEscaper creates a blank escaper for the given set.
103 func newEscaper(t
*Template
) *escaper
{
106 map[string]context
{},
107 map[string]*template
.Template
{},
109 map[*parse
.ActionNode
][]string{},
110 map[*parse
.TemplateNode
]string{},
111 map[*parse
.TextNode
][]byte{},
115 // filterFailsafe is an innocuous word that is emitted in place of unsafe values
116 // by sanitizer functions. It is not a keyword in any programming language,
117 // contains no special characters, is not empty, and when it appears in output
118 // it is distinct enough that a developer can find the source of the problem
119 // via a search engine.
120 const filterFailsafe
= "ZgotmplZ"
122 // escape escapes a template node.
123 func (e
*escaper
) escape(c context
, n parse
.Node
) context
{
124 switch n
:= n
.(type) {
125 case *parse
.ActionNode
:
126 return e
.escapeAction(c
, n
)
128 return e
.escapeBranch(c
, &n
.BranchNode
, "if")
129 case *parse
.ListNode
:
130 return e
.escapeList(c
, n
)
131 case *parse
.RangeNode
:
132 return e
.escapeBranch(c
, &n
.BranchNode
, "range")
133 case *parse
.TemplateNode
:
134 return e
.escapeTemplate(c
, n
)
135 case *parse
.TextNode
:
136 return e
.escapeText(c
, n
)
137 case *parse
.WithNode
:
138 return e
.escapeBranch(c
, &n
.BranchNode
, "with")
140 panic("escaping " + n
.String() + " is unimplemented")
143 // escapeAction escapes an action template node.
144 func (e
*escaper
) escapeAction(c context
, n
*parse
.ActionNode
) context
{
145 if len(n
.Pipe
.Decl
) != 0 {
146 // A local variable assignment, not an interpolation.
150 s
:= make([]string, 0, 3)
154 case stateURL
, stateCSSDqStr
, stateCSSSqStr
, stateCSSDqURL
, stateCSSSqURL
, stateCSSURL
:
157 s
= append(s
, "html_template_urlfilter")
159 case urlPartPreQuery
:
161 case stateCSSDqStr
, stateCSSSqStr
:
162 s
= append(s
, "html_template_cssescaper")
164 s
= append(s
, "html_template_urlnormalizer")
166 case urlPartQueryOrFrag
:
167 s
= append(s
, "html_template_urlescaper")
171 err
: errorf(ErrAmbigContext
, n
.Line
, "%s appears in an ambiguous URL context", n
),
174 panic(c
.urlPart
.String())
177 s
= append(s
, "html_template_jsvalescaper")
178 // A slash after a value starts a div operator.
180 case stateJSDqStr
, stateJSSqStr
:
181 s
= append(s
, "html_template_jsstrescaper")
183 s
= append(s
, "html_template_jsregexpescaper")
185 s
= append(s
, "html_template_cssvaluefilter")
187 s
= append(s
, "html_template_htmlescaper")
189 s
= append(s
, "html_template_rcdataescaper")
191 // Handled below in delim check.
192 case stateAttrName
, stateTag
:
193 c
.state
= stateAttrName
194 s
= append(s
, "html_template_htmlnamefilter")
196 if isComment(c
.state
) {
197 s
= append(s
, "html_template_commentescaper")
199 panic("unexpected state " + c
.state
.String())
204 // No extra-escaping needed for raw text content.
205 case delimSpaceOrTagEnd
:
206 s
= append(s
, "html_template_nospaceescaper")
208 s
= append(s
, "html_template_attrescaper")
210 e
.editActionNode(n
, s
)
214 // allIdents returns the names of the identifiers under the Ident field of the node,
215 // which might be a singleton (Identifier) or a slice (Field).
216 func allIdents(node parse
.Node
) []string {
217 switch node
:= node
.(type) {
218 case *parse
.IdentifierNode
:
219 return []string{node
.Ident
}
220 case *parse
.FieldNode
:
223 panic("unidentified node type in allIdents")
226 // ensurePipelineContains ensures that the pipeline has commands with
227 // the identifiers in s in order.
228 // If the pipeline already has some of the sanitizers, do not interfere.
229 // For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it
230 // has one matching, "html", and one to insert, "escapeJSVal", to produce
231 // (.X | escapeJSVal | html).
232 func ensurePipelineContains(p
*parse
.PipeNode
, s
[]string) {
237 // Find the identifiers at the end of the command chain.
239 for i
:= n
- 1; i
>= 0; i
-- {
240 if cmd
:= p
.Cmds
[i
]; len(cmd
.Args
) != 0 {
241 if _
, ok
:= cmd
.Args
[0].(*parse
.IdentifierNode
); ok
{
245 idents
= p
.Cmds
[i
+1:]
248 for _
, idNode
:= range idents
{
249 for _
, ident
:= range allIdents(idNode
.Args
[0]) {
250 if escFnsEq(s
[dups
], ident
) {
258 newCmds
:= make([]*parse
.CommandNode
, n
-len(idents
), n
+len(s
)-dups
)
259 copy(newCmds
, p
.Cmds
)
260 // Merge existing identifier commands with the sanitizers needed.
261 for _
, idNode
:= range idents
{
262 pos
:= idNode
.Args
[0].Position()
263 for _
, ident
:= range allIdents(idNode
.Args
[0]) {
264 i
:= indexOfStr(ident
, s
, escFnsEq
)
266 for _
, name
:= range s
[:i
] {
267 newCmds
= appendCmd(newCmds
, newIdentCmd(name
, pos
))
272 newCmds
= appendCmd(newCmds
, idNode
)
274 // Create any remaining sanitizers.
275 for _
, name
:= range s
{
276 newCmds
= appendCmd(newCmds
, newIdentCmd(name
, p
.Position()))
281 // redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
283 var redundantFuncs
= map[string]map[string]bool{
284 "html_template_commentescaper": {
285 "html_template_attrescaper": true,
286 "html_template_nospaceescaper": true,
287 "html_template_htmlescaper": true,
289 "html_template_cssescaper": {
290 "html_template_attrescaper": true,
292 "html_template_jsregexpescaper": {
293 "html_template_attrescaper": true,
295 "html_template_jsstrescaper": {
296 "html_template_attrescaper": true,
298 "html_template_urlescaper": {
299 "html_template_urlnormalizer": true,
303 // appendCmd appends the given command to the end of the command pipeline
304 // unless it is redundant with the last command.
305 func appendCmd(cmds
[]*parse
.CommandNode
, cmd
*parse
.CommandNode
) []*parse
.CommandNode
{
306 if n
:= len(cmds
); n
!= 0 {
307 last
, ok
:= cmds
[n
-1].Args
[0].(*parse
.IdentifierNode
)
308 next
, _
:= cmd
.Args
[0].(*parse
.IdentifierNode
)
309 if ok
&& redundantFuncs
[last
.Ident
][next
.Ident
] {
313 return append(cmds
, cmd
)
316 // indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found.
317 func indexOfStr(s
string, strs
[]string, eq
func(a
, b
string) bool) int {
318 for i
, t
:= range strs
{
326 // escFnsEq reports whether the two escaping functions are equivalent.
327 func escFnsEq(a
, b
string) bool {
328 if e
:= equivEscapers
[a
]; e
!= "" {
331 if e
:= equivEscapers
[b
]; e
!= "" {
337 // newIdentCmd produces a command containing a single identifier node.
338 func newIdentCmd(identifier
string, pos parse
.Pos
) *parse
.CommandNode
{
339 return &parse
.CommandNode
{
340 NodeType
: parse
.NodeCommand
,
341 Args
: []parse
.Node
{parse
.NewIdentifier(identifier
).SetPos(pos
)},
345 // nudge returns the context that would result from following empty string
346 // transitions from the input context.
347 // For example, parsing:
349 // will end in context{stateBeforeValue, attrURL}, but parsing one extra rune:
351 // will end in context{stateURL, delimSpaceOrTagEnd, ...}.
352 // There are two transitions that happen when the 'x' is seen:
353 // (1) Transition from a before-value state to a start-of-value state without
354 // consuming any character.
355 // (2) Consume 'x' and transition past the first value character.
356 // In this case, nudging produces the context after (1) happens.
357 func nudge(c context
) context
{
360 // In `<foo {{.}}`, the action should emit an attribute.
361 c
.state
= stateAttrName
362 case stateBeforeValue
:
363 // In `<foo bar={{.}}`, the action is an undelimited value.
364 c
.state
, c
.delim
, c
.attr
= attrStartStates
[c
.attr
], delimSpaceOrTagEnd
, attrNone
366 // In `<foo bar {{.}}`, the action is an attribute name.
367 c
.state
, c
.attr
= stateAttrName
, attrNone
372 // join joins the two contexts of a branch template node. The result is an
373 // error context if either of the input contexts are error contexts, or if the
374 // the input contexts differ.
375 func join(a
, b context
, line
int, nodeName
string) context
{
376 if a
.state
== stateError
{
379 if b
.state
== stateError
{
387 c
.urlPart
= b
.urlPart
389 // The contexts differ only by urlPart.
390 c
.urlPart
= urlPartUnknown
397 // The contexts differ only by jsCtx.
398 c
.jsCtx
= jsCtxUnknown
402 // Allow a nudged context to join with an unnudged one.
404 // <p title={{if .C}}{{.}}{{end}}
405 // ends in an unquoted value state even though the else branch
406 // ends in stateBeforeValue.
407 if c
, d
:= nudge(a
), nudge(b
); !(c
.eq(a
) && d
.eq(b
)) {
408 if e
:= join(c
, d
, line
, nodeName
); e
.state
!= stateError
{
415 err
: errorf(ErrBranchEnd
, line
, "{{%s}} branches end in different contexts: %v, %v", nodeName
, a
, b
),
419 // escapeBranch escapes a branch template node: "if", "range" and "with".
420 func (e
*escaper
) escapeBranch(c context
, n
*parse
.BranchNode
, nodeName
string) context
{
421 c0
:= e
.escapeList(c
, n
.List
)
422 if nodeName
== "range" && c0
.state
!= stateError
{
423 // The "true" branch of a "range" node can execute multiple times.
424 // We check that executing n.List once results in the same context
425 // as executing n.List twice.
426 c1
, _
:= e
.escapeListConditionally(c0
, n
.List
, nil)
427 c0
= join(c0
, c1
, n
.Line
, nodeName
)
428 if c0
.state
== stateError
{
429 // Make clear that this is a problem on loop re-entry
430 // since developers tend to overlook that branch when
431 // debugging templates.
433 c0
.err
.Description
= "on range loop re-entry: " + c0
.err
.Description
437 c1
:= e
.escapeList(c
, n
.ElseList
)
438 return join(c0
, c1
, n
.Line
, nodeName
)
441 // escapeList escapes a list template node.
442 func (e
*escaper
) escapeList(c context
, n
*parse
.ListNode
) context
{
446 for _
, m
:= range n
.Nodes
{
452 // escapeListConditionally escapes a list node but only preserves edits and
453 // inferences in e if the inferences and output context satisfy filter.
454 // It returns the best guess at an output context, and the result of the filter
455 // which is the same as whether e was updated.
456 func (e
*escaper
) escapeListConditionally(c context
, n
*parse
.ListNode
, filter
func(*escaper
, context
) bool) (context
, bool) {
457 e1
:= newEscaper(e
.tmpl
)
458 // Make type inferences available to f.
459 for k
, v
:= range e
.output
{
462 c
= e1
.escapeList(c
, n
)
463 ok
:= filter
!= nil && filter(e1
, c
)
465 // Copy inferences and edits from e1 back into e.
466 for k
, v
:= range e1
.output
{
469 for k
, v
:= range e1
.derived
{
472 for k
, v
:= range e1
.called
{
475 for k
, v
:= range e1
.actionNodeEdits
{
476 e
.editActionNode(k
, v
)
478 for k
, v
:= range e1
.templateNodeEdits
{
479 e
.editTemplateNode(k
, v
)
481 for k
, v
:= range e1
.textNodeEdits
{
488 // escapeTemplate escapes a {{template}} call node.
489 func (e
*escaper
) escapeTemplate(c context
, n
*parse
.TemplateNode
) context
{
490 c
, name
:= e
.escapeTree(c
, n
.Name
, n
.Line
)
492 e
.editTemplateNode(n
, name
)
497 // escapeTree escapes the named template starting in the given context as
498 // necessary and returns its output context.
499 func (e
*escaper
) escapeTree(c context
, name
string, line
int) (context
, string) {
500 // Mangle the template name with the input context to produce a reliable
502 dname
:= c
.mangle(name
)
503 e
.called
[dname
] = true
504 if out
, ok
:= e
.output
[dname
]; ok
{
508 t
:= e
.template(name
)
510 // Two cases: The template exists but is empty, or has never been mentioned at
511 // all. Distinguish the cases in the error messages.
512 if e
.tmpl
.set
[name
] != nil {
515 err
: errorf(ErrNoSuchTemplate
, line
, "%q is an incomplete or empty template", name
),
520 err
: errorf(ErrNoSuchTemplate
, line
, "no such template %q", name
),
524 // Use any template derived during an earlier call to escapeTemplate
525 // with different top level templates, or clone if necessary.
526 dt
:= e
.template(dname
)
528 dt
= template
.New(dname
)
529 dt
.Tree
= &parse
.Tree
{Name
: dname
, Root
: t
.Root
.CopyList()}
530 e
.derived
[dname
] = dt
534 return e
.computeOutCtx(c
, t
), dname
537 // computeOutCtx takes a template and its start context and computes the output
538 // context while storing any inferences in e.
539 func (e
*escaper
) computeOutCtx(c context
, t
*template
.Template
) context
{
540 // Propagate context over the body.
541 c1
, ok
:= e
.escapeTemplateBody(c
, t
)
543 // Look for a fixed point by assuming c1 as the output context.
544 if c2
, ok2
:= e
.escapeTemplateBody(c1
, t
); ok2
{
547 // Use c1 as the error context if neither assumption worked.
549 if !ok
&& c1
.state
!= stateError
{
552 // TODO: Find the first node with a line in t.text.Tree.Root
553 err
: errorf(ErrOutputContext
, 0, "cannot compute output context for template %s", t
.Name()),
559 // escapeTemplateBody escapes the given template assuming the given output
560 // context, and returns the best guess at the output context and whether the
561 // assumption was correct.
562 func (e
*escaper
) escapeTemplateBody(c context
, t
*template
.Template
) (context
, bool) {
563 filter
:= func(e1
*escaper
, c1 context
) bool {
564 if c1
.state
== stateError
{
565 // Do not update the input escaper, e.
568 if !e1
.called
[t
.Name()] {
569 // If t is not recursively called, then c1 is an
570 // accurate output context.
573 // c1 is accurate if it matches our assumed output context.
576 // We need to assume an output context so that recursive template calls
577 // take the fast path out of escapeTree instead of infinitely recursing.
578 // Naively assuming that the input context is the same as the output
579 // works >90% of the time.
580 e
.output
[t
.Name()] = c
581 return e
.escapeListConditionally(c
, t
.Tree
.Root
, filter
)
584 // delimEnds maps each delim to a string of characters that terminate it.
585 var delimEnds
= [...]string{
586 delimDoubleQuote
: `"`,
587 delimSingleQuote
: "'",
588 // Determined empirically by running the below in various browsers.
589 // var div = document.createElement("DIV");
590 // for (var i = 0; i < 0x10000; ++i) {
591 // div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>";
592 // if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0)
593 // document.write("<p>U+" + i.toString(16));
595 delimSpaceOrTagEnd
: " \t\n\f\r>",
598 var doctypeBytes
= []byte("<!DOCTYPE")
600 // escapeText escapes a text template node.
601 func (e
*escaper
) escapeText(c context
, n
*parse
.TextNode
) context
{
602 s
, written
, i
, b
:= n
.Text
, 0, 0, new(bytes
.Buffer
)
604 c1
, nread
:= contextAfterText(c
, s
[i
:])
606 if c
.state
== stateText || c
.state
== stateRCDATA
{
608 if c1
.state
!= c
.state
{
609 for j
:= end
- 1; j
>= i
; j
-- {
616 for j
:= i
; j
< end
; j
++ {
617 if s
[j
] == '<' && !bytes
.HasPrefix(bytes
.ToUpper(s
[j
:]), doctypeBytes
) {
618 b
.Write(s
[written
:j
])
619 b
.WriteString("<")
623 } else if isComment(c
.state
) && c
.delim
== delimNone
{
625 case stateJSBlockCmt
:
626 // http://es5.github.com/#x7.4:
627 // "Comments behave like white space and are
628 // discarded except that, if a MultiLineComment
629 // contains a line terminator character, then
630 // the entire comment is considered to be a
631 // LineTerminator for purposes of parsing by
632 // the syntactic grammar."
633 if bytes
.IndexAny(s
[written
:i1
], "\n\r\u2028\u2029") != -1 {
638 case stateCSSBlockCmt
:
643 if c
.state
!= c1
.state
&& isComment(c1
.state
) && c1
.delim
== delimNone
{
644 // Preserve the portion between written and the comment start.
646 if c1
.state
== stateHTMLCmt
{
647 // "<!--" instead of "/*" or "//"
650 b
.Write(s
[written
:cs
])
653 if i
== i1
&& c
.state
== c1
.state
{
654 panic(fmt
.Sprintf("infinite loop from %v to %v on %q..%q", c
, c1
, s
[:i
], s
[i
:]))
659 if written
!= 0 && c
.state
!= stateError
{
660 if !isComment(c
.state
) || c
.delim
!= delimNone
{
661 b
.Write(n
.Text
[written
:])
663 e
.editTextNode(n
, b
.Bytes())
668 // contextAfterText starts in context c, consumes some tokens from the front of
669 // s, then returns the context after those tokens and the unprocessed suffix.
670 func contextAfterText(c context
, s
[]byte) (context
, int) {
671 if c
.delim
== delimNone
{
672 c1
, i
:= tSpecialTagEnd(c
, s
)
674 // A special end tag (`</script>`) has been seen and
675 // all content preceding it has been consumed.
678 // Consider all content up to any end tag.
679 return transitionFunc
[c
.state
](c
, s
[:i
])
682 i
:= bytes
.IndexAny(s
, delimEnds
[c
.delim
])
686 if c
.delim
== delimSpaceOrTagEnd
{
687 // http://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state
688 // lists the runes below as error characters.
689 // Error out because HTML parsers may differ on whether
690 // "<a id= onclick=f(" ends inside id's or onclick's value,
691 // "<a class=`foo " ends inside a value,
692 // "<a style=font:'Arial'" needs open-quote fixup.
693 // IE treats '`' as a quotation character.
694 if j
:= bytes
.IndexAny(s
[:i
], "\"'<=`"); j
>= 0 {
697 err
: errorf(ErrBadHTML
, 0, "%q in unquoted attr: %q", s
[j
:j
+1], s
[:i
]),
702 // Remain inside the attribute.
703 // Decode the value so non-HTML rules can easily handle
704 // <button onclick="alert("Hi!")">
705 // without having to entity decode token boundaries.
706 for u
:= []byte(html
.UnescapeString(string(s
))); len(u
) != 0; {
707 c1
, i1
:= transitionFunc
[c
.state
](c
, u
)
712 if c
.delim
!= delimSpaceOrTagEnd
{
713 // Consume any quote.
716 // On exiting an attribute, we discard all state information
717 // except the state and element.
718 return context
{state
: stateTag
, element
: c
.element
}, i
721 // editActionNode records a change to an action pipeline for later commit.
722 func (e
*escaper
) editActionNode(n
*parse
.ActionNode
, cmds
[]string) {
723 if _
, ok
:= e
.actionNodeEdits
[n
]; ok
{
724 panic(fmt
.Sprintf("node %s shared between templates", n
))
726 e
.actionNodeEdits
[n
] = cmds
729 // editTemplateNode records a change to a {{template}} callee for later commit.
730 func (e
*escaper
) editTemplateNode(n
*parse
.TemplateNode
, callee
string) {
731 if _
, ok
:= e
.templateNodeEdits
[n
]; ok
{
732 panic(fmt
.Sprintf("node %s shared between templates", n
))
734 e
.templateNodeEdits
[n
] = callee
737 // editTextNode records a change to a text node for later commit.
738 func (e
*escaper
) editTextNode(n
*parse
.TextNode
, text
[]byte) {
739 if _
, ok
:= e
.textNodeEdits
[n
]; ok
{
740 panic(fmt
.Sprintf("node %s shared between templates", n
))
742 e
.textNodeEdits
[n
] = text
745 // commit applies changes to actions and template calls needed to contextually
746 // autoescape content and adds any derived templates to the set.
747 func (e
*escaper
) commit() {
748 for name
:= range e
.output
{
749 e
.template(name
).Funcs(funcMap
)
751 for _
, t
:= range e
.derived
{
752 if _
, err
:= e
.tmpl
.text
.AddParseTree(t
.Name(), t
.Tree
); err
!= nil {
753 panic("error adding derived template")
756 for n
, s
:= range e
.actionNodeEdits
{
757 ensurePipelineContains(n
.Pipe
, s
)
759 for n
, name
:= range e
.templateNodeEdits
{
762 for n
, s
:= range e
.textNodeEdits
{
767 // template returns the named template given a mangled template name.
768 func (e
*escaper
) template(name
string) *template
.Template
{
769 t
:= e
.tmpl
.text
.Lookup(name
)
776 // Forwarding functions so that clients need only import this package
777 // to reach the general escaping functions of text/template.
779 // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
780 func HTMLEscape(w io
.Writer
, b
[]byte) {
781 template
.HTMLEscape(w
, b
)
784 // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
785 func HTMLEscapeString(s
string) string {
786 return template
.HTMLEscapeString(s
)
789 // HTMLEscaper returns the escaped HTML equivalent of the textual
790 // representation of its arguments.
791 func HTMLEscaper(args
...interface{}) string {
792 return template
.HTMLEscaper(args
...)
795 // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
796 func JSEscape(w io
.Writer
, b
[]byte) {
797 template
.JSEscape(w
, b
)
800 // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
801 func JSEscapeString(s
string) string {
802 return template
.JSEscapeString(s
)
805 // JSEscaper returns the escaped JavaScript equivalent of the textual
806 // representation of its arguments.
807 func JSEscaper(args
...interface{}) string {
808 return template
.JSEscaper(args
...)
811 // URLQueryEscaper returns the escaped value of the textual representation of
812 // its arguments in a form suitable for embedding in a URL query.
813 func URLQueryEscaper(args
...interface{}) string {
814 return template
.URLQueryEscaper(args
...)