1
2
3
4
5 package httpguts
6
7 import (
8 "net"
9 "strings"
10 "unicode/utf8"
11
12 "golang.org/x/net/idna"
13 )
14
15 var isTokenTable = [127]bool{
16 '!': true,
17 '#': true,
18 '$': true,
19 '%': true,
20 '&': true,
21 '\'': true,
22 '*': true,
23 '+': true,
24 '-': true,
25 '.': true,
26 '0': true,
27 '1': true,
28 '2': true,
29 '3': true,
30 '4': true,
31 '5': true,
32 '6': true,
33 '7': true,
34 '8': true,
35 '9': true,
36 'A': true,
37 'B': true,
38 'C': true,
39 'D': true,
40 'E': true,
41 'F': true,
42 'G': true,
43 'H': true,
44 'I': true,
45 'J': true,
46 'K': true,
47 'L': true,
48 'M': true,
49 'N': true,
50 'O': true,
51 'P': true,
52 'Q': true,
53 'R': true,
54 'S': true,
55 'T': true,
56 'U': true,
57 'W': true,
58 'V': true,
59 'X': true,
60 'Y': true,
61 'Z': true,
62 '^': true,
63 '_': true,
64 '`': true,
65 'a': true,
66 'b': true,
67 'c': true,
68 'd': true,
69 'e': true,
70 'f': true,
71 'g': true,
72 'h': true,
73 'i': true,
74 'j': true,
75 'k': true,
76 'l': true,
77 'm': true,
78 'n': true,
79 'o': true,
80 'p': true,
81 'q': true,
82 'r': true,
83 's': true,
84 't': true,
85 'u': true,
86 'v': true,
87 'w': true,
88 'x': true,
89 'y': true,
90 'z': true,
91 '|': true,
92 '~': true,
93 }
94
95 func IsTokenRune(r rune) bool {
96 i := int(r)
97 return i < len(isTokenTable) && isTokenTable[i]
98 }
99
100 func isNotToken(r rune) bool {
101 return !IsTokenRune(r)
102 }
103
104
105
106 func HeaderValuesContainsToken(values []string, token string) bool {
107 for _, v := range values {
108 if headerValueContainsToken(v, token) {
109 return true
110 }
111 }
112 return false
113 }
114
115
116
117 func isOWS(b byte) bool { return b == ' ' || b == '\t' }
118
119
120
121 func trimOWS(x string) string {
122
123
124
125
126 for len(x) > 0 && isOWS(x[0]) {
127 x = x[1:]
128 }
129 for len(x) > 0 && isOWS(x[len(x)-1]) {
130 x = x[:len(x)-1]
131 }
132 return x
133 }
134
135
136
137
138
139 func headerValueContainsToken(v string, token string) bool {
140 for comma := strings.IndexByte(v, ','); comma != -1; comma = strings.IndexByte(v, ',') {
141 if tokenEqual(trimOWS(v[:comma]), token) {
142 return true
143 }
144 v = v[comma+1:]
145 }
146 return tokenEqual(trimOWS(v), token)
147 }
148
149
150 func lowerASCII(b byte) byte {
151 if 'A' <= b && b <= 'Z' {
152 return b + ('a' - 'A')
153 }
154 return b
155 }
156
157
158 func tokenEqual(t1, t2 string) bool {
159 if len(t1) != len(t2) {
160 return false
161 }
162 for i, b := range t1 {
163 if b >= utf8.RuneSelf {
164
165 return false
166 }
167 if lowerASCII(byte(b)) != lowerASCII(t2[i]) {
168 return false
169 }
170 }
171 return true
172 }
173
174
175
176
177
178 func isLWS(b byte) bool { return b == ' ' || b == '\t' }
179
180
181
182
183
184
185 func isCTL(b byte) bool {
186 const del = 0x7f
187 return b < ' ' || b == del
188 }
189
190
191
192
193
194
195
196
197
198
199
200
201 func ValidHeaderFieldName(v string) bool {
202 if len(v) == 0 {
203 return false
204 }
205 for _, r := range v {
206 if !IsTokenRune(r) {
207 return false
208 }
209 }
210 return true
211 }
212
213
214 func ValidHostHeader(h string) bool {
215
216
217
218
219
220
221
222
223
224
225
226 for i := 0; i < len(h); i++ {
227 if !validHostByte[h[i]] {
228 return false
229 }
230 }
231 return true
232 }
233
234
235 var validHostByte = [256]bool{
236 '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true,
237 '8': true, '9': true,
238
239 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true,
240 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true,
241 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true,
242 'y': true, 'z': true,
243
244 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true,
245 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true,
246 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true,
247 'Y': true, 'Z': true,
248
249 '!': true,
250 '$': true,
251 '%': true,
252 '&': true,
253 '(': true,
254 ')': true,
255 '*': true,
256 '+': true,
257 ',': true,
258 '-': true,
259 '.': true,
260 ':': true,
261 ';': true,
262 '=': true,
263 '[': true,
264 '\'': true,
265 ']': true,
266 '_': true,
267 '~': true,
268 }
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308 func ValidHeaderFieldValue(v string) bool {
309 for i := 0; i < len(v); i++ {
310 b := v[i]
311 if isCTL(b) && !isLWS(b) {
312 return false
313 }
314 }
315 return true
316 }
317
318 func isASCII(s string) bool {
319 for i := 0; i < len(s); i++ {
320 if s[i] >= utf8.RuneSelf {
321 return false
322 }
323 }
324 return true
325 }
326
327
328
329 func PunycodeHostPort(v string) (string, error) {
330 if isASCII(v) {
331 return v, nil
332 }
333
334 host, port, err := net.SplitHostPort(v)
335 if err != nil {
336
337
338
339 host = v
340 port = ""
341 }
342 host, err = idna.ToASCII(host)
343 if err != nil {
344
345
346 return "", err
347 }
348 if port == "" {
349 return host, nil
350 }
351 return net.JoinHostPort(host, port), nil
352 }
353
View as plain text