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 ...interface{}) 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 ...interface{}) 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 ...interface{}) 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 ...interface{}) 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(bytes.Buffer)
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 b.WriteString(s[written:i])
150 b.WriteString(repl)
151 written = i + w
152 }
153 } else if badRunes {
154
155
156 } else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff {
157 fmt.Fprintf(b, "%s&#x%x;", s[written:i], r)
158 written = i + w
159 }
160 }
161 if written == 0 {
162 return s
163 }
164 b.WriteString(s[written:])
165 return b.String()
166 }
167
168
169
170 func stripTags(html string) string {
171 var b bytes.Buffer
172 s, c, i, allText := []byte(html), context{}, 0, true
173
174
175 for i != len(s) {
176 if c.delim == delimNone {
177 st := c.state
178
179 if c.element != elementNone && !isInTag(st) {
180 st = stateRCDATA
181 }
182 d, nread := transitionFunc[st](c, s[i:])
183 i1 := i + nread
184 if c.state == stateText || c.state == stateRCDATA {
185
186 j := i1
187 if d.state != c.state {
188 for j1 := j - 1; j1 >= i; j1-- {
189 if s[j1] == '<' {
190 j = j1
191 break
192 }
193 }
194 }
195 b.Write(s[i:j])
196 } else {
197 allText = false
198 }
199 c, i = d, i1
200 continue
201 }
202 i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim])
203 if i1 < i {
204 break
205 }
206 if c.delim != delimSpaceOrTagEnd {
207
208 i1++
209 }
210 c, i = context{state: stateTag, element: c.element}, i1
211 }
212 if allText {
213 return html
214 } else if c.state == stateText || c.state == stateRCDATA {
215 b.Write(s[i:])
216 }
217 return b.String()
218 }
219
220
221
222 func htmlNameFilter(args ...interface{}) string {
223 s, t := stringify(args...)
224 if t == contentTypeHTMLAttr {
225 return s
226 }
227 if len(s) == 0 {
228
229
230
231
232
233 return filterFailsafe
234 }
235 s = strings.ToLower(s)
236 if t := attrType(s); t != contentTypePlain {
237
238
239 return filterFailsafe
240 }
241 for _, r := range s {
242 switch {
243 case '0' <= r && r <= '9':
244 case 'a' <= r && r <= 'z':
245 default:
246 return filterFailsafe
247 }
248 }
249 return s
250 }
251
252
253
254
255
256
257
258 func commentEscaper(args ...interface{}) string {
259 return ""
260 }
261
View as plain text