Source file
src/net/http/cookie.go
1
2
3
4
5 package http
6
7 import (
8 "bytes"
9 "log"
10 "net"
11 "strconv"
12 "strings"
13 "time"
14 )
15
16
17
18
19
20 type Cookie struct {
21 Name string
22 Value string
23
24 Path string
25 Domain string
26 Expires time.Time
27 RawExpires string
28
29
30
31
32 MaxAge int
33 Secure bool
34 HttpOnly bool
35 Raw string
36 Unparsed []string
37 }
38
39
40
41 func readSetCookies(h Header) []*Cookie {
42 cookieCount := len(h["Set-Cookie"])
43 if cookieCount == 0 {
44 return []*Cookie{}
45 }
46 cookies := make([]*Cookie, 0, cookieCount)
47 for _, line := range h["Set-Cookie"] {
48 parts := strings.Split(strings.TrimSpace(line), ";")
49 if len(parts) == 1 && parts[0] == "" {
50 continue
51 }
52 parts[0] = strings.TrimSpace(parts[0])
53 j := strings.Index(parts[0], "=")
54 if j < 0 {
55 continue
56 }
57 name, value := parts[0][:j], parts[0][j+1:]
58 if !isCookieNameValid(name) {
59 continue
60 }
61 value, ok := parseCookieValue(value, true)
62 if !ok {
63 continue
64 }
65 c := &Cookie{
66 Name: name,
67 Value: value,
68 Raw: line,
69 }
70 for i := 1; i < len(parts); i++ {
71 parts[i] = strings.TrimSpace(parts[i])
72 if len(parts[i]) == 0 {
73 continue
74 }
75
76 attr, val := parts[i], ""
77 if j := strings.Index(attr, "="); j >= 0 {
78 attr, val = attr[:j], attr[j+1:]
79 }
80 lowerAttr := strings.ToLower(attr)
81 val, ok = parseCookieValue(val, false)
82 if !ok {
83 c.Unparsed = append(c.Unparsed, parts[i])
84 continue
85 }
86 switch lowerAttr {
87 case "secure":
88 c.Secure = true
89 continue
90 case "httponly":
91 c.HttpOnly = true
92 continue
93 case "domain":
94 c.Domain = val
95 continue
96 case "max-age":
97 secs, err := strconv.Atoi(val)
98 if err != nil || secs != 0 && val[0] == '0' {
99 break
100 }
101 if secs <= 0 {
102 secs = -1
103 }
104 c.MaxAge = secs
105 continue
106 case "expires":
107 c.RawExpires = val
108 exptime, err := time.Parse(time.RFC1123, val)
109 if err != nil {
110 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
111 if err != nil {
112 c.Expires = time.Time{}
113 break
114 }
115 }
116 c.Expires = exptime.UTC()
117 continue
118 case "path":
119 c.Path = val
120 continue
121 }
122 c.Unparsed = append(c.Unparsed, parts[i])
123 }
124 cookies = append(cookies, c)
125 }
126 return cookies
127 }
128
129
130
131
132 func SetCookie(w ResponseWriter, cookie *Cookie) {
133 if v := cookie.String(); v != "" {
134 w.Header().Add("Set-Cookie", v)
135 }
136 }
137
138
139
140
141
142 func (c *Cookie) String() string {
143 if c == nil || !isCookieNameValid(c.Name) {
144 return ""
145 }
146 var b bytes.Buffer
147 b.WriteString(sanitizeCookieName(c.Name))
148 b.WriteRune('=')
149 b.WriteString(sanitizeCookieValue(c.Value))
150
151 if len(c.Path) > 0 {
152 b.WriteString("; Path=")
153 b.WriteString(sanitizeCookiePath(c.Path))
154 }
155 if len(c.Domain) > 0 {
156 if validCookieDomain(c.Domain) {
157
158
159
160
161 d := c.Domain
162 if d[0] == '.' {
163 d = d[1:]
164 }
165 b.WriteString("; Domain=")
166 b.WriteString(d)
167 } else {
168 log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain)
169 }
170 }
171 if validCookieExpires(c.Expires) {
172 b.WriteString("; Expires=")
173 b2 := b.Bytes()
174 b.Reset()
175 b.Write(c.Expires.UTC().AppendFormat(b2, TimeFormat))
176 }
177 if c.MaxAge > 0 {
178 b.WriteString("; Max-Age=")
179 b2 := b.Bytes()
180 b.Reset()
181 b.Write(strconv.AppendInt(b2, int64(c.MaxAge), 10))
182 } else if c.MaxAge < 0 {
183 b.WriteString("; Max-Age=0")
184 }
185 if c.HttpOnly {
186 b.WriteString("; HttpOnly")
187 }
188 if c.Secure {
189 b.WriteString("; Secure")
190 }
191 return b.String()
192 }
193
194
195
196
197
198 func readCookies(h Header, filter string) []*Cookie {
199 lines, ok := h["Cookie"]
200 if !ok {
201 return []*Cookie{}
202 }
203
204 cookies := []*Cookie{}
205 for _, line := range lines {
206 parts := strings.Split(strings.TrimSpace(line), ";")
207 if len(parts) == 1 && parts[0] == "" {
208 continue
209 }
210
211 for i := 0; i < len(parts); i++ {
212 parts[i] = strings.TrimSpace(parts[i])
213 if len(parts[i]) == 0 {
214 continue
215 }
216 name, val := parts[i], ""
217 if j := strings.Index(name, "="); j >= 0 {
218 name, val = name[:j], name[j+1:]
219 }
220 if !isCookieNameValid(name) {
221 continue
222 }
223 if filter != "" && filter != name {
224 continue
225 }
226 val, ok := parseCookieValue(val, true)
227 if !ok {
228 continue
229 }
230 cookies = append(cookies, &Cookie{Name: name, Value: val})
231 }
232 }
233 return cookies
234 }
235
236
237 func validCookieDomain(v string) bool {
238 if isCookieDomainName(v) {
239 return true
240 }
241 if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
242 return true
243 }
244 return false
245 }
246
247
248 func validCookieExpires(t time.Time) bool {
249
250 return t.Year() >= 1601
251 }
252
253
254
255
256 func isCookieDomainName(s string) bool {
257 if len(s) == 0 {
258 return false
259 }
260 if len(s) > 255 {
261 return false
262 }
263
264 if s[0] == '.' {
265
266 s = s[1:]
267 }
268 last := byte('.')
269 ok := false
270 partlen := 0
271 for i := 0; i < len(s); i++ {
272 c := s[i]
273 switch {
274 default:
275 return false
276 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
277
278 ok = true
279 partlen++
280 case '0' <= c && c <= '9':
281
282 partlen++
283 case c == '-':
284
285 if last == '.' {
286 return false
287 }
288 partlen++
289 case c == '.':
290
291 if last == '.' || last == '-' {
292 return false
293 }
294 if partlen > 63 || partlen == 0 {
295 return false
296 }
297 partlen = 0
298 }
299 last = c
300 }
301 if last == '-' || partlen > 63 {
302 return false
303 }
304
305 return ok
306 }
307
308 var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
309
310 func sanitizeCookieName(n string) string {
311 return cookieNameSanitizer.Replace(n)
312 }
313
314
315
316
317
318
319
320
321
322
323
324 func sanitizeCookieValue(v string) string {
325 v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
326 if len(v) == 0 {
327 return v
328 }
329 if strings.IndexByte(v, ' ') >= 0 || strings.IndexByte(v, ',') >= 0 {
330 return `"` + v + `"`
331 }
332 return v
333 }
334
335 func validCookieValueByte(b byte) bool {
336 return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
337 }
338
339
340
341 func sanitizeCookiePath(v string) string {
342 return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v)
343 }
344
345 func validCookiePathByte(b byte) bool {
346 return 0x20 <= b && b < 0x7f && b != ';'
347 }
348
349 func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
350 ok := true
351 for i := 0; i < len(v); i++ {
352 if valid(v[i]) {
353 continue
354 }
355 log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName)
356 ok = false
357 break
358 }
359 if ok {
360 return v
361 }
362 buf := make([]byte, 0, len(v))
363 for i := 0; i < len(v); i++ {
364 if b := v[i]; valid(b) {
365 buf = append(buf, b)
366 }
367 }
368 return string(buf)
369 }
370
371 func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
372
373 if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
374 raw = raw[1 : len(raw)-1]
375 }
376 for i := 0; i < len(raw); i++ {
377 if !validCookieValueByte(raw[i]) {
378 return "", false
379 }
380 }
381 return raw, true
382 }
383
384 func isCookieNameValid(raw string) bool {
385 if raw == "" {
386 return false
387 }
388 return strings.IndexFunc(raw, isNotToken) < 0
389 }
390
View as plain text