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