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