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