Skip to content

Commit ed88076

Browse files
vdoblernigeltao
authored andcommitted
net/http: allow commas and spaces in cookie values
According to RFC 6265 a cookie value may contain neither commas nor spaces but such values are very common in the wild and browsers handle them very well so we'll allow both commas and spaces. Values starting or ending in a comma or a space are sent in the quoted form to prevent missinterpetations. RFC 6265 conforming values are handled as before and semicolons, backslashes and double-quotes are still disallowed. Fixes #7243 LGTM=nigeltao R=nigeltao CC=bradfitz, golang-codereviews https://golang.org/cl/86050045
1 parent f8f34c3 commit ed88076

File tree

2 files changed

+97
-38
lines changed

2 files changed

+97
-38
lines changed

src/pkg/net/http/cookie.go

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,7 @@ func readSetCookies(h Header) []*Cookie {
7676
attr, val = attr[:j], attr[j+1:]
7777
}
7878
lowerAttr := strings.ToLower(attr)
79-
parseCookieValueFn := parseCookieValue
80-
if lowerAttr == "expires" {
81-
parseCookieValueFn = parseCookieExpiresValue
82-
}
83-
val, success = parseCookieValueFn(val)
79+
val, success = parseCookieValue(val)
8480
if !success {
8581
c.Unparsed = append(c.Unparsed, parts[i])
8682
continue
@@ -298,12 +294,23 @@ func sanitizeCookieName(n string) string {
298294
// ; US-ASCII characters excluding CTLs,
299295
// ; whitespace DQUOTE, comma, semicolon,
300296
// ; and backslash
297+
// We loosen this as spaces and commas are common in cookie values
298+
// but we produce a quoted cookie-value in when value starts or ends
299+
// with a comma or space.
300+
// See http://golang.org/issue/7243 for the discussion.
301301
func sanitizeCookieValue(v string) string {
302-
return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
302+
v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
303+
if len(v) == 0 {
304+
return v
305+
}
306+
if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' {
307+
return `"` + v + `"`
308+
}
309+
return v
303310
}
304311

305312
func validCookieValueByte(b byte) bool {
306-
return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\'
313+
return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
307314
}
308315

309316
// path-av = "Path=" path-value
@@ -338,38 +345,13 @@ func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
338345
return string(buf)
339346
}
340347

341-
func unquoteCookieValue(v string) string {
342-
if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
343-
return v[1 : len(v)-1]
344-
}
345-
return v
346-
}
347-
348-
func isCookieByte(c byte) bool {
349-
switch {
350-
case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
351-
0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
352-
return true
353-
}
354-
return false
355-
}
356-
357-
func isCookieExpiresByte(c byte) (ok bool) {
358-
return isCookieByte(c) || c == ',' || c == ' '
359-
}
360-
361348
func parseCookieValue(raw string) (string, bool) {
362-
return parseCookieValueUsing(raw, isCookieByte)
363-
}
364-
365-
func parseCookieExpiresValue(raw string) (string, bool) {
366-
return parseCookieValueUsing(raw, isCookieExpiresByte)
367-
}
368-
369-
func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) {
370-
raw = unquoteCookieValue(raw)
349+
// Strip the quotes, if present.
350+
if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
351+
raw = raw[1 : len(raw)-1]
352+
}
371353
for i := 0; i < len(raw); i++ {
372-
if !validByte(raw[i]) {
354+
if !validCookieValueByte(raw[i]) {
373355
return "", false
374356
}
375357
}

src/pkg/net/http/cookie_test.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,44 @@ var writeSetCookiesTests = []struct {
5252
&Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
5353
"cookie-8=eight",
5454
},
55+
// The "special" cookies have values containing commas or spaces which
56+
// are disallowed by RFC 6265 but are common in the wild.
57+
{
58+
&Cookie{Name: "special-1", Value: "a z"},
59+
`special-1=a z`,
60+
},
61+
{
62+
&Cookie{Name: "special-2", Value: " z"},
63+
`special-2=" z"`,
64+
},
65+
{
66+
&Cookie{Name: "special-3", Value: "a "},
67+
`special-3="a "`,
68+
},
69+
{
70+
&Cookie{Name: "special-4", Value: " "},
71+
`special-4=" "`,
72+
},
73+
{
74+
&Cookie{Name: "special-5", Value: "a,z"},
75+
`special-5=a,z`,
76+
},
77+
{
78+
&Cookie{Name: "special-6", Value: ",z"},
79+
`special-6=",z"`,
80+
},
81+
{
82+
&Cookie{Name: "special-7", Value: "a,"},
83+
`special-7="a,"`,
84+
},
85+
{
86+
&Cookie{Name: "special-8", Value: ","},
87+
`special-8=","`,
88+
},
89+
{
90+
&Cookie{Name: "empty-value", Value: ""},
91+
`empty-value=`,
92+
},
5593
}
5694

5795
func TestWriteSetCookies(t *testing.T) {
@@ -178,6 +216,40 @@ var readSetCookiesTests = []struct {
178216
Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly",
179217
}},
180218
},
219+
// Make sure we can properly read back the Set-Cookie headers we create
220+
// for values containing spaces or commas:
221+
{
222+
Header{"Set-Cookie": {`special-1=a z`}},
223+
[]*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
224+
},
225+
{
226+
Header{"Set-Cookie": {`special-2=" z"`}},
227+
[]*Cookie{{Name: "special-2", Value: " z", Raw: `special-2=" z"`}},
228+
},
229+
{
230+
Header{"Set-Cookie": {`special-3="a "`}},
231+
[]*Cookie{{Name: "special-3", Value: "a ", Raw: `special-3="a "`}},
232+
},
233+
{
234+
Header{"Set-Cookie": {`special-4=" "`}},
235+
[]*Cookie{{Name: "special-4", Value: " ", Raw: `special-4=" "`}},
236+
},
237+
{
238+
Header{"Set-Cookie": {`special-5=a,z`}},
239+
[]*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
240+
},
241+
{
242+
Header{"Set-Cookie": {`special-6=",z"`}},
243+
[]*Cookie{{Name: "special-6", Value: ",z", Raw: `special-6=",z"`}},
244+
},
245+
{
246+
Header{"Set-Cookie": {`special-7=a,`}},
247+
[]*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
248+
},
249+
{
250+
Header{"Set-Cookie": {`special-8=","`}},
251+
[]*Cookie{{Name: "special-8", Value: ",", Raw: `special-8=","`}},
252+
},
181253

182254
// TODO(bradfitz): users have reported seeing this in the
183255
// wild, but do browsers handle it? RFC 6265 just says "don't
@@ -264,9 +336,14 @@ func TestCookieSanitizeValue(t *testing.T) {
264336
in, want string
265337
}{
266338
{"foo", "foo"},
267-
{"foo bar", "foobar"},
339+
{"foo;bar", "foobar"},
340+
{"foo\\bar", "foobar"},
341+
{"foo\"bar", "foobar"},
268342
{"\x00\x7e\x7f\x80", "\x7e"},
269343
{`"withquotes"`, "withquotes"},
344+
{"a z", "a z"},
345+
{" z", `" z"`},
346+
{"a ", `"a "`},
270347
}
271348
for _, tt := range tests {
272349
if got := sanitizeCookieValue(tt.in); got != tt.want {

0 commit comments

Comments
 (0)