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
95
96 var htmlNospaceReplacementTable = []string{
97 0: "�",
98 '\t': "	",
99 '\n': " ",
100 '\v': "",
101 '\f': "",
102 '\r': " ",
103 ' ': " ",
104 '"': """,
105 '&': "&",
106 '\'': "'",
107 '+': "+",
108 '<': "<",
109 '=': "=",
110 '>': ">",
111
112
113
114 '`': "`",
115 }
116
117
118
119 var htmlNospaceNormReplacementTable = []string{
120 0: "�",
121 '\t': "	",
122 '\n': " ",
123 '\v': "",
124 '\f': "",
125 '\r': " ",
126 ' ': " ",
127 '"': """,
128 '\'': "'",
129 '+': "+",
130 '<': "<",
131 '=': "=",
132 '>': ">",
133
134
135
136 '`': "`",
137 }
138
139
140
141 func htmlReplacer(s string, replacementTable []string, badRunes bool) string {
142 written, b := 0, new(strings.Builder)
143 r, w := rune(0), 0
144 for i := 0; i < len(s); i += w {
145
146
147
148 r, w = utf8.DecodeRuneInString(s[i:])
149 if int(r) < len(replacementTable) {
150 if repl := replacementTable[r]; len(repl) != 0 {
151 if written == 0 {
152 b.Grow(len(s))
153 }
154 b.WriteString(s[written:i])
155 b.WriteString(repl)
156 written = i + w
157 }
158 } else if badRunes {
159
160
161 } else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff {
162 if written == 0 {
163 b.Grow(len(s))
164 }
165 fmt.Fprintf(b, "%s&#x%x;", s[written:i], r)
166 written = i + w
167 }
168 }
169 if written == 0 {
170 return s
171 }
172 b.WriteString(s[written:])
173 return b.String()
174 }
175
176
177
178 func stripTags(html string) string {
179 var b bytes.Buffer
180 s, c, i, allText := []byte(html), context{}, 0, true
181
182
183 for i != len(s) {
184 if c.delim == delimNone {
185 st := c.state
186
187 if c.element != elementNone && !isInTag(st) {
188 st = stateRCDATA
189 }
190 d, nread := transitionFunc[st](c, s[i:])
191 i1 := i + nread
192 if c.state == stateText || c.state == stateRCDATA {
193
194 j := i1
195 if d.state != c.state {
196 for j1 := j - 1; j1 >= i; j1-- {
197 if s[j1] == '<' {
198 j = j1
199 break
200 }
201 }
202 }
203 b.Write(s[i:j])
204 } else {
205 allText = false
206 }
207 c, i = d, i1
208 continue
209 }
210 i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim])
211 if i1 < i {
212 break
213 }
214 if c.delim != delimSpaceOrTagEnd {
215
216 i1++
217 }
218 c, i = context{state: stateTag, element: c.element}, i1
219 }
220 if allText {
221 return html
222 } else if c.state == stateText || c.state == stateRCDATA {
223 b.Write(s[i:])
224 }
225 return b.String()
226 }
227
228
229
230 func htmlNameFilter(args ...any) string {
231 s, t := stringify(args...)
232 if t == contentTypeHTMLAttr {
233 return s
234 }
235 if len(s) == 0 {
236
237
238
239
240
241 return filterFailsafe
242 }
243 s = strings.ToLower(s)
244 if t := attrType(s); t != contentTypePlain {
245
246 return filterFailsafe
247 }
248 for _, r := range s {
249 switch {
250 case '0' <= r && r <= '9':
251 case 'a' <= r && r <= 'z':
252 default:
253 return filterFailsafe
254 }
255 }
256 return s
257 }
258
259
260
261
262
263
264
265 func commentEscaper(args ...any) string {
266 return ""
267 }
268
View as plain text