Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 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. |
| 4 | |
| 5 | package http |
| 6 | |
| 7 | import ( |
| 8 | "bytes" |
| 9 | "fmt" |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 10 | "log" |
Volker Dobler | 4f86a96 | 2013-08-12 15:14:34 -0700 | [diff] [blame] | 11 | "net" |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 12 | "strconv" |
| 13 | "strings" |
| 14 | "time" |
| 15 | ) |
| 16 | |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 17 | // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an |
| 18 | // HTTP response or the Cookie header of an HTTP request. |
Brad Fitzpatrick | 9462bce | 2015-06-29 18:10:43 -0700 | [diff] [blame] | 19 | // |
| 20 | // See http://tools.ietf.org/html/rfc6265 for details. |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 21 | type Cookie struct { |
Brad Fitzpatrick | 9462bce | 2015-06-29 18:10:43 -0700 | [diff] [blame] | 22 | Name string |
| 23 | Value string |
| 24 | |
| 25 | Path string // optional |
| 26 | Domain string // optional |
| 27 | Expires time.Time // optional |
| 28 | RawExpires string // for reading cookies only |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 29 | |
Robert Griesemer | 465b9c3 | 2012-10-30 13:38:01 -0700 | [diff] [blame] | 30 | // MaxAge=0 means no 'Max-Age' attribute specified. |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 31 | // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' |
| 32 | // MaxAge>0 means Max-Age attribute present and given in seconds |
| 33 | MaxAge int |
| 34 | Secure bool |
| 35 | HttpOnly bool |
| 36 | Raw string |
| 37 | Unparsed []string // Raw text of unparsed attribute-value pairs |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 38 | } |
| 39 | |
| 40 | // readSetCookies parses all "Set-Cookie" values from |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 41 | // the header h and returns the successfully parsed Cookies. |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 42 | func readSetCookies(h Header) []*Cookie { |
| 43 | cookies := []*Cookie{} |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 44 | for _, line := range h["Set-Cookie"] { |
Rob Pike | ebb1566 | 2011-06-28 09:43:14 +1000 | [diff] [blame] | 45 | parts := strings.Split(strings.TrimSpace(line), ";") |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 46 | if len(parts) == 1 && parts[0] == "" { |
| 47 | continue |
| 48 | } |
| 49 | parts[0] = strings.TrimSpace(parts[0]) |
Brad Fitzpatrick | d8e27db | 2013-08-05 16:27:24 -0700 | [diff] [blame] | 50 | j := strings.Index(parts[0], "=") |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 51 | if j < 0 { |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 52 | continue |
| 53 | } |
| 54 | name, value := parts[0][:j], parts[0][j+1:] |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 55 | if !isCookieNameValid(name) { |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 56 | continue |
| 57 | } |
Nigel Tao | e59ad69 | 2014-09-25 10:21:52 +1000 | [diff] [blame] | 58 | value, success := parseCookieValue(value, true) |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 59 | if !success { |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 60 | continue |
| 61 | } |
| 62 | c := &Cookie{ |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 63 | Name: name, |
| 64 | Value: value, |
| 65 | Raw: line, |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 66 | } |
| 67 | for i := 1; i < len(parts); i++ { |
| 68 | parts[i] = strings.TrimSpace(parts[i]) |
| 69 | if len(parts[i]) == 0 { |
| 70 | continue |
| 71 | } |
| 72 | |
| 73 | attr, val := parts[i], "" |
Brad Fitzpatrick | d8e27db | 2013-08-05 16:27:24 -0700 | [diff] [blame] | 74 | if j := strings.Index(attr, "="); j >= 0 { |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 75 | attr, val = attr[:j], attr[j+1:] |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 76 | } |
Brad Fitzpatrick | 3933cb2 | 2011-05-24 08:31:43 -0700 | [diff] [blame] | 77 | lowerAttr := strings.ToLower(attr) |
Nigel Tao | e59ad69 | 2014-09-25 10:21:52 +1000 | [diff] [blame] | 78 | val, success = parseCookieValue(val, false) |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 79 | if !success { |
| 80 | c.Unparsed = append(c.Unparsed, parts[i]) |
| 81 | continue |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 82 | } |
Brad Fitzpatrick | 3933cb2 | 2011-05-24 08:31:43 -0700 | [diff] [blame] | 83 | switch lowerAttr { |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 84 | case "secure": |
| 85 | c.Secure = true |
| 86 | continue |
| 87 | case "httponly": |
| 88 | c.HttpOnly = true |
| 89 | continue |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 90 | case "domain": |
| 91 | c.Domain = val |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 92 | continue |
| 93 | case "max-age": |
| 94 | secs, err := strconv.Atoi(val) |
Volker Dobler | 4dda23a | 2012-01-15 19:32:16 +1100 | [diff] [blame] | 95 | if err != nil || secs != 0 && val[0] == '0' { |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 96 | break |
| 97 | } |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 98 | if secs <= 0 { |
| 99 | c.MaxAge = -1 |
| 100 | } else { |
| 101 | c.MaxAge = secs |
| 102 | } |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 103 | continue |
| 104 | case "expires": |
| 105 | c.RawExpires = val |
| 106 | exptime, err := time.Parse(time.RFC1123, val) |
| 107 | if err != nil { |
Brad Fitzpatrick | 3933cb2 | 2011-05-24 08:31:43 -0700 | [diff] [blame] | 108 | exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) |
| 109 | if err != nil { |
| 110 | c.Expires = time.Time{} |
| 111 | break |
| 112 | } |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 113 | } |
Russ Cox | 03823b8 | 2011-11-30 12:01:46 -0500 | [diff] [blame] | 114 | c.Expires = exptime.UTC() |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 115 | continue |
| 116 | case "path": |
| 117 | c.Path = val |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 118 | continue |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 119 | } |
| 120 | c.Unparsed = append(c.Unparsed, parts[i]) |
| 121 | } |
| 122 | cookies = append(cookies, c) |
| 123 | } |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 124 | return cookies |
| 125 | } |
| 126 | |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 127 | // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. |
Brad Fitzpatrick | 9462bce | 2015-06-29 18:10:43 -0700 | [diff] [blame] | 128 | // The provided cookie must have a valid Name. Invalid cookies may be |
| 129 | // silently dropped. |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 130 | func SetCookie(w ResponseWriter, cookie *Cookie) { |
Brad Fitzpatrick | 9462bce | 2015-06-29 18:10:43 -0700 | [diff] [blame] | 131 | if v := cookie.String(); v != "" { |
| 132 | w.Header().Add("Set-Cookie", v) |
| 133 | } |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 134 | } |
| 135 | |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 136 | // String returns the serialization of the cookie for use in a Cookie |
| 137 | // header (if only Name and Value are set) or a Set-Cookie response |
| 138 | // header (if other fields are set). |
Brad Fitzpatrick | 9462bce | 2015-06-29 18:10:43 -0700 | [diff] [blame] | 139 | // If c is nil or c.Name is invalid, the empty string is returned. |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 140 | func (c *Cookie) String() string { |
Brad Fitzpatrick | 9462bce | 2015-06-29 18:10:43 -0700 | [diff] [blame] | 141 | if c == nil || !isCookieNameValid(c.Name) { |
| 142 | return "" |
| 143 | } |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 144 | var b bytes.Buffer |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 145 | fmt.Fprintf(&b, "%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 146 | if len(c.Path) > 0 { |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 147 | fmt.Fprintf(&b, "; Path=%s", sanitizeCookiePath(c.Path)) |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 148 | } |
| 149 | if len(c.Domain) > 0 { |
Volker Dobler | 4f86a96 | 2013-08-12 15:14:34 -0700 | [diff] [blame] | 150 | if validCookieDomain(c.Domain) { |
| 151 | // A c.Domain containing illegal characters is not |
| 152 | // sanitized but simply dropped which turns the cookie |
Volker Dobler | f1d61b9 | 2013-08-26 07:41:37 -0500 | [diff] [blame] | 153 | // into a host-only cookie. A leading dot is okay |
| 154 | // but won't be sent. |
| 155 | d := c.Domain |
| 156 | if d[0] == '.' { |
| 157 | d = d[1:] |
| 158 | } |
| 159 | fmt.Fprintf(&b, "; Domain=%s", d) |
Volker Dobler | 4f86a96 | 2013-08-12 15:14:34 -0700 | [diff] [blame] | 160 | } else { |
| 161 | log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", |
| 162 | c.Domain) |
| 163 | } |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 164 | } |
Russ Cox | 03823b8 | 2011-11-30 12:01:46 -0500 | [diff] [blame] | 165 | if c.Expires.Unix() > 0 { |
Brad Fitzpatrick | d751be9 | 2015-06-04 12:57:53 -0700 | [diff] [blame] | 166 | fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(TimeFormat)) |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 167 | } |
| 168 | if c.MaxAge > 0 { |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 169 | fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 170 | } else if c.MaxAge < 0 { |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 171 | fmt.Fprintf(&b, "; Max-Age=0") |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 172 | } |
| 173 | if c.HttpOnly { |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 174 | fmt.Fprintf(&b, "; HttpOnly") |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 175 | } |
| 176 | if c.Secure { |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 177 | fmt.Fprintf(&b, "; Secure") |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 178 | } |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 179 | return b.String() |
Brad Fitzpatrick | 9ea0bd3 | 2011-05-17 15:07:44 -0700 | [diff] [blame] | 180 | } |
| 181 | |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 182 | // readCookies parses all "Cookie" values from the header h and |
| 183 | // returns the successfully parsed Cookies. |
| 184 | // |
| 185 | // if filter isn't empty, only cookies of that name are returned |
| 186 | func readCookies(h Header, filter string) []*Cookie { |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 187 | cookies := []*Cookie{} |
| 188 | lines, ok := h["Cookie"] |
| 189 | if !ok { |
| 190 | return cookies |
| 191 | } |
Brad Fitzpatrick | da7b96f | 2011-06-20 10:33:07 -0700 | [diff] [blame] | 192 | |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 193 | for _, line := range lines { |
Rob Pike | ebb1566 | 2011-06-28 09:43:14 +1000 | [diff] [blame] | 194 | parts := strings.Split(strings.TrimSpace(line), ";") |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 195 | if len(parts) == 1 && parts[0] == "" { |
| 196 | continue |
| 197 | } |
| 198 | // Per-line attributes |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 199 | parsedPairs := 0 |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 200 | for i := 0; i < len(parts); i++ { |
| 201 | parts[i] = strings.TrimSpace(parts[i]) |
| 202 | if len(parts[i]) == 0 { |
| 203 | continue |
| 204 | } |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 205 | name, val := parts[i], "" |
Brad Fitzpatrick | d8e27db | 2013-08-05 16:27:24 -0700 | [diff] [blame] | 206 | if j := strings.Index(name, "="); j >= 0 { |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 207 | name, val = name[:j], name[j+1:] |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 208 | } |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 209 | if !isCookieNameValid(name) { |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 210 | continue |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 211 | } |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 212 | if filter != "" && filter != name { |
Brad Fitzpatrick | da7b96f | 2011-06-20 10:33:07 -0700 | [diff] [blame] | 213 | continue |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 214 | } |
Nigel Tao | e59ad69 | 2014-09-25 10:21:52 +1000 | [diff] [blame] | 215 | val, success := parseCookieValue(val, true) |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 216 | if !success { |
| 217 | continue |
| 218 | } |
Brad Fitzpatrick | 6e9b1a7 | 2011-06-16 13:02:28 -0700 | [diff] [blame] | 219 | cookies = append(cookies, &Cookie{Name: name, Value: val}) |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 220 | parsedPairs++ |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 221 | } |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 222 | } |
Petar Maymounkov | 6afe7eb | 2011-03-06 15:02:06 -0500 | [diff] [blame] | 223 | return cookies |
| 224 | } |
| 225 | |
Volker Dobler | 4f86a96 | 2013-08-12 15:14:34 -0700 | [diff] [blame] | 226 | // validCookieDomain returns wheter v is a valid cookie domain-value. |
| 227 | func validCookieDomain(v string) bool { |
| 228 | if isCookieDomainName(v) { |
| 229 | return true |
| 230 | } |
| 231 | if net.ParseIP(v) != nil && !strings.Contains(v, ":") { |
| 232 | return true |
| 233 | } |
| 234 | return false |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 235 | } |
| 236 | |
Volker Dobler | 4f86a96 | 2013-08-12 15:14:34 -0700 | [diff] [blame] | 237 | // isCookieDomainName returns whether s is a valid domain name or a valid |
| 238 | // domain name with a leading dot '.'. It is almost a direct copy of |
| 239 | // package net's isDomainName. |
| 240 | func isCookieDomainName(s string) bool { |
| 241 | if len(s) == 0 { |
| 242 | return false |
| 243 | } |
| 244 | if len(s) > 255 { |
| 245 | return false |
| 246 | } |
| 247 | |
| 248 | if s[0] == '.' { |
| 249 | // A cookie a domain attribute may start with a leading dot. |
| 250 | s = s[1:] |
| 251 | } |
| 252 | last := byte('.') |
| 253 | ok := false // Ok once we've seen a letter. |
| 254 | partlen := 0 |
| 255 | for i := 0; i < len(s); i++ { |
| 256 | c := s[i] |
| 257 | switch { |
| 258 | default: |
| 259 | return false |
| 260 | case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': |
| 261 | // No '_' allowed here (in contrast to package net). |
| 262 | ok = true |
| 263 | partlen++ |
| 264 | case '0' <= c && c <= '9': |
| 265 | // fine |
| 266 | partlen++ |
| 267 | case c == '-': |
| 268 | // Byte before dash cannot be dot. |
| 269 | if last == '.' { |
| 270 | return false |
| 271 | } |
| 272 | partlen++ |
| 273 | case c == '.': |
| 274 | // Byte before dot cannot be dot, dash. |
| 275 | if last == '.' || last == '-' { |
| 276 | return false |
| 277 | } |
| 278 | if partlen > 63 || partlen == 0 { |
| 279 | return false |
| 280 | } |
| 281 | partlen = 0 |
| 282 | } |
| 283 | last = c |
| 284 | } |
| 285 | if last == '-' || partlen > 63 { |
| 286 | return false |
| 287 | } |
| 288 | |
| 289 | return ok |
| 290 | } |
| 291 | |
| 292 | var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") |
| 293 | |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 294 | func sanitizeCookieName(n string) string { |
Brad Fitzpatrick | f75ff01 | 2011-10-03 13:12:01 -0700 | [diff] [blame] | 295 | return cookieNameSanitizer.Replace(n) |
Petar Maymounkov | 8b35293 | 2011-04-14 15:05:02 -0700 | [diff] [blame] | 296 | } |
| 297 | |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 298 | // http://tools.ietf.org/html/rfc6265#section-4.1.1 |
| 299 | // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) |
| 300 | // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E |
| 301 | // ; US-ASCII characters excluding CTLs, |
| 302 | // ; whitespace DQUOTE, comma, semicolon, |
| 303 | // ; and backslash |
Volker Dobler | ed88076 | 2014-04-16 23:01:02 -0700 | [diff] [blame] | 304 | // We loosen this as spaces and commas are common in cookie values |
| 305 | // but we produce a quoted cookie-value in when value starts or ends |
| 306 | // with a comma or space. |
Brad Fitzpatrick | 2ae7737 | 2015-07-10 17:17:11 -0600 | [diff] [blame^] | 307 | // See https://golang.org/issue/7243 for the discussion. |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 308 | func sanitizeCookieValue(v string) string { |
Volker Dobler | ed88076 | 2014-04-16 23:01:02 -0700 | [diff] [blame] | 309 | v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) |
| 310 | if len(v) == 0 { |
| 311 | return v |
| 312 | } |
| 313 | if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' { |
| 314 | return `"` + v + `"` |
| 315 | } |
| 316 | return v |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 317 | } |
| 318 | |
| 319 | func validCookieValueByte(b byte) bool { |
Volker Dobler | ed88076 | 2014-04-16 23:01:02 -0700 | [diff] [blame] | 320 | return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' |
Brad Fitzpatrick | 17d803d | 2013-08-01 12:16:37 -0700 | [diff] [blame] | 321 | } |
| 322 | |
| 323 | // path-av = "Path=" path-value |
| 324 | // path-value = <any CHAR except CTLs or ";"> |
| 325 | func sanitizeCookiePath(v string) string { |
| 326 | return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v) |
| 327 | } |
| 328 | |
| 329 | func validCookiePathByte(b byte) bool { |
| 330 | return 0x20 <= b && b < 0x7f && b != ';' |
| 331 | } |
| 332 | |
| 333 | func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { |
| 334 | ok := true |
| 335 | for i := 0; i < len(v); i++ { |
| 336 | if valid(v[i]) { |
| 337 | continue |
| 338 | } |
| 339 | log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName) |
| 340 | ok = false |
| 341 | break |
| 342 | } |
| 343 | if ok { |
| 344 | return v |
| 345 | } |
| 346 | buf := make([]byte, 0, len(v)) |
| 347 | for i := 0; i < len(v); i++ { |
| 348 | if b := v[i]; valid(b) { |
| 349 | buf = append(buf, b) |
| 350 | } |
| 351 | } |
| 352 | return string(buf) |
Petar Maymounkov | 8b35293 | 2011-04-14 15:05:02 -0700 | [diff] [blame] | 353 | } |
| 354 | |
Nigel Tao | e59ad69 | 2014-09-25 10:21:52 +1000 | [diff] [blame] | 355 | func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { |
Volker Dobler | ed88076 | 2014-04-16 23:01:02 -0700 | [diff] [blame] | 356 | // Strip the quotes, if present. |
Nigel Tao | e59ad69 | 2014-09-25 10:21:52 +1000 | [diff] [blame] | 357 | if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { |
Volker Dobler | ed88076 | 2014-04-16 23:01:02 -0700 | [diff] [blame] | 358 | raw = raw[1 : len(raw)-1] |
| 359 | } |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 360 | for i := 0; i < len(raw); i++ { |
Volker Dobler | ed88076 | 2014-04-16 23:01:02 -0700 | [diff] [blame] | 361 | if !validCookieValueByte(raw[i]) { |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 362 | return "", false |
| 363 | } |
| 364 | } |
| 365 | return raw, true |
| 366 | } |
| 367 | |
| 368 | func isCookieNameValid(raw string) bool { |
Brad Fitzpatrick | 9462bce | 2015-06-29 18:10:43 -0700 | [diff] [blame] | 369 | if raw == "" { |
| 370 | return false |
| 371 | } |
Pascal S. de Kloe | b678c19 | 2012-04-23 10:26:10 -0700 | [diff] [blame] | 372 | return strings.IndexFunc(raw, isNotToken) < 0 |
Petar Maymounkov | 3e042eb | 2011-03-07 12:08:39 -0500 | [diff] [blame] | 373 | } |