1
2
3
4
5 package template
6
7 import (
8 "bytes"
9 "fmt"
10 "strings"
11 )
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 func urlFilter(args ...interface{}) string {
36 s, t := stringify(args...)
37 if t == contentTypeURL {
38 return s
39 }
40 if !isSafeURL(s) {
41 return "#" + filterFailsafe
42 }
43 return s
44 }
45
46
47
48 func isSafeURL(s string) bool {
49 if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
50
51 protocol := s[:i]
52 if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
53 return false
54 }
55 }
56 return true
57 }
58
59
60
61 func urlEscaper(args ...interface{}) string {
62 return urlProcessor(false, args...)
63 }
64
65
66
67
68
69
70 func urlNormalizer(args ...interface{}) string {
71 return urlProcessor(true, args...)
72 }
73
74
75
76 func urlProcessor(norm bool, args ...interface{}) string {
77 s, t := stringify(args...)
78 if t == contentTypeURL {
79 norm = true
80 }
81 var b bytes.Buffer
82 if processURLOnto(s, norm, &b) {
83 return b.String()
84 }
85 return s
86 }
87
88
89
90 func processURLOnto(s string, norm bool, b *bytes.Buffer) bool {
91 b.Grow(len(s) + 16)
92 written := 0
93
94
95
96
97
98
99 for i, n := 0, len(s); i < n; i++ {
100 c := s[i]
101 switch c {
102
103
104
105
106
107
108 case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
109 if norm {
110 continue
111 }
112
113
114
115
116
117 case '-', '.', '_', '~':
118 continue
119 case '%':
120
121 if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) {
122 continue
123 }
124 default:
125
126 if 'a' <= c && c <= 'z' {
127 continue
128 }
129 if 'A' <= c && c <= 'Z' {
130 continue
131 }
132 if '0' <= c && c <= '9' {
133 continue
134 }
135 }
136 b.WriteString(s[written:i])
137 fmt.Fprintf(b, "%%%02x", c)
138 written = i + 1
139 }
140 b.WriteString(s[written:])
141 return written != 0
142 }
143
144
145
146 func srcsetFilterAndEscaper(args ...interface{}) string {
147 s, t := stringify(args...)
148 switch t {
149 case contentTypeSrcset:
150 return s
151 case contentTypeURL:
152
153
154 var b bytes.Buffer
155 if processURLOnto(s, true, &b) {
156 s = b.String()
157 }
158
159 return strings.ReplaceAll(s, ",", "%2c")
160 }
161
162 var b bytes.Buffer
163 written := 0
164 for i := 0; i < len(s); i++ {
165 if s[i] == ',' {
166 filterSrcsetElement(s, written, i, &b)
167 b.WriteString(",")
168 written = i + 1
169 }
170 }
171 filterSrcsetElement(s, written, len(s), &b)
172 return b.String()
173 }
174
175
176 const htmlSpaceAndASCIIAlnumBytes = "\x00\x36\x00\x00\x01\x00\xff\x03\xfe\xff\xff\x07\xfe\xff\xff\x07"
177
178
179
180 func isHTMLSpace(c byte) bool {
181 return (c <= 0x20) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7)))
182 }
183
184 func isHTMLSpaceOrASCIIAlnum(c byte) bool {
185 return (c < 0x80) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7)))
186 }
187
188 func filterSrcsetElement(s string, left int, right int, b *bytes.Buffer) {
189 start := left
190 for start < right && isHTMLSpace(s[start]) {
191 start++
192 }
193 end := right
194 for i := start; i < right; i++ {
195 if isHTMLSpace(s[i]) {
196 end = i
197 break
198 }
199 }
200 if url := s[start:end]; isSafeURL(url) {
201
202
203 metadataOk := true
204 for i := end; i < right; i++ {
205 if !isHTMLSpaceOrASCIIAlnum(s[i]) {
206 metadataOk = false
207 break
208 }
209 }
210 if metadataOk {
211 b.WriteString(s[left:start])
212 processURLOnto(url, true, b)
213 b.WriteString(s[end:right])
214 return
215 }
216 }
217 b.WriteString("#")
218 b.WriteString(filterFailsafe)
219 }
220
View as plain text