1
2
3
4
5 package template
6
7 import (
8 "bytes"
9 "fmt"
10 "html"
11 "internal/godebug"
12 "io"
13 "maps"
14 "regexp"
15 "text/template"
16 "text/template/parse"
17 )
18
19
20
21
22
23
24 func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
25 c, _ := tmpl.esc.escapeTree(context{}, node, name, 0)
26 var err error
27 if c.err != nil {
28 err, c.err.Name = c.err, name
29 } else if c.state != stateText {
30 err = &Error{ErrEndContext, nil, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)}
31 }
32 if err != nil {
33
34 if t := tmpl.set[name]; t != nil {
35 t.escapeErr = err
36 t.text.Tree = nil
37 t.Tree = nil
38 }
39 return err
40 }
41 tmpl.esc.commit()
42 if t := tmpl.set[name]; t != nil {
43 t.escapeErr = escapeOK
44 t.Tree = t.text.Tree
45 }
46 return nil
47 }
48
49
50
51 func evalArgs(args ...any) string {
52
53 if len(args) == 1 {
54 if s, ok := args[0].(string); ok {
55 return s
56 }
57 }
58 for i, arg := range args {
59 args[i] = indirectToStringerOrError(arg)
60 }
61 return fmt.Sprint(args...)
62 }
63
64
65 var funcMap = template.FuncMap{
66 "_html_template_attrescaper": attrEscaper,
67 "_html_template_commentescaper": commentEscaper,
68 "_html_template_cssescaper": cssEscaper,
69 "_html_template_cssvaluefilter": cssValueFilter,
70 "_html_template_htmlnamefilter": htmlNameFilter,
71 "_html_template_htmlescaper": htmlEscaper,
72 "_html_template_jsregexpescaper": jsRegexpEscaper,
73 "_html_template_jsstrescaper": jsStrEscaper,
74 "_html_template_jstmpllitescaper": jsTmplLitEscaper,
75 "_html_template_jsvalescaper": jsValEscaper,
76 "_html_template_nospaceescaper": htmlNospaceEscaper,
77 "_html_template_rcdataescaper": rcdataEscaper,
78 "_html_template_srcsetescaper": srcsetFilterAndEscaper,
79 "_html_template_urlescaper": urlEscaper,
80 "_html_template_urlfilter": urlFilter,
81 "_html_template_urlnormalizer": urlNormalizer,
82 "_eval_args_": evalArgs,
83 }
84
85
86
87 type escaper struct {
88
89 ns *nameSpace
90
91
92 output map[string]context
93
94
95 derived map[string]*template.Template
96
97 called map[string]bool
98
99
100
101 actionNodeEdits map[*parse.ActionNode][]string
102 templateNodeEdits map[*parse.TemplateNode]string
103 textNodeEdits map[*parse.TextNode][]byte
104
105 rangeContext *rangeContext
106 }
107
108
109 type rangeContext struct {
110 outer *rangeContext
111 breaks []context
112 continues []context
113 }
114
115
116 func makeEscaper(n *nameSpace) escaper {
117 return escaper{
118 n,
119 map[string]context{},
120 map[string]*template.Template{},
121 map[string]bool{},
122 map[*parse.ActionNode][]string{},
123 map[*parse.TemplateNode]string{},
124 map[*parse.TextNode][]byte{},
125 nil,
126 }
127 }
128
129
130
131
132
133
134 const filterFailsafe = "ZgotmplZ"
135
136
137 func (e *escaper) escape(c context, n parse.Node) context {
138 switch n := n.(type) {
139 case *parse.ActionNode:
140 return e.escapeAction(c, n)
141 case *parse.BreakNode:
142 c.n = n
143 e.rangeContext.breaks = append(e.rangeContext.breaks, c)
144 return context{state: stateDead}
145 case *parse.CommentNode:
146 return c
147 case *parse.ContinueNode:
148 c.n = n
149 e.rangeContext.continues = append(e.rangeContext.continues, c)
150 return context{state: stateDead}
151 case *parse.IfNode:
152 return e.escapeBranch(c, &n.BranchNode, "if")
153 case *parse.ListNode:
154 return e.escapeList(c, n)
155 case *parse.RangeNode:
156 return e.escapeBranch(c, &n.BranchNode, "range")
157 case *parse.TemplateNode:
158 return e.escapeTemplate(c, n)
159 case *parse.TextNode:
160 return e.escapeText(c, n)
161 case *parse.WithNode:
162 return e.escapeBranch(c, &n.BranchNode, "with")
163 }
164 panic("escaping " + n.String() + " is unimplemented")
165 }
166
167 var debugAllowActionJSTmpl = godebug.New("jstmpllitinterp")
168
169
170 func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
171 if len(n.Pipe.Decl) != 0 {
172
173 return c
174 }
175 c = nudge(c)
176
177 for pos, idNode := range n.Pipe.Cmds {
178 node, ok := idNode.Args[0].(*parse.IdentifierNode)
179 if !ok {
180
181
182
183
184
185
186
187 continue
188 }
189 ident := node.Ident
190 if _, ok := predefinedEscapers[ident]; ok {
191 if pos < len(n.Pipe.Cmds)-1 ||
192 c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" {
193 return context{
194 state: stateError,
195 err: errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident),
196 }
197 }
198 }
199 }
200 s := make([]string, 0, 3)
201 switch c.state {
202 case stateError:
203 return c
204 case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
205 switch c.urlPart {
206 case urlPartNone:
207 s = append(s, "_html_template_urlfilter")
208 fallthrough
209 case urlPartPreQuery:
210 switch c.state {
211 case stateCSSDqStr, stateCSSSqStr:
212 s = append(s, "_html_template_cssescaper")
213 default:
214 s = append(s, "_html_template_urlnormalizer")
215 }
216 case urlPartQueryOrFrag:
217 s = append(s, "_html_template_urlescaper")
218 case urlPartUnknown:
219 return context{
220 state: stateError,
221 err: errorf(ErrAmbigContext, n, n.Line, "%s appears in an ambiguous context within a URL", n),
222 }
223 default:
224 panic(c.urlPart.String())
225 }
226 case stateJS:
227 s = append(s, "_html_template_jsvalescaper")
228
229 c.jsCtx = jsCtxDivOp
230 case stateJSDqStr, stateJSSqStr:
231 s = append(s, "_html_template_jsstrescaper")
232 case stateJSTmplLit:
233 s = append(s, "_html_template_jstmpllitescaper")
234 case stateJSRegexp:
235 s = append(s, "_html_template_jsregexpescaper")
236 case stateCSS:
237 s = append(s, "_html_template_cssvaluefilter")
238 case stateText:
239 s = append(s, "_html_template_htmlescaper")
240 case stateRCDATA:
241 s = append(s, "_html_template_rcdataescaper")
242 case stateAttr:
243
244 case stateAttrName, stateTag:
245 c.state = stateAttrName
246 s = append(s, "_html_template_htmlnamefilter")
247 case stateSrcset:
248 s = append(s, "_html_template_srcsetescaper")
249 default:
250 if isComment(c.state) {
251 s = append(s, "_html_template_commentescaper")
252 } else {
253 panic("unexpected state " + c.state.String())
254 }
255 }
256 switch c.delim {
257 case delimNone:
258
259 case delimSpaceOrTagEnd:
260 s = append(s, "_html_template_nospaceescaper")
261 default:
262 s = append(s, "_html_template_attrescaper")
263 }
264 e.editActionNode(n, s)
265 return c
266 }
267
268
269
270
271 func ensurePipelineContains(p *parse.PipeNode, s []string) {
272 if len(s) == 0 {
273
274 return
275 }
276
277
278
279 pipelineLen := len(p.Cmds)
280 if pipelineLen > 0 {
281 lastCmd := p.Cmds[pipelineLen-1]
282 if idNode, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok {
283 if esc := idNode.Ident; predefinedEscapers[esc] {
284
285 if len(p.Cmds) == 1 && len(lastCmd.Args) > 1 {
286
287
288
289
290
291 lastCmd.Args[0] = parse.NewIdentifier("_eval_args_").SetTree(nil).SetPos(lastCmd.Args[0].Position())
292 p.Cmds = appendCmd(p.Cmds, newIdentCmd(esc, p.Position()))
293 pipelineLen++
294 }
295
296
297 dup := false
298 for i, escaper := range s {
299 if escFnsEq(esc, escaper) {
300 s[i] = idNode.Ident
301 dup = true
302 }
303 }
304 if dup {
305
306
307 pipelineLen--
308 }
309 }
310 }
311 }
312
313 newCmds := make([]*parse.CommandNode, pipelineLen, pipelineLen+len(s))
314 insertedIdents := make(map[string]bool)
315 for i := 0; i < pipelineLen; i++ {
316 cmd := p.Cmds[i]
317 newCmds[i] = cmd
318 if idNode, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
319 insertedIdents[normalizeEscFn(idNode.Ident)] = true
320 }
321 }
322 for _, name := range s {
323 if !insertedIdents[normalizeEscFn(name)] {
324
325
326
327
328 newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
329 }
330 }
331 p.Cmds = newCmds
332 }
333
334
335
336 var predefinedEscapers = map[string]bool{
337 "html": true,
338 "urlquery": true,
339 }
340
341
342
343 var equivEscapers = map[string]string{
344
345
346 "_html_template_attrescaper": "html",
347 "_html_template_htmlescaper": "html",
348 "_html_template_rcdataescaper": "html",
349
350
351
352 "_html_template_urlescaper": "urlquery",
353
354
355
356
357
358
359 "_html_template_urlnormalizer": "urlquery",
360 }
361
362
363 func escFnsEq(a, b string) bool {
364 return normalizeEscFn(a) == normalizeEscFn(b)
365 }
366
367
368
369 func normalizeEscFn(e string) string {
370 if norm := equivEscapers[e]; norm != "" {
371 return norm
372 }
373 return e
374 }
375
376
377
378 var redundantFuncs = map[string]map[string]bool{
379 "_html_template_commentescaper": {
380 "_html_template_attrescaper": true,
381 "_html_template_htmlescaper": true,
382 },
383 "_html_template_cssescaper": {
384 "_html_template_attrescaper": true,
385 },
386 "_html_template_jsregexpescaper": {
387 "_html_template_attrescaper": true,
388 },
389 "_html_template_jsstrescaper": {
390 "_html_template_attrescaper": true,
391 },
392 "_html_template_jstmpllitescaper": {
393 "_html_template_attrescaper": true,
394 },
395 "_html_template_urlescaper": {
396 "_html_template_urlnormalizer": true,
397 },
398 }
399
400
401
402 func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode {
403 if n := len(cmds); n != 0 {
404 last, okLast := cmds[n-1].Args[0].(*parse.IdentifierNode)
405 next, okNext := cmd.Args[0].(*parse.IdentifierNode)
406 if okLast && okNext && redundantFuncs[last.Ident][next.Ident] {
407 return cmds
408 }
409 }
410 return append(cmds, cmd)
411 }
412
413
414 func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode {
415 return &parse.CommandNode{
416 NodeType: parse.NodeCommand,
417 Args: []parse.Node{parse.NewIdentifier(identifier).SetTree(nil).SetPos(pos)},
418 }
419 }
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439 func nudge(c context) context {
440 switch c.state {
441 case stateTag:
442
443 c.state = stateAttrName
444 case stateBeforeValue:
445
446 c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone
447 case stateAfterName:
448
449 c.state, c.attr = stateAttrName, attrNone
450 }
451 return c
452 }
453
454
455
456
457 func join(a, b context, node parse.Node, nodeName string) context {
458 if a.state == stateError {
459 return a
460 }
461 if b.state == stateError {
462 return b
463 }
464 if a.state == stateDead {
465 return b
466 }
467 if b.state == stateDead {
468 return a
469 }
470 if a.eq(b) {
471 return a
472 }
473
474 c := a
475 c.urlPart = b.urlPart
476 if c.eq(b) {
477
478 c.urlPart = urlPartUnknown
479 return c
480 }
481
482 c = a
483 c.jsCtx = b.jsCtx
484 if c.eq(b) {
485
486 c.jsCtx = jsCtxUnknown
487 return c
488 }
489
490
491
492
493
494
495 if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) {
496 if e := join(c, d, node, nodeName); e.state != stateError {
497 return e
498 }
499 }
500
501 return context{
502 state: stateError,
503 err: errorf(ErrBranchEnd, node, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b),
504 }
505 }
506
507
508 func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
509 if nodeName == "range" {
510 e.rangeContext = &rangeContext{outer: e.rangeContext}
511 }
512 c0 := e.escapeList(c, n.List)
513 if nodeName == "range" {
514 if c0.state != stateError {
515 c0 = joinRange(c0, e.rangeContext)
516 }
517 e.rangeContext = e.rangeContext.outer
518 if c0.state == stateError {
519 return c0
520 }
521
522
523
524
525 e.rangeContext = &rangeContext{outer: e.rangeContext}
526 c1, _ := e.escapeListConditionally(c0, n.List, nil)
527 c0 = join(c0, c1, n, nodeName)
528 if c0.state == stateError {
529 e.rangeContext = e.rangeContext.outer
530
531
532
533 c0.err.Line = n.Line
534 c0.err.Description = "on range loop re-entry: " + c0.err.Description
535 return c0
536 }
537 c0 = joinRange(c0, e.rangeContext)
538 e.rangeContext = e.rangeContext.outer
539 if c0.state == stateError {
540 return c0
541 }
542 }
543 c1 := e.escapeList(c, n.ElseList)
544 return join(c0, c1, n, nodeName)
545 }
546
547 func joinRange(c0 context, rc *rangeContext) context {
548
549
550
551 for _, c := range rc.breaks {
552 c0 = join(c0, c, c.n, "range")
553 if c0.state == stateError {
554 c0.err.Line = c.n.(*parse.BreakNode).Line
555 c0.err.Description = "at range loop break: " + c0.err.Description
556 return c0
557 }
558 }
559 for _, c := range rc.continues {
560 c0 = join(c0, c, c.n, "range")
561 if c0.state == stateError {
562 c0.err.Line = c.n.(*parse.ContinueNode).Line
563 c0.err.Description = "at range loop continue: " + c0.err.Description
564 return c0
565 }
566 }
567 return c0
568 }
569
570
571 func (e *escaper) escapeList(c context, n *parse.ListNode) context {
572 if n == nil {
573 return c
574 }
575 for _, m := range n.Nodes {
576 c = e.escape(c, m)
577 if c.state == stateDead {
578 break
579 }
580 }
581 return c
582 }
583
584
585
586
587
588 func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
589 e1 := makeEscaper(e.ns)
590 e1.rangeContext = e.rangeContext
591
592 maps.Copy(e1.output, e.output)
593 c = e1.escapeList(c, n)
594 ok := filter != nil && filter(&e1, c)
595 if ok {
596
597 maps.Copy(e.output, e1.output)
598 maps.Copy(e.derived, e1.derived)
599 maps.Copy(e.called, e1.called)
600 for k, v := range e1.actionNodeEdits {
601 e.editActionNode(k, v)
602 }
603 for k, v := range e1.templateNodeEdits {
604 e.editTemplateNode(k, v)
605 }
606 for k, v := range e1.textNodeEdits {
607 e.editTextNode(k, v)
608 }
609 }
610 return c, ok
611 }
612
613
614 func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context {
615 c, name := e.escapeTree(c, n, n.Name, n.Line)
616 if name != n.Name {
617 e.editTemplateNode(n, name)
618 }
619 return c
620 }
621
622
623
624 func (e *escaper) escapeTree(c context, node parse.Node, name string, line int) (context, string) {
625
626
627 dname := c.mangle(name)
628 e.called[dname] = true
629 if out, ok := e.output[dname]; ok {
630
631 return out, dname
632 }
633 t := e.template(name)
634 if t == nil {
635
636
637 if e.ns.set[name] != nil {
638 return context{
639 state: stateError,
640 err: errorf(ErrNoSuchTemplate, node, line, "%q is an incomplete or empty template", name),
641 }, dname
642 }
643 return context{
644 state: stateError,
645 err: errorf(ErrNoSuchTemplate, node, line, "no such template %q", name),
646 }, dname
647 }
648 if dname != name {
649
650
651 dt := e.template(dname)
652 if dt == nil {
653 dt = template.New(dname)
654 dt.Tree = &parse.Tree{Name: dname, Root: t.Root.CopyList()}
655 e.derived[dname] = dt
656 }
657 t = dt
658 }
659 return e.computeOutCtx(c, t), dname
660 }
661
662
663
664 func (e *escaper) computeOutCtx(c context, t *template.Template) context {
665
666 c1, ok := e.escapeTemplateBody(c, t)
667 if !ok {
668
669 if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 {
670 c1, ok = c2, true
671 }
672
673 }
674 if !ok && c1.state != stateError {
675 return context{
676 state: stateError,
677 err: errorf(ErrOutputContext, t.Tree.Root, 0, "cannot compute output context for template %s", t.Name()),
678 }
679 }
680 return c1
681 }
682
683
684
685
686 func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) {
687 filter := func(e1 *escaper, c1 context) bool {
688 if c1.state == stateError {
689
690 return false
691 }
692 if !e1.called[t.Name()] {
693
694
695 return true
696 }
697
698 return c.eq(c1)
699 }
700
701
702
703
704 e.output[t.Name()] = c
705 return e.escapeListConditionally(c, t.Tree.Root, filter)
706 }
707
708
709 var delimEnds = [...]string{
710 delimDoubleQuote: `"`,
711 delimSingleQuote: "'",
712
713
714
715
716
717
718
719 delimSpaceOrTagEnd: " \t\n\f\r>",
720 }
721
722 var (
723
724
725
726
727
728
729
730 specialScriptTagRE = regexp.MustCompile("(?i)<(script|/script|!--)")
731 specialScriptTagReplacement = []byte("\\x3C$1")
732 )
733
734 func containsSpecialScriptTag(s []byte) bool {
735 return specialScriptTagRE.Match(s)
736 }
737
738 func escapeSpecialScriptTags(s []byte) []byte {
739 return specialScriptTagRE.ReplaceAll(s, specialScriptTagReplacement)
740 }
741
742 var doctypeBytes = []byte("<!DOCTYPE")
743
744
745 func (e *escaper) escapeText(c context, n *parse.TextNode) context {
746 s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
747 for i != len(s) {
748 c1, nread := contextAfterText(c, s[i:])
749 i1 := i + nread
750 if c.state == stateText || c.state == stateRCDATA {
751 end := i1
752 if c1.state != c.state {
753 for j := end - 1; j >= i; j-- {
754 if s[j] == '<' {
755 end = j
756 break
757 }
758 }
759 }
760 for j := i; j < end; j++ {
761 if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) {
762 b.Write(s[written:j])
763 b.WriteString("<")
764 written = j + 1
765 }
766 }
767 } else if isComment(c.state) && c.delim == delimNone {
768 switch c.state {
769 case stateJSBlockCmt:
770
771
772
773
774
775
776
777 if bytes.ContainsAny(s[written:i1], "\n\r\u2028\u2029") {
778 b.WriteByte('\n')
779 } else {
780 b.WriteByte(' ')
781 }
782 case stateCSSBlockCmt:
783 b.WriteByte(' ')
784 }
785 written = i1
786 }
787 if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone {
788
789 cs := i1 - 2
790 if c1.state == stateHTMLCmt || c1.state == stateJSHTMLOpenCmt {
791
792 cs -= 2
793 } else if c1.state == stateJSHTMLCloseCmt {
794
795 cs -= 1
796 }
797 b.Write(s[written:cs])
798 written = i1
799 }
800 if isInScriptLiteral(c.state) && containsSpecialScriptTag(s[i:i1]) {
801 b.Write(s[written:i])
802 b.Write(escapeSpecialScriptTags(s[i:i1]))
803 written = i1
804 }
805 if i == i1 && c.state == c1.state {
806 panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:]))
807 }
808 c, i = c1, i1
809 }
810
811 if written != 0 && c.state != stateError {
812 if !isComment(c.state) || c.delim != delimNone {
813 b.Write(n.Text[written:])
814 }
815 e.editTextNode(n, b.Bytes())
816 }
817 return c
818 }
819
820
821
822 func contextAfterText(c context, s []byte) (context, int) {
823 if c.delim == delimNone {
824 c1, i := tSpecialTagEnd(c, s)
825 if i == 0 {
826
827
828 return c1, 0
829 }
830
831 return transitionFunc[c.state](c, s[:i])
832 }
833
834
835
836 i := bytes.IndexAny(s, delimEnds[c.delim])
837 if i == -1 {
838 i = len(s)
839 }
840 if c.delim == delimSpaceOrTagEnd {
841
842
843
844
845
846
847
848 if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 {
849 return context{
850 state: stateError,
851 err: errorf(ErrBadHTML, nil, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]),
852 }, len(s)
853 }
854 }
855 if i == len(s) {
856
857
858
859
860 for u := []byte(html.UnescapeString(string(s))); len(u) != 0; {
861 c1, i1 := transitionFunc[c.state](c, u)
862 c, u = c1, u[i1:]
863 }
864 return c, len(s)
865 }
866
867 element := c.element
868
869
870 if c.state == stateAttr && c.element == elementScript && c.attr == attrScriptType && !isJSType(string(s[:i])) {
871 element = elementNone
872 }
873
874 if c.delim != delimSpaceOrTagEnd {
875
876 i++
877 }
878
879
880 return context{state: stateTag, element: element}, i
881 }
882
883
884 func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) {
885 if _, ok := e.actionNodeEdits[n]; ok {
886 panic(fmt.Sprintf("node %s shared between templates", n))
887 }
888 e.actionNodeEdits[n] = cmds
889 }
890
891
892 func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) {
893 if _, ok := e.templateNodeEdits[n]; ok {
894 panic(fmt.Sprintf("node %s shared between templates", n))
895 }
896 e.templateNodeEdits[n] = callee
897 }
898
899
900 func (e *escaper) editTextNode(n *parse.TextNode, text []byte) {
901 if _, ok := e.textNodeEdits[n]; ok {
902 panic(fmt.Sprintf("node %s shared between templates", n))
903 }
904 e.textNodeEdits[n] = text
905 }
906
907
908
909 func (e *escaper) commit() {
910 for name := range e.output {
911 e.template(name).Funcs(funcMap)
912 }
913
914
915 tmpl := e.arbitraryTemplate()
916 for _, t := range e.derived {
917 if _, err := tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
918 panic("error adding derived template")
919 }
920 }
921 for n, s := range e.actionNodeEdits {
922 ensurePipelineContains(n.Pipe, s)
923 }
924 for n, name := range e.templateNodeEdits {
925 n.Name = name
926 }
927 for n, s := range e.textNodeEdits {
928 n.Text = s
929 }
930
931
932 e.called = make(map[string]bool)
933 e.actionNodeEdits = make(map[*parse.ActionNode][]string)
934 e.templateNodeEdits = make(map[*parse.TemplateNode]string)
935 e.textNodeEdits = make(map[*parse.TextNode][]byte)
936 }
937
938
939 func (e *escaper) template(name string) *template.Template {
940
941
942 t := e.arbitraryTemplate().text.Lookup(name)
943 if t == nil {
944 t = e.derived[name]
945 }
946 return t
947 }
948
949
950
951 func (e *escaper) arbitraryTemplate() *Template {
952 for _, t := range e.ns.set {
953 return t
954 }
955 panic("no templates in name space")
956 }
957
958
959
960
961
962 func HTMLEscape(w io.Writer, b []byte) {
963 template.HTMLEscape(w, b)
964 }
965
966
967 func HTMLEscapeString(s string) string {
968 return template.HTMLEscapeString(s)
969 }
970
971
972
973 func HTMLEscaper(args ...any) string {
974 return template.HTMLEscaper(args...)
975 }
976
977
978 func JSEscape(w io.Writer, b []byte) {
979 template.JSEscape(w, b)
980 }
981
982
983 func JSEscapeString(s string) string {
984 return template.JSEscapeString(s)
985 }
986
987
988
989 func JSEscaper(args ...any) string {
990 return template.JSEscaper(args...)
991 }
992
993
994
995 func URLQueryEscaper(args ...any) string {
996 return template.URLQueryEscaper(args...)
997 }
998
View as plain text