]> Cypherpunks.ru repositories - gostls13.git/blob - src/html/template/error.go
html/template: support parsing complex JS template literals
[gostls13.git] / src / html / template / error.go
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         "fmt"
9         "text/template/parse"
10 )
11
12 // Error describes a problem encountered during template Escaping.
13 type Error struct {
14         // ErrorCode describes the kind of error.
15         ErrorCode ErrorCode
16         // Node is the node that caused the problem, if known.
17         // If not nil, it overrides Name and Line.
18         Node parse.Node
19         // Name is the name of the template in which the error was encountered.
20         Name string
21         // Line is the line number of the error in the template source or 0.
22         Line int
23         // Description is a human-readable description of the problem.
24         Description string
25 }
26
27 // ErrorCode is a code for a kind of error.
28 type ErrorCode int
29
30 // We define codes for each error that manifests while escaping templates, but
31 // escaped templates may also fail at runtime.
32 //
33 // Output: "ZgotmplZ"
34 // Example:
35 //
36 //      <img src="{{.X}}">
37 //      where {{.X}} evaluates to `javascript:...`
38 //
39 // Discussion:
40 //
41 //      "ZgotmplZ" is a special value that indicates that unsafe content reached a
42 //      CSS or URL context at runtime. The output of the example will be
43 //        <img src="#ZgotmplZ">
44 //      If the data comes from a trusted source, use content types to exempt it
45 //      from filtering: URL(`javascript:...`).
46 const (
47         // OK indicates the lack of an error.
48         OK ErrorCode = iota
49
50         // ErrAmbigContext: "... appears in an ambiguous context within a URL"
51         // Example:
52         //   <a href="
53         //      {{if .C}}
54         //        /path/
55         //      {{else}}
56         //        /search?q=
57         //      {{end}}
58         //      {{.X}}
59         //   ">
60         // Discussion:
61         //   {{.X}} is in an ambiguous URL context since, depending on {{.C}},
62         //  it may be either a URL suffix or a query parameter.
63         //   Moving {{.X}} into the condition removes the ambiguity:
64         //   <a href="{{if .C}}/path/{{.X}}{{else}}/search?q={{.X}}">
65         ErrAmbigContext
66
67         // ErrBadHTML: "expected space, attr name, or end of tag, but got ...",
68         //   "... in unquoted attr", "... in attribute name"
69         // Example:
70         //   <a href = /search?q=foo>
71         //   <href=foo>
72         //   <form na<e=...>
73         //   <option selected<
74         // Discussion:
75         //   This is often due to a typo in an HTML element, but some runes
76         //   are banned in tag names, attribute names, and unquoted attribute
77         //   values because they can tickle parser ambiguities.
78         //   Quoting all attributes is the best policy.
79         ErrBadHTML
80
81         // ErrBranchEnd: "{{if}} branches end in different contexts"
82         // Example:
83         //   {{if .C}}<a href="{{end}}{{.X}}
84         // Discussion:
85         //   Package html/template statically examines each path through an
86         //   {{if}}, {{range}}, or {{with}} to escape any following pipelines.
87         //   The example is ambiguous since {{.X}} might be an HTML text node,
88         //   or a URL prefix in an HTML attribute. The context of {{.X}} is
89         //   used to figure out how to escape it, but that context depends on
90         //   the run-time value of {{.C}} which is not statically known.
91         //
92         //   The problem is usually something like missing quotes or angle
93         //   brackets, or can be avoided by refactoring to put the two contexts
94         //   into different branches of an if, range or with. If the problem
95         //   is in a {{range}} over a collection that should never be empty,
96         //   adding a dummy {{else}} can help.
97         ErrBranchEnd
98
99         // ErrEndContext: "... ends in a non-text context: ..."
100         // Examples:
101         //   <div
102         //   <div title="no close quote>
103         //   <script>f()
104         // Discussion:
105         //   Executed templates should produce a DocumentFragment of HTML.
106         //   Templates that end without closing tags will trigger this error.
107         //   Templates that should not be used in an HTML context or that
108         //   produce incomplete Fragments should not be executed directly.
109         //
110         //   {{define "main"}} <script>{{template "helper"}}</script> {{end}}
111         //   {{define "helper"}} document.write(' <div title=" ') {{end}}
112         //
113         //   "helper" does not produce a valid document fragment, so should
114         //   not be Executed directly.
115         ErrEndContext
116
117         // ErrNoSuchTemplate: "no such template ..."
118         // Examples:
119         //   {{define "main"}}<div {{template "attrs"}}>{{end}}
120         //   {{define "attrs"}}href="{{.URL}}"{{end}}
121         // Discussion:
122         //   Package html/template looks through template calls to compute the
123         //   context.
124         //   Here the {{.URL}} in "attrs" must be treated as a URL when called
125         //   from "main", but you will get this error if "attrs" is not defined
126         //   when "main" is parsed.
127         ErrNoSuchTemplate
128
129         // ErrOutputContext: "cannot compute output context for template ..."
130         // Examples:
131         //   {{define "t"}}{{if .T}}{{template "t" .T}}{{end}}{{.H}}",{{end}}
132         // Discussion:
133         //   A recursive template does not end in the same context in which it
134         //   starts, and a reliable output context cannot be computed.
135         //   Look for typos in the named template.
136         //   If the template should not be called in the named start context,
137         //   look for calls to that template in unexpected contexts.
138         //   Maybe refactor recursive templates to not be recursive.
139         ErrOutputContext
140
141         // ErrPartialCharset: "unfinished JS regexp charset in ..."
142         // Example:
143         //     <script>var pattern = /foo[{{.Chars}}]/</script>
144         // Discussion:
145         //   Package html/template does not support interpolation into regular
146         //   expression literal character sets.
147         ErrPartialCharset
148
149         // ErrPartialEscape: "unfinished escape sequence in ..."
150         // Example:
151         //   <script>alert("\{{.X}}")</script>
152         // Discussion:
153         //   Package html/template does not support actions following a
154         //   backslash.
155         //   This is usually an error and there are better solutions; for
156         //   example
157         //     <script>alert("{{.X}}")</script>
158         //   should work, and if {{.X}} is a partial escape sequence such as
159         //   "xA0", mark the whole sequence as safe content: JSStr(`\xA0`)
160         ErrPartialEscape
161
162         // ErrRangeLoopReentry: "on range loop re-entry: ..."
163         // Example:
164         //   <script>var x = [{{range .}}'{{.}},{{end}}]</script>
165         // Discussion:
166         //   If an iteration through a range would cause it to end in a
167         //   different context than an earlier pass, there is no single context.
168         //   In the example, there is missing a quote, so it is not clear
169         //   whether {{.}} is meant to be inside a JS string or in a JS value
170         //   context. The second iteration would produce something like
171         //
172         //     <script>var x = ['firstValue,'secondValue]</script>
173         ErrRangeLoopReentry
174
175         // ErrSlashAmbig: '/' could start a division or regexp.
176         // Example:
177         //   <script>
178         //     {{if .C}}var x = 1{{end}}
179         //     /-{{.N}}/i.test(x) ? doThis : doThat();
180         //   </script>
181         // Discussion:
182         //   The example above could produce `var x = 1/-2/i.test(s)...`
183         //   in which the first '/' is a mathematical division operator or it
184         //   could produce `/-2/i.test(s)` in which the first '/' starts a
185         //   regexp literal.
186         //   Look for missing semicolons inside branches, and maybe add
187         //   parentheses to make it clear which interpretation you intend.
188         ErrSlashAmbig
189
190         // ErrPredefinedEscaper: "predefined escaper ... disallowed in template"
191         // Example:
192         //   <div class={{. | html}}>Hello<div>
193         // Discussion:
194         //   Package html/template already contextually escapes all pipelines to
195         //   produce HTML output safe against code injection. Manually escaping
196         //   pipeline output using the predefined escapers "html" or "urlquery" is
197         //   unnecessary, and may affect the correctness or safety of the escaped
198         //   pipeline output in Go 1.8 and earlier.
199         //
200         //   In most cases, such as the given example, this error can be resolved by
201         //   simply removing the predefined escaper from the pipeline and letting the
202         //   contextual autoescaper handle the escaping of the pipeline. In other
203         //   instances, where the predefined escaper occurs in the middle of a
204         //   pipeline where subsequent commands expect escaped input, e.g.
205         //     {{.X | html | makeALink}}
206         //   where makeALink does
207         //     return `<a href="`+input+`">link</a>`
208         //   consider refactoring the surrounding template to make use of the
209         //   contextual autoescaper, i.e.
210         //     <a href="{{.X}}">link</a>
211         //
212         //   To ease migration to Go 1.9 and beyond, "html" and "urlquery" will
213         //   continue to be allowed as the last command in a pipeline. However, if the
214         //   pipeline occurs in an unquoted attribute value context, "html" is
215         //   disallowed. Avoid using "html" and "urlquery" entirely in new templates.
216         ErrPredefinedEscaper
217
218         // ErrJSTemplate: "... appears in a JS template literal"
219         // Example:
220         //     <script>var tmpl = `{{.Interp}}`</script>
221         // Discussion:
222         //   Package html/template does not support actions inside of JS template
223         //   literals.
224         //
225         // Deprecated: ErrJSTemplate is no longer returned when an action is present
226         // in a JS template literal. Actions inside of JS template literals are now
227         // escaped as expected.
228         ErrJSTemplate
229 )
230
231 func (e *Error) Error() string {
232         switch {
233         case e.Node != nil:
234                 loc, _ := (*parse.Tree)(nil).ErrorContext(e.Node)
235                 return fmt.Sprintf("html/template:%s: %s", loc, e.Description)
236         case e.Line != 0:
237                 return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description)
238         case e.Name != "":
239                 return fmt.Sprintf("html/template:%s: %s", e.Name, e.Description)
240         }
241         return "html/template: " + e.Description
242 }
243
244 // errorf creates an error given a format string f and args.
245 // The template Name still needs to be supplied.
246 func errorf(k ErrorCode, node parse.Node, line int, f string, args ...any) *Error {
247         return &Error{k, node, "", line, fmt.Sprintf(f, args...)}
248 }