diff options
Diffstat (limited to 'httputil/header/header.go')
-rw-r--r-- | httputil/header/header.go | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/httputil/header/header.go b/httputil/header/header.go new file mode 100644 index 0000000..0f1572e --- /dev/null +++ b/httputil/header/header.go | |||
@@ -0,0 +1,298 @@ | |||
1 | // Copyright 2013 The Go Authors. All rights reserved. | ||
2 | // | ||
3 | // Use of this source code is governed by a BSD-style | ||
4 | // license that can be found in the LICENSE file or at | ||
5 | // https://developers.google.com/open-source/licenses/bsd. | ||
6 | |||
7 | // Package header provides functions for parsing HTTP headers. | ||
8 | package header | ||
9 | |||
10 | import ( | ||
11 | "net/http" | ||
12 | "strings" | ||
13 | "time" | ||
14 | ) | ||
15 | |||
16 | // Octet types from RFC 2616. | ||
17 | var octetTypes [256]octetType | ||
18 | |||
19 | type octetType byte | ||
20 | |||
21 | const ( | ||
22 | isToken octetType = 1 << iota | ||
23 | isSpace | ||
24 | ) | ||
25 | |||
26 | func init() { | ||
27 | // OCTET = <any 8-bit sequence of data> | ||
28 | // CHAR = <any US-ASCII character (octets 0 - 127)> | ||
29 | // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> | ||
30 | // CR = <US-ASCII CR, carriage return (13)> | ||
31 | // LF = <US-ASCII LF, linefeed (10)> | ||
32 | // SP = <US-ASCII SP, space (32)> | ||
33 | // HT = <US-ASCII HT, horizontal-tab (9)> | ||
34 | // <"> = <US-ASCII double-quote mark (34)> | ||
35 | // CRLF = CR LF | ||
36 | // LWS = [CRLF] 1*( SP | HT ) | ||
37 | // TEXT = <any OCTET except CTLs, but including LWS> | ||
38 | // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | ||
39 | // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT | ||
40 | // token = 1*<any CHAR except CTLs or separators> | ||
41 | // qdtext = <any TEXT except <">> | ||
42 | |||
43 | for c := 0; c < 256; c++ { | ||
44 | var t octetType | ||
45 | isCtl := c <= 31 || c == 127 | ||
46 | isChar := 0 <= c && c <= 127 | ||
47 | isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 | ||
48 | if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { | ||
49 | t |= isSpace | ||
50 | } | ||
51 | if isChar && !isCtl && !isSeparator { | ||
52 | t |= isToken | ||
53 | } | ||
54 | octetTypes[c] = t | ||
55 | } | ||
56 | } | ||
57 | |||
58 | // Copy returns a shallow copy of the header. | ||
59 | func Copy(header http.Header) http.Header { | ||
60 | h := make(http.Header) | ||
61 | for k, vs := range header { | ||
62 | h[k] = vs | ||
63 | } | ||
64 | return h | ||
65 | } | ||
66 | |||
67 | var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC} | ||
68 | |||
69 | // ParseTime parses the header as time. The zero value is returned if the | ||
70 | // header is not present or there is an error parsing the | ||
71 | // header. | ||
72 | func ParseTime(header http.Header, key string) time.Time { | ||
73 | if s := header.Get(key); s != "" { | ||
74 | for _, layout := range timeLayouts { | ||
75 | if t, err := time.Parse(layout, s); err == nil { | ||
76 | return t.UTC() | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | return time.Time{} | ||
81 | } | ||
82 | |||
83 | // ParseList parses a comma separated list of values. Commas are ignored in | ||
84 | // quoted strings. Quoted values are not unescaped or unquoted. Whitespace is | ||
85 | // trimmed. | ||
86 | func ParseList(header http.Header, key string) []string { | ||
87 | var result []string | ||
88 | for _, s := range header[http.CanonicalHeaderKey(key)] { | ||
89 | begin := 0 | ||
90 | end := 0 | ||
91 | escape := false | ||
92 | quote := false | ||
93 | for i := 0; i < len(s); i++ { | ||
94 | b := s[i] | ||
95 | switch { | ||
96 | case escape: | ||
97 | escape = false | ||
98 | end = i + 1 | ||
99 | case quote: | ||
100 | switch b { | ||
101 | case '\\': | ||
102 | escape = true | ||
103 | case '"': | ||
104 | quote = false | ||
105 | } | ||
106 | end = i + 1 | ||
107 | case b == '"': | ||
108 | quote = true | ||
109 | end = i + 1 | ||
110 | case octetTypes[b]&isSpace != 0: | ||
111 | if begin == end { | ||
112 | begin = i + 1 | ||
113 | end = begin | ||
114 | } | ||
115 | case b == ',': | ||
116 | if begin < end { | ||
117 | result = append(result, s[begin:end]) | ||
118 | } | ||
119 | begin = i + 1 | ||
120 | end = begin | ||
121 | default: | ||
122 | end = i + 1 | ||
123 | } | ||
124 | } | ||
125 | if begin < end { | ||
126 | result = append(result, s[begin:end]) | ||
127 | } | ||
128 | } | ||
129 | return result | ||
130 | } | ||
131 | |||
132 | // ParseValueAndParams parses a comma separated list of values with optional | ||
133 | // semicolon separated name-value pairs. Content-Type and Content-Disposition | ||
134 | // headers are in this format. | ||
135 | func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) { | ||
136 | params = make(map[string]string) | ||
137 | s := header.Get(key) | ||
138 | value, s = expectTokenSlash(s) | ||
139 | if value == "" { | ||
140 | return | ||
141 | } | ||
142 | value = strings.ToLower(value) | ||
143 | s = skipSpace(s) | ||
144 | for strings.HasPrefix(s, ";") { | ||
145 | var pkey string | ||
146 | pkey, s = expectToken(skipSpace(s[1:])) | ||
147 | if pkey == "" { | ||
148 | return | ||
149 | } | ||
150 | if !strings.HasPrefix(s, "=") { | ||
151 | return | ||
152 | } | ||
153 | var pvalue string | ||
154 | pvalue, s = expectTokenOrQuoted(s[1:]) | ||
155 | if pvalue == "" { | ||
156 | return | ||
157 | } | ||
158 | pkey = strings.ToLower(pkey) | ||
159 | params[pkey] = pvalue | ||
160 | s = skipSpace(s) | ||
161 | } | ||
162 | return | ||
163 | } | ||
164 | |||
165 | // AcceptSpec describes an Accept* header. | ||
166 | type AcceptSpec struct { | ||
167 | Value string | ||
168 | Q float64 | ||
169 | } | ||
170 | |||
171 | // ParseAccept parses Accept* headers. | ||
172 | func ParseAccept(header http.Header, key string) (specs []AcceptSpec) { | ||
173 | loop: | ||
174 | for _, s := range header[key] { | ||
175 | for { | ||
176 | var spec AcceptSpec | ||
177 | spec.Value, s = expectTokenSlash(s) | ||
178 | if spec.Value == "" { | ||
179 | continue loop | ||
180 | } | ||
181 | spec.Q = 1.0 | ||
182 | s = skipSpace(s) | ||
183 | if strings.HasPrefix(s, ";") { | ||
184 | s = skipSpace(s[1:]) | ||
185 | if !strings.HasPrefix(s, "q=") { | ||
186 | continue loop | ||
187 | } | ||
188 | spec.Q, s = expectQuality(s[2:]) | ||
189 | if spec.Q < 0.0 { | ||
190 | continue loop | ||
191 | } | ||
192 | } | ||
193 | specs = append(specs, spec) | ||
194 | s = skipSpace(s) | ||
195 | if !strings.HasPrefix(s, ",") { | ||
196 | continue loop | ||
197 | } | ||
198 | s = skipSpace(s[1:]) | ||
199 | } | ||
200 | } | ||
201 | return | ||
202 | } | ||
203 | |||
204 | func skipSpace(s string) (rest string) { | ||
205 | i := 0 | ||
206 | for ; i < len(s); i++ { | ||
207 | if octetTypes[s[i]]&isSpace == 0 { | ||
208 | break | ||
209 | } | ||
210 | } | ||
211 | return s[i:] | ||
212 | } | ||
213 | |||
214 | func expectToken(s string) (token, rest string) { | ||
215 | i := 0 | ||
216 | for ; i < len(s); i++ { | ||
217 | if octetTypes[s[i]]&isToken == 0 { | ||
218 | break | ||
219 | } | ||
220 | } | ||
221 | return s[:i], s[i:] | ||
222 | } | ||
223 | |||
224 | func expectTokenSlash(s string) (token, rest string) { | ||
225 | i := 0 | ||
226 | for ; i < len(s); i++ { | ||
227 | b := s[i] | ||
228 | if (octetTypes[b]&isToken == 0) && b != '/' { | ||
229 | break | ||
230 | } | ||
231 | } | ||
232 | return s[:i], s[i:] | ||
233 | } | ||
234 | |||
235 | func expectQuality(s string) (q float64, rest string) { | ||
236 | switch { | ||
237 | case len(s) == 0: | ||
238 | return -1, "" | ||
239 | case s[0] == '0': | ||
240 | q = 0 | ||
241 | case s[0] == '1': | ||
242 | q = 1 | ||
243 | default: | ||
244 | return -1, "" | ||
245 | } | ||
246 | s = s[1:] | ||
247 | if !strings.HasPrefix(s, ".") { | ||
248 | return q, s | ||
249 | } | ||
250 | s = s[1:] | ||
251 | i := 0 | ||
252 | n := 0 | ||
253 | d := 1 | ||
254 | for ; i < len(s); i++ { | ||
255 | b := s[i] | ||
256 | if b < '0' || b > '9' { | ||
257 | break | ||
258 | } | ||
259 | n = n*10 + int(b) - '0' | ||
260 | d *= 10 | ||
261 | } | ||
262 | return q + float64(n)/float64(d), s[i:] | ||
263 | } | ||
264 | |||
265 | func expectTokenOrQuoted(s string) (value string, rest string) { | ||
266 | if !strings.HasPrefix(s, "\"") { | ||
267 | return expectToken(s) | ||
268 | } | ||
269 | s = s[1:] | ||
270 | for i := 0; i < len(s); i++ { | ||
271 | switch s[i] { | ||
272 | case '"': | ||
273 | return s[:i], s[i+1:] | ||
274 | case '\\': | ||
275 | p := make([]byte, len(s)-1) | ||
276 | j := copy(p, s[:i]) | ||
277 | escape := true | ||
278 | for i = i + 1; i < len(s); i++ { | ||
279 | b := s[i] | ||
280 | switch { | ||
281 | case escape: | ||
282 | escape = false | ||
283 | p[j] = b | ||
284 | j++ | ||
285 | case b == '\\': | ||
286 | escape = true | ||
287 | case b == '"': | ||
288 | return string(p[:j]), s[i+1:] | ||
289 | default: | ||
290 | p[j] = b | ||
291 | j++ | ||
292 | } | ||
293 | } | ||
294 | return "", "" | ||
295 | } | ||
296 | } | ||
297 | return "", "" | ||
298 | } | ||