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 func isLWS(b byte) bool { return b == ' ' || b == '\t' }
178
179
180
181
182
183 func isCTL(b byte) bool {
184 const del = 0x7f
185 return b < ' ' || b == del
186 }
187
188
189
190
191
192
193
194
195
196
197
198 func ValidHeaderFieldName(v string) bool {
199 if len(v) == 0 {
200 return false
201 }
202 for _, r := range v {
203 if !IsTokenRune(r) {
204 return false
205 }
206 }
207 return true
208 }
209
210
211 func ValidHostHeader(h string) bool {
212
213
214
215
216
217
218
219
220
221
222
223 for i := 0; i < len(h); i++ {
224 if !validHostByte[h[i]] {
225 return false
226 }
227 }
228 return true
229 }
230
231
232 var validHostByte = [256]bool{
233 '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true,
234 '8': true, '9': true,
235
236 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true,
237 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true,
238 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true,
239 'y': true, 'z': true,
240
241 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true,
242 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true,
243 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true,
244 'Y': true, 'Z': true,
245
246 '!': true,
247 '$': true,
248 '%': true,
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 }
266
267
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 func ValidHeaderFieldValue(v string) bool {
305 for i := 0; i < len(v); i++ {
306 b := v[i]
307 if isCTL(b) && !isLWS(b) {
308 return false
309 }
310 }
311 return true
312 }
313
314 func isASCII(s string) bool {
315 for i := 0; i < len(s); i++ {
316 if s[i] >= utf8.RuneSelf {
317 return false
318 }
319 }
320 return true
321 }
322
323
324
325 func PunycodeHostPort(v string) (string, error) {
326 if isASCII(v) {
327 return v, nil
328 }
329
330 host, port, err := net.SplitHostPort(v)
331 if err != nil {
332
333
334
335 host = v
336 port = ""
337 }
338 host, err = idna.ToASCII(host)
339 if err != nil {
340
341
342 return "", err
343 }
344 if port == "" {
345 return host, nil
346 }
347 return net.JoinHostPort(host, port), nil
348 }
349
View as plain text