//
// See https://tools.ietf.org/html/rfc6265 for details.
type Cookie struct {
- Name string
- Value string
+ Name string
+ Value string
+ Quoted bool // indicates whether the Value was originally quoted
Path string // optional
Domain string // optional
if !isCookieNameValid(name) {
return nil, errInvalidCookieName
}
- value, found = parseCookieValue(value, true)
+ value, quoted, found := parseCookieValue(value, true)
if !found {
return nil, errInvalidCookieValue
}
- cookies = append(cookies, &Cookie{Name: name, Value: value})
+ cookies = append(cookies, &Cookie{Name: name, Value: value, Quoted: quoted})
}
return cookies, nil
}
if !isCookieNameValid(name) {
return nil, errInvalidCookieName
}
- value, ok = parseCookieValue(value, true)
+ value, quoted, ok := parseCookieValue(value, true)
if !ok {
return nil, errInvalidCookieValue
}
c := &Cookie{
- Name: name,
- Value: value,
- Raw: line,
+ Name: name,
+ Value: value,
+ Quoted: quoted,
+ Raw: line,
}
for i := 1; i < len(parts); i++ {
parts[i] = textproto.TrimString(parts[i])
if !isASCII {
continue
}
- val, ok = parseCookieValue(val, false)
+ val, _, ok = parseCookieValue(val, false)
if !ok {
c.Unparsed = append(c.Unparsed, parts[i])
continue
b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength)
b.WriteString(c.Name)
b.WriteRune('=')
- b.WriteString(sanitizeCookieValue(c.Value))
+ b.WriteString(sanitizeCookieValue(c.Value, c.Quoted))
if len(c.Path) > 0 {
b.WriteString("; Path=")
if filter != "" && filter != name {
continue
}
- val, ok := parseCookieValue(val, true)
+ val, quoted, ok := parseCookieValue(val, true)
if !ok {
continue
}
- cookies = append(cookies, &Cookie{Name: name, Value: val})
+ cookies = append(cookies, &Cookie{Name: name, Value: val, Quoted: quoted})
}
}
return cookies
}
// sanitizeCookieValue produces a suitable cookie-value from v.
+// It receives a quoted bool indicating whether the value was originally
+// quoted.
// https://tools.ietf.org/html/rfc6265#section-4.1.1
//
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
// ; and backslash
//
// We loosen this as spaces and commas are common in cookie values
-// but we produce a quoted cookie-value if and only if v contains
-// commas or spaces.
+// thus we produce a quoted cookie-value if v contains commas or spaces.
// See https://golang.org/issue/7243 for the discussion.
-func sanitizeCookieValue(v string) string {
+func sanitizeCookieValue(v string, quoted bool) string {
v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
if len(v) == 0 {
return v
}
- if strings.ContainsAny(v, " ,") {
+ if strings.ContainsAny(v, " ,") || quoted {
return `"` + v + `"`
}
return v
return string(buf)
}
-func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
+// parseCookieValue parses a cookie value according to RFC 6265.
+// If allowDoubleQuote is true, parseCookieValue will consider that it
+// is parsing the cookie-value;
+// otherwise, it will consider that it is parsing a cookie-av value
+// (cookie attribute-value).
+//
+// It returns the parsed cookie value, a boolean indicating whether the
+// parsing was successful, and a boolean indicating whether the parsed
+// value was enclosed in double quotes.
+func parseCookieValue(raw string, allowDoubleQuote bool) (value string, quoted, ok bool) {
// Strip the quotes, if present.
if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
raw = raw[1 : len(raw)-1]
+ quoted = true
}
for i := 0; i < len(raw); i++ {
if !validCookieValueByte(raw[i]) {
- return "", false
+ return "", quoted, false
}
}
- return raw, true
+ return raw, quoted, true
}
func isCookieNameValid(raw string) bool {
&Cookie{Name: "a\rb", Value: "v"},
``,
},
+ // Quoted values (issue #46443)
+ {
+ &Cookie{Name: "cookie", Value: "quoted", Quoted: true},
+ `cookie="quoted"`,
+ },
+ {
+ &Cookie{Name: "cookie", Value: "quoted with spaces", Quoted: true},
+ `cookie="quoted with spaces"`,
+ },
+ {
+ &Cookie{Name: "cookie", Value: "quoted,with,commas", Quoted: true},
+ `cookie="quoted,with,commas"`,
+ },
}
func TestWriteSetCookies(t *testing.T) {
},
"cookie-1=v$1; cookie-2=v$2; cookie-3=v$3",
},
+ // Quoted values (issue #46443)
+ {
+ []*Cookie{
+ {Name: "cookie-1", Value: "quoted", Quoted: true},
+ {Name: "cookie-2", Value: "quoted with spaces", Quoted: true},
+ {Name: "cookie-3", Value: "quoted,with,commas", Quoted: true},
+ },
+ `cookie-1="quoted"; cookie-2="quoted with spaces"; cookie-3="quoted,with,commas"`,
+ },
}
func TestAddCookie(t *testing.T) {
},
{
Header{"Set-Cookie": {`special-2=" z"`}},
- []*Cookie{{Name: "special-2", Value: " z", Raw: `special-2=" z"`}},
+ []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
},
{
Header{"Set-Cookie": {`special-3="a "`}},
- []*Cookie{{Name: "special-3", Value: "a ", Raw: `special-3="a "`}},
+ []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
},
{
Header{"Set-Cookie": {`special-4=" "`}},
- []*Cookie{{Name: "special-4", Value: " ", Raw: `special-4=" "`}},
+ []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
},
{
Header{"Set-Cookie": {`special-5=a,z`}},
},
{
Header{"Set-Cookie": {`special-6=",z"`}},
- []*Cookie{{Name: "special-6", Value: ",z", Raw: `special-6=",z"`}},
+ []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
},
{
Header{"Set-Cookie": {`special-7=a,`}},
},
{
Header{"Set-Cookie": {`special-8=","`}},
- []*Cookie{{Name: "special-8", Value: ",", Raw: `special-8=","`}},
+ []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
},
// Make sure we can properly read back the Set-Cookie headers
// for names containing spaces:
{
Header{"Set-Cookie": {`special-9 =","`}},
- []*Cookie{{Name: "special-9", Value: ",", Raw: `special-9 =","`}},
+ []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
+ },
+ // Quoted values (issue #46443)
+ {
+ Header{"Set-Cookie": {`cookie="quoted"`}},
+ []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
},
// TODO(bradfitz): users have reported seeing this in the
Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
"",
[]*Cookie{
- {Name: "Cookie-1", Value: "v$1"},
- {Name: "c2", Value: "v2"},
+ {Name: "Cookie-1", Value: "v$1", Quoted: true},
+ {Name: "c2", Value: "v2", Quoted: true},
},
},
{
Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
"",
[]*Cookie{
- {Name: "Cookie-1", Value: "v$1"},
+ {Name: "Cookie-1", Value: "v$1", Quoted: true},
{Name: "c2", Value: "v2"},
},
},
log.SetOutput(&logbuf)
tests := []struct {
- in, want string
+ in string
+ quoted bool
+ want string
}{
- {"foo", "foo"},
- {"foo;bar", "foobar"},
- {"foo\\bar", "foobar"},
- {"foo\"bar", "foobar"},
- {"\x00\x7e\x7f\x80", "\x7e"},
- {`"withquotes"`, "withquotes"},
- {"a z", `"a z"`},
- {" z", `" z"`},
- {"a ", `"a "`},
- {"a,z", `"a,z"`},
- {",z", `",z"`},
- {"a,", `"a,"`},
+ {"foo", false, "foo"},
+ {"foo;bar", false, "foobar"},
+ {"foo\\bar", false, "foobar"},
+ {"foo\"bar", false, "foobar"},
+ {"\x00\x7e\x7f\x80", false, "\x7e"},
+ {`withquotes`, true, `"withquotes"`},
+ {`"withquotes"`, true, `"withquotes"`}, // double quotes are not valid octets
+ {"a z", false, `"a z"`},
+ {" z", false, `" z"`},
+ {"a ", false, `"a "`},
+ {"a,z", false, `"a,z"`},
+ {",z", false, `",z"`},
+ {"a,", false, `"a,"`},
}
for _, tt := range tests {
- if got := sanitizeCookieValue(tt.in); got != tt.want {
+ if got := sanitizeCookieValue(tt.in, tt.quoted); got != tt.want {
t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want)
}
}
},
{
line: `Cookie-1="v$1";c2="v2"`,
- cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}},
+ cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Quoted: true}, {Name: "c2", Value: "v2", Quoted: true}},
},
{
line: "k1=",
},
{
line: `special-2=" z"`,
- cookie: &Cookie{Name: "special-2", Value: " z", Raw: `special-2=" z"`},
+ cookie: &Cookie{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`},
},
{
line: `special-3="a "`,
- cookie: &Cookie{Name: "special-3", Value: "a ", Raw: `special-3="a "`},
+ cookie: &Cookie{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`},
},
{
line: `special-4=" "`,
- cookie: &Cookie{Name: "special-4", Value: " ", Raw: `special-4=" "`},
+ cookie: &Cookie{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`},
},
{
line: `special-5=a,z`,
},
{
line: `special-6=",z"`,
- cookie: &Cookie{Name: "special-6", Value: ",z", Raw: `special-6=",z"`},
+ cookie: &Cookie{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`},
},
{
line: `special-7=a,`,
},
{
line: `special-8=","`,
- cookie: &Cookie{Name: "special-8", Value: ",", Raw: `special-8=","`},
+ cookie: &Cookie{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`},
},
// Make sure we can properly read back the Set-Cookie headers
// for names containing spaces:
{
line: `special-9 =","`,
- cookie: &Cookie{Name: "special-9", Value: ",", Raw: `special-9 =","`},
+ cookie: &Cookie{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`},
},
{
line: "",