1
2
3
4
5 package template
6
7 import (
8 "bytes"
9 "fmt"
10 "strings"
11 "unicode/utf8"
12 )
13
14
15 func htmlNospaceEscaper(args ...any) string {
16 s, t := stringify(args...)
17 if t == contentTypeHTML {
18 return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false)
19 }
20 return htmlReplacer(s, htmlNospaceReplacementTable, false)
21 }
22
23
24 func attrEscaper(args ...any) string {
25 s, t := stringify(args...)
26 if t == contentTypeHTML {
27 return htmlReplacer(stripTags(s), htmlNormReplacementTable, true)
28 }
29 return htmlReplacer(s, htmlReplacementTable, true)
30 }
31
32
33 func rcdataEscaper(args ...any) string {
34 s, t := stringify(args...)
35 if t == contentTypeHTML {
36 return htmlReplacer(s, htmlNormReplacementTable, true)
37 }
38 return htmlReplacer(s, htmlReplacementTable, true)
39 }
40
41
42 func htmlEscaper(args ...any) string {
43 s, t := stringify(args...)
44 if t == contentTypeHTML {
45 return s
46 }
47 return htmlReplacer(s, htmlReplacementTable, true)
48 }
49
50
51
52 var htmlReplacementTable = []string{
53
54
55
56
57
58
59 0: "\uFFFD",
60 '"': """,
61 '&': "&",
62 '\'': "'",
63 '+': "+",
64 '<': "<",
65 '>': ">",
66 }
67
68
69
70 var htmlNormReplacementTable = []string{
71 0: "\uFFFD",
72 '"': """,
73 '\'': "'",
74 '+': "+",
75 '<': "<",
76 '>': ">",
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 var htmlNospaceReplacementTable = []string{
95 0: "�",
96 '\t': "	",
97 '\n': " ",
98 '\v': "",
99 '\f': "",
100 '\r': " ",
101 ' ': " ",
102 '"': """,
103 '&': "&",
104 '\'': "'",
105 '+': "+",
106 '<': "<",
107 '=': "=",
108 '>': ">",
109
110
111
112 '`': "`",
113 }
114
115
116
117 var htmlNospaceNormReplacementTable = []string{
118 0: "�",
119 '\t': "	",
120 '\n': " ",
121 '\v': "",
122 '\f': "",
123 '\r': " ",
124 ' ': " ",
125 '"': """,
126 '\'': "'",
127 '+': "+",
128 '<': "<",
129 '=': "=",
130 '>': ">",
131
132
133
134 '`': "`",
135 }
136
137
138
139 func htmlReplacer(s string, replacementTable []string, badRunes bool) string {
140 written, b := 0, new(strings.Builder)
141 r, w := rune(0), 0
142 for i := 0; i < len(s); i += w {
143
144
145
146 r, w = utf8.DecodeRuneInString(s[i:])
147 if int(r) < len(replacementTable) {
148 if repl := replacementTable[r]; len(repl) != 0 {
149 if written == 0 {
150 b.Grow(len(s))
151 }
152 b.WriteString(s[written:i])
153 b.WriteString(repl)
154 written = i + w
155 }
156 } else if badRunes {
157
158
159 } else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff {
160 if written == 0 {
161 b.Grow(len(s))
162 }
163 fmt.Fprintf(b, "%s&#x%x;", s[written:i], r)
164 written = i + w
165 }
166 }
167 if written == 0 {
168 return s
169 }
170 b.WriteString(s[written:])
171 return b.String()
172 }
173
174
175
176 func stripTags(html string) string {
177 var b bytes.Buffer
178 s, c, i, allText := []byte(html), context{}, 0, true
179
180
181 for i != len(s) {
182 if c.delim == delimNone {
183 st := c.state
184
185 if c.element != elementNone && !isInTag(st) {
186 st = stateRCDATA
187 }
188 d, nread := transitionFunc[st](c, s[i:])
189 i1 := i + nread
190 if c.state == stateText || c.state == stateRCDATA {
191
192 j := i1
193 if d.state != c.state {
194 for j1 := j - 1; j1 >= i; j1-- {
195 if s[j1] == '<' {
196 j = j1
197 break
198 }
199 }
200 }
201 b.Write(s[i:j])
202 } else {
203 allText = false
204 }
205 c, i = d, i1
206 continue
207 }
208 i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim])
209 if i1 < i {
210 break
211 }
212 if c.delim != delimSpaceOrTagEnd {
213
214 i1++
215 }
216 c, i = context{state: stateTag, element: c.element}, i1
217 }
218 if allText {
219 return html
220 } else if c.state == stateText || c.state == stateRCDATA {
221 b.Write(s[i:])
222 }
223 return b.String()
224 }
225
226
227
228 func htmlNameFilter(args ...any) string {
229 s, t := stringify(args...)
230 if t == contentTypeHTMLAttr {
231 return s
232 }
233 if len(s) == 0 {
234
235
236
237
238
239 return filterFailsafe
240 }
241 s = strings.ToLower(s)
242 if t := attrType(s); t != contentTypePlain {
243
244 return filterFailsafe
245 }
246 for _, r := range s {
247 switch {
248 case '0' <= r && r <= '9':
249 case 'a' <= r && r <= 'z':
250 default:
251 return filterFailsafe
252 }
253 }
254 return s
255 }
256
257
258
259
260
261
262
263 func commentEscaper(args ...any) string {
264 return ""
265 }
266
View as plain text