libgo: update to Go 1.11
[official-gcc.git] / libgo / go / net / http / cookie.go
blobb1a6cef6f700f11002b18c31172647f63a601bb7
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 package http
7 import (
8 "log"
9 "net"
10 "strconv"
11 "strings"
12 "time"
15 // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
16 // HTTP response or the Cookie header of an HTTP request.
18 // See https://tools.ietf.org/html/rfc6265 for details.
19 type Cookie struct {
20 Name string
21 Value string
23 Path string // optional
24 Domain string // optional
25 Expires time.Time // optional
26 RawExpires string // for reading cookies only
28 // MaxAge=0 means no 'Max-Age' attribute specified.
29 // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
30 // MaxAge>0 means Max-Age attribute present and given in seconds
31 MaxAge int
32 Secure bool
33 HttpOnly bool
34 SameSite SameSite
35 Raw string
36 Unparsed []string // Raw text of unparsed attribute-value pairs
39 // SameSite allows a server define a cookie attribute making it impossible to
40 // the browser send this cookie along with cross-site requests. The main goal
41 // is mitigate the risk of cross-origin information leakage, and provides some
42 // protection against cross-site request forgery attacks.
44 // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
45 type SameSite int
47 const (
48 SameSiteDefaultMode SameSite = iota + 1
49 SameSiteLaxMode
50 SameSiteStrictMode
53 // readSetCookies parses all "Set-Cookie" values from
54 // the header h and returns the successfully parsed Cookies.
55 func readSetCookies(h Header) []*Cookie {
56 cookieCount := len(h["Set-Cookie"])
57 if cookieCount == 0 {
58 return []*Cookie{}
60 cookies := make([]*Cookie, 0, cookieCount)
61 for _, line := range h["Set-Cookie"] {
62 parts := strings.Split(strings.TrimSpace(line), ";")
63 if len(parts) == 1 && parts[0] == "" {
64 continue
66 parts[0] = strings.TrimSpace(parts[0])
67 j := strings.Index(parts[0], "=")
68 if j < 0 {
69 continue
71 name, value := parts[0][:j], parts[0][j+1:]
72 if !isCookieNameValid(name) {
73 continue
75 value, ok := parseCookieValue(value, true)
76 if !ok {
77 continue
79 c := &Cookie{
80 Name: name,
81 Value: value,
82 Raw: line,
84 for i := 1; i < len(parts); i++ {
85 parts[i] = strings.TrimSpace(parts[i])
86 if len(parts[i]) == 0 {
87 continue
90 attr, val := parts[i], ""
91 if j := strings.Index(attr, "="); j >= 0 {
92 attr, val = attr[:j], attr[j+1:]
94 lowerAttr := strings.ToLower(attr)
95 val, ok = parseCookieValue(val, false)
96 if !ok {
97 c.Unparsed = append(c.Unparsed, parts[i])
98 continue
100 switch lowerAttr {
101 case "samesite":
102 lowerVal := strings.ToLower(val)
103 switch lowerVal {
104 case "lax":
105 c.SameSite = SameSiteLaxMode
106 case "strict":
107 c.SameSite = SameSiteStrictMode
108 default:
109 c.SameSite = SameSiteDefaultMode
111 continue
112 case "secure":
113 c.Secure = true
114 continue
115 case "httponly":
116 c.HttpOnly = true
117 continue
118 case "domain":
119 c.Domain = val
120 continue
121 case "max-age":
122 secs, err := strconv.Atoi(val)
123 if err != nil || secs != 0 && val[0] == '0' {
124 break
126 if secs <= 0 {
127 secs = -1
129 c.MaxAge = secs
130 continue
131 case "expires":
132 c.RawExpires = val
133 exptime, err := time.Parse(time.RFC1123, val)
134 if err != nil {
135 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
136 if err != nil {
137 c.Expires = time.Time{}
138 break
141 c.Expires = exptime.UTC()
142 continue
143 case "path":
144 c.Path = val
145 continue
147 c.Unparsed = append(c.Unparsed, parts[i])
149 cookies = append(cookies, c)
151 return cookies
154 // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
155 // The provided cookie must have a valid Name. Invalid cookies may be
156 // silently dropped.
157 func SetCookie(w ResponseWriter, cookie *Cookie) {
158 if v := cookie.String(); v != "" {
159 w.Header().Add("Set-Cookie", v)
163 // String returns the serialization of the cookie for use in a Cookie
164 // header (if only Name and Value are set) or a Set-Cookie response
165 // header (if other fields are set).
166 // If c is nil or c.Name is invalid, the empty string is returned.
167 func (c *Cookie) String() string {
168 if c == nil || !isCookieNameValid(c.Name) {
169 return ""
171 var b strings.Builder
172 b.WriteString(sanitizeCookieName(c.Name))
173 b.WriteRune('=')
174 b.WriteString(sanitizeCookieValue(c.Value))
176 if len(c.Path) > 0 {
177 b.WriteString("; Path=")
178 b.WriteString(sanitizeCookiePath(c.Path))
180 if len(c.Domain) > 0 {
181 if validCookieDomain(c.Domain) {
182 // A c.Domain containing illegal characters is not
183 // sanitized but simply dropped which turns the cookie
184 // into a host-only cookie. A leading dot is okay
185 // but won't be sent.
186 d := c.Domain
187 if d[0] == '.' {
188 d = d[1:]
190 b.WriteString("; Domain=")
191 b.WriteString(d)
192 } else {
193 log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain)
196 var buf [len(TimeFormat)]byte
197 if validCookieExpires(c.Expires) {
198 b.WriteString("; Expires=")
199 b.Write(c.Expires.UTC().AppendFormat(buf[:0], TimeFormat))
201 if c.MaxAge > 0 {
202 b.WriteString("; Max-Age=")
203 b.Write(strconv.AppendInt(buf[:0], int64(c.MaxAge), 10))
204 } else if c.MaxAge < 0 {
205 b.WriteString("; Max-Age=0")
207 if c.HttpOnly {
208 b.WriteString("; HttpOnly")
210 if c.Secure {
211 b.WriteString("; Secure")
213 switch c.SameSite {
214 case SameSiteDefaultMode:
215 b.WriteString("; SameSite")
216 case SameSiteLaxMode:
217 b.WriteString("; SameSite=Lax")
218 case SameSiteStrictMode:
219 b.WriteString("; SameSite=Strict")
221 return b.String()
224 // readCookies parses all "Cookie" values from the header h and
225 // returns the successfully parsed Cookies.
227 // if filter isn't empty, only cookies of that name are returned
228 func readCookies(h Header, filter string) []*Cookie {
229 lines, ok := h["Cookie"]
230 if !ok {
231 return []*Cookie{}
234 cookies := []*Cookie{}
235 for _, line := range lines {
236 parts := strings.Split(strings.TrimSpace(line), ";")
237 if len(parts) == 1 && parts[0] == "" {
238 continue
240 // Per-line attributes
241 for i := 0; i < len(parts); i++ {
242 parts[i] = strings.TrimSpace(parts[i])
243 if len(parts[i]) == 0 {
244 continue
246 name, val := parts[i], ""
247 if j := strings.Index(name, "="); j >= 0 {
248 name, val = name[:j], name[j+1:]
250 if !isCookieNameValid(name) {
251 continue
253 if filter != "" && filter != name {
254 continue
256 val, ok := parseCookieValue(val, true)
257 if !ok {
258 continue
260 cookies = append(cookies, &Cookie{Name: name, Value: val})
263 return cookies
266 // validCookieDomain returns whether v is a valid cookie domain-value.
267 func validCookieDomain(v string) bool {
268 if isCookieDomainName(v) {
269 return true
271 if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
272 return true
274 return false
277 // validCookieExpires returns whether v is a valid cookie expires-value.
278 func validCookieExpires(t time.Time) bool {
279 // IETF RFC 6265 Section 5.1.1.5, the year must not be less than 1601
280 return t.Year() >= 1601
283 // isCookieDomainName returns whether s is a valid domain name or a valid
284 // domain name with a leading dot '.'. It is almost a direct copy of
285 // package net's isDomainName.
286 func isCookieDomainName(s string) bool {
287 if len(s) == 0 {
288 return false
290 if len(s) > 255 {
291 return false
294 if s[0] == '.' {
295 // A cookie a domain attribute may start with a leading dot.
296 s = s[1:]
298 last := byte('.')
299 ok := false // Ok once we've seen a letter.
300 partlen := 0
301 for i := 0; i < len(s); i++ {
302 c := s[i]
303 switch {
304 default:
305 return false
306 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
307 // No '_' allowed here (in contrast to package net).
308 ok = true
309 partlen++
310 case '0' <= c && c <= '9':
311 // fine
312 partlen++
313 case c == '-':
314 // Byte before dash cannot be dot.
315 if last == '.' {
316 return false
318 partlen++
319 case c == '.':
320 // Byte before dot cannot be dot, dash.
321 if last == '.' || last == '-' {
322 return false
324 if partlen > 63 || partlen == 0 {
325 return false
327 partlen = 0
329 last = c
331 if last == '-' || partlen > 63 {
332 return false
335 return ok
338 var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
340 func sanitizeCookieName(n string) string {
341 return cookieNameSanitizer.Replace(n)
344 // https://tools.ietf.org/html/rfc6265#section-4.1.1
345 // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
346 // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
347 // ; US-ASCII characters excluding CTLs,
348 // ; whitespace DQUOTE, comma, semicolon,
349 // ; and backslash
350 // We loosen this as spaces and commas are common in cookie values
351 // but we produce a quoted cookie-value in when value starts or ends
352 // with a comma or space.
353 // See https://golang.org/issue/7243 for the discussion.
354 func sanitizeCookieValue(v string) string {
355 v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
356 if len(v) == 0 {
357 return v
359 if strings.IndexByte(v, ' ') >= 0 || strings.IndexByte(v, ',') >= 0 {
360 return `"` + v + `"`
362 return v
365 func validCookieValueByte(b byte) bool {
366 return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
369 // path-av = "Path=" path-value
370 // path-value = <any CHAR except CTLs or ";">
371 func sanitizeCookiePath(v string) string {
372 return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v)
375 func validCookiePathByte(b byte) bool {
376 return 0x20 <= b && b < 0x7f && b != ';'
379 func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
380 ok := true
381 for i := 0; i < len(v); i++ {
382 if valid(v[i]) {
383 continue
385 log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName)
386 ok = false
387 break
389 if ok {
390 return v
392 buf := make([]byte, 0, len(v))
393 for i := 0; i < len(v); i++ {
394 if b := v[i]; valid(b) {
395 buf = append(buf, b)
398 return string(buf)
401 func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
402 // Strip the quotes, if present.
403 if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
404 raw = raw[1 : len(raw)-1]
406 for i := 0; i < len(raw); i++ {
407 if !validCookieValueByte(raw[i]) {
408 return "", false
411 return raw, true
414 func isCookieNameValid(raw string) bool {
415 if raw == "" {
416 return false
418 return strings.IndexFunc(raw, isNotToken) < 0