Skip to content

Commit a639078

Browse files
nunogoncalves03neild
authored andcommitted
net/http: add field Cookie.Quoted bool
The current implementation of the http package strips double quotes from the cookie-value during parsing, resulting in the serialized cookie not including them. This patch addresses this limitation by introducing a new field to track whether the original value was enclosed in quotes. Additionally, the internal representation of a cookie in the cookiejar package has been adjusted to align with the new representation. The syntax of cookies is outlined in RFC 6265 Section 4.1.1: https://datatracker.ietf.org/doc/html/rfc6265\#section-4.1.1 Fixes #46443 Change-Id: Iac12a56397d77a6060a75757ab0daeacc60457f3 GitHub-Last-Rev: a76440e GitHub-Pull-Request: #66752 Reviewed-on: https://go-review.googlesource.com/c/go/+/577755 Reviewed-by: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 0106462 commit a639078

File tree

7 files changed

+124
-53
lines changed

7 files changed

+124
-53
lines changed

api/next/46443.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg net/http, type Cookie struct, Quoted bool #46443
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[`Cookie`](/pkg/net/http#Cookie) now preserves double quotes surrounding
2+
a cookie value. The new `Cookie.Quoted` field indicates whether the
3+
`Cookie.Value` was originally quoted.

src/net/http/cookie.go

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import (
2121
//
2222
// See https://tools.ietf.org/html/rfc6265 for details.
2323
type Cookie struct {
24-
Name string
25-
Value string
24+
Name string
25+
Value string
26+
Quoted bool // indicates whether the Value was originally quoted
2627

2728
Path string // optional
2829
Domain string // optional
@@ -80,11 +81,11 @@ func ParseCookie(line string) ([]*Cookie, error) {
8081
if !isCookieNameValid(name) {
8182
return nil, errInvalidCookieName
8283
}
83-
value, found = parseCookieValue(value, true)
84+
value, quoted, found := parseCookieValue(value, true)
8485
if !found {
8586
return nil, errInvalidCookieValue
8687
}
87-
cookies = append(cookies, &Cookie{Name: name, Value: value})
88+
cookies = append(cookies, &Cookie{Name: name, Value: value, Quoted: quoted})
8889
}
8990
return cookies, nil
9091
}
@@ -105,14 +106,15 @@ func ParseSetCookie(line string) (*Cookie, error) {
105106
if !isCookieNameValid(name) {
106107
return nil, errInvalidCookieName
107108
}
108-
value, ok = parseCookieValue(value, true)
109+
value, quoted, ok := parseCookieValue(value, true)
109110
if !ok {
110111
return nil, errInvalidCookieValue
111112
}
112113
c := &Cookie{
113-
Name: name,
114-
Value: value,
115-
Raw: line,
114+
Name: name,
115+
Value: value,
116+
Quoted: quoted,
117+
Raw: line,
116118
}
117119
for i := 1; i < len(parts); i++ {
118120
parts[i] = textproto.TrimString(parts[i])
@@ -125,7 +127,7 @@ func ParseSetCookie(line string) (*Cookie, error) {
125127
if !isASCII {
126128
continue
127129
}
128-
val, ok = parseCookieValue(val, false)
130+
val, _, ok = parseCookieValue(val, false)
129131
if !ok {
130132
c.Unparsed = append(c.Unparsed, parts[i])
131133
continue
@@ -229,7 +231,7 @@ func (c *Cookie) String() string {
229231
b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength)
230232
b.WriteString(c.Name)
231233
b.WriteRune('=')
232-
b.WriteString(sanitizeCookieValue(c.Value))
234+
b.WriteString(sanitizeCookieValue(c.Value, c.Quoted))
233235

234236
if len(c.Path) > 0 {
235237
b.WriteString("; Path=")
@@ -341,11 +343,11 @@ func readCookies(h Header, filter string) []*Cookie {
341343
if filter != "" && filter != name {
342344
continue
343345
}
344-
val, ok := parseCookieValue(val, true)
346+
val, quoted, ok := parseCookieValue(val, true)
345347
if !ok {
346348
continue
347349
}
348-
cookies = append(cookies, &Cookie{Name: name, Value: val})
350+
cookies = append(cookies, &Cookie{Name: name, Value: val, Quoted: quoted})
349351
}
350352
}
351353
return cookies
@@ -430,6 +432,8 @@ func sanitizeCookieName(n string) string {
430432
}
431433

432434
// sanitizeCookieValue produces a suitable cookie-value from v.
435+
// It receives a quoted bool indicating whether the value was originally
436+
// quoted.
433437
// https://tools.ietf.org/html/rfc6265#section-4.1.1
434438
//
435439
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
@@ -439,15 +443,14 @@ func sanitizeCookieName(n string) string {
439443
// ; and backslash
440444
//
441445
// We loosen this as spaces and commas are common in cookie values
442-
// but we produce a quoted cookie-value if and only if v contains
443-
// commas or spaces.
446+
// thus we produce a quoted cookie-value if v contains commas or spaces.
444447
// See https://golang.org/issue/7243 for the discussion.
445-
func sanitizeCookieValue(v string) string {
448+
func sanitizeCookieValue(v string, quoted bool) string {
446449
v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
447450
if len(v) == 0 {
448451
return v
449452
}
450-
if strings.ContainsAny(v, " ,") {
453+
if strings.ContainsAny(v, " ,") || quoted {
451454
return `"` + v + `"`
452455
}
453456
return v
@@ -489,17 +492,27 @@ func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
489492
return string(buf)
490493
}
491494

492-
func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
495+
// parseCookieValue parses a cookie value according to RFC 6265.
496+
// If allowDoubleQuote is true, parseCookieValue will consider that it
497+
// is parsing the cookie-value;
498+
// otherwise, it will consider that it is parsing a cookie-av value
499+
// (cookie attribute-value).
500+
//
501+
// It returns the parsed cookie value, a boolean indicating whether the
502+
// parsing was successful, and a boolean indicating whether the parsed
503+
// value was enclosed in double quotes.
504+
func parseCookieValue(raw string, allowDoubleQuote bool) (value string, quoted, ok bool) {
493505
// Strip the quotes, if present.
494506
if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
495507
raw = raw[1 : len(raw)-1]
508+
quoted = true
496509
}
497510
for i := 0; i < len(raw); i++ {
498511
if !validCookieValueByte(raw[i]) {
499-
return "", false
512+
return "", quoted, false
500513
}
501514
}
502-
return raw, true
515+
return raw, quoted, true
503516
}
504517

505518
func isCookieNameValid(raw string) bool {

src/net/http/cookie_test.go

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,19 @@ var writeSetCookiesTests = []struct {
147147
&Cookie{Name: "a\rb", Value: "v"},
148148
``,
149149
},
150+
// Quoted values (issue #46443)
151+
{
152+
&Cookie{Name: "cookie", Value: "quoted", Quoted: true},
153+
`cookie="quoted"`,
154+
},
155+
{
156+
&Cookie{Name: "cookie", Value: "quoted with spaces", Quoted: true},
157+
`cookie="quoted with spaces"`,
158+
},
159+
{
160+
&Cookie{Name: "cookie", Value: "quoted,with,commas", Quoted: true},
161+
`cookie="quoted,with,commas"`,
162+
},
150163
}
151164

152165
func TestWriteSetCookies(t *testing.T) {
@@ -215,6 +228,15 @@ var addCookieTests = []struct {
215228
},
216229
"cookie-1=v$1; cookie-2=v$2; cookie-3=v$3",
217230
},
231+
// Quoted values (issue #46443)
232+
{
233+
[]*Cookie{
234+
{Name: "cookie-1", Value: "quoted", Quoted: true},
235+
{Name: "cookie-2", Value: "quoted with spaces", Quoted: true},
236+
{Name: "cookie-3", Value: "quoted,with,commas", Quoted: true},
237+
},
238+
`cookie-1="quoted"; cookie-2="quoted with spaces"; cookie-3="quoted,with,commas"`,
239+
},
218240
}
219241

220242
func TestAddCookie(t *testing.T) {
@@ -326,37 +348,42 @@ var readSetCookiesTests = []struct {
326348
},
327349
{
328350
Header{"Set-Cookie": {`special-2=" z"`}},
329-
[]*Cookie{{Name: "special-2", Value: " z", Raw: `special-2=" z"`}},
351+
[]*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
330352
},
331353
{
332354
Header{"Set-Cookie": {`special-3="a "`}},
333-
[]*Cookie{{Name: "special-3", Value: "a ", Raw: `special-3="a "`}},
355+
[]*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
334356
},
335357
{
336358
Header{"Set-Cookie": {`special-4=" "`}},
337-
[]*Cookie{{Name: "special-4", Value: " ", Raw: `special-4=" "`}},
359+
[]*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
338360
},
339361
{
340362
Header{"Set-Cookie": {`special-5=a,z`}},
341363
[]*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
342364
},
343365
{
344366
Header{"Set-Cookie": {`special-6=",z"`}},
345-
[]*Cookie{{Name: "special-6", Value: ",z", Raw: `special-6=",z"`}},
367+
[]*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
346368
},
347369
{
348370
Header{"Set-Cookie": {`special-7=a,`}},
349371
[]*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
350372
},
351373
{
352374
Header{"Set-Cookie": {`special-8=","`}},
353-
[]*Cookie{{Name: "special-8", Value: ",", Raw: `special-8=","`}},
375+
[]*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
354376
},
355377
// Make sure we can properly read back the Set-Cookie headers
356378
// for names containing spaces:
357379
{
358380
Header{"Set-Cookie": {`special-9 =","`}},
359-
[]*Cookie{{Name: "special-9", Value: ",", Raw: `special-9 =","`}},
381+
[]*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
382+
},
383+
// Quoted values (issue #46443)
384+
{
385+
Header{"Set-Cookie": {`cookie="quoted"`}},
386+
[]*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
360387
},
361388

362389
// TODO(bradfitz): users have reported seeing this in the
@@ -425,15 +452,15 @@ var readCookiesTests = []struct {
425452
Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
426453
"",
427454
[]*Cookie{
428-
{Name: "Cookie-1", Value: "v$1"},
429-
{Name: "c2", Value: "v2"},
455+
{Name: "Cookie-1", Value: "v$1", Quoted: true},
456+
{Name: "c2", Value: "v2", Quoted: true},
430457
},
431458
},
432459
{
433460
Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
434461
"",
435462
[]*Cookie{
436-
{Name: "Cookie-1", Value: "v$1"},
463+
{Name: "Cookie-1", Value: "v$1", Quoted: true},
437464
{Name: "c2", Value: "v2"},
438465
},
439466
},
@@ -486,23 +513,26 @@ func TestCookieSanitizeValue(t *testing.T) {
486513
log.SetOutput(&logbuf)
487514

488515
tests := []struct {
489-
in, want string
516+
in string
517+
quoted bool
518+
want string
490519
}{
491-
{"foo", "foo"},
492-
{"foo;bar", "foobar"},
493-
{"foo\\bar", "foobar"},
494-
{"foo\"bar", "foobar"},
495-
{"\x00\x7e\x7f\x80", "\x7e"},
496-
{`"withquotes"`, "withquotes"},
497-
{"a z", `"a z"`},
498-
{" z", `" z"`},
499-
{"a ", `"a "`},
500-
{"a,z", `"a,z"`},
501-
{",z", `",z"`},
502-
{"a,", `"a,"`},
520+
{"foo", false, "foo"},
521+
{"foo;bar", false, "foobar"},
522+
{"foo\\bar", false, "foobar"},
523+
{"foo\"bar", false, "foobar"},
524+
{"\x00\x7e\x7f\x80", false, "\x7e"},
525+
{`withquotes`, true, `"withquotes"`},
526+
{`"withquotes"`, true, `"withquotes"`}, // double quotes are not valid octets
527+
{"a z", false, `"a z"`},
528+
{" z", false, `" z"`},
529+
{"a ", false, `"a "`},
530+
{"a,z", false, `"a,z"`},
531+
{",z", false, `",z"`},
532+
{"a,", false, `"a,"`},
503533
}
504534
for _, tt := range tests {
505-
if got := sanitizeCookieValue(tt.in); got != tt.want {
535+
if got := sanitizeCookieValue(tt.in, tt.quoted); got != tt.want {
506536
t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want)
507537
}
508538
}
@@ -668,7 +698,7 @@ func TestParseCookie(t *testing.T) {
668698
},
669699
{
670700
line: `Cookie-1="v$1";c2="v2"`,
671-
cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}},
701+
cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Quoted: true}, {Name: "c2", Value: "v2", Quoted: true}},
672702
},
673703
{
674704
line: "k1=",
@@ -800,37 +830,37 @@ func TestParseSetCookie(t *testing.T) {
800830
},
801831
{
802832
line: `special-2=" z"`,
803-
cookie: &Cookie{Name: "special-2", Value: " z", Raw: `special-2=" z"`},
833+
cookie: &Cookie{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`},
804834
},
805835
{
806836
line: `special-3="a "`,
807-
cookie: &Cookie{Name: "special-3", Value: "a ", Raw: `special-3="a "`},
837+
cookie: &Cookie{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`},
808838
},
809839
{
810840
line: `special-4=" "`,
811-
cookie: &Cookie{Name: "special-4", Value: " ", Raw: `special-4=" "`},
841+
cookie: &Cookie{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`},
812842
},
813843
{
814844
line: `special-5=a,z`,
815845
cookie: &Cookie{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`},
816846
},
817847
{
818848
line: `special-6=",z"`,
819-
cookie: &Cookie{Name: "special-6", Value: ",z", Raw: `special-6=",z"`},
849+
cookie: &Cookie{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`},
820850
},
821851
{
822852
line: `special-7=a,`,
823853
cookie: &Cookie{Name: "special-7", Value: "a,", Raw: `special-7=a,`},
824854
},
825855
{
826856
line: `special-8=","`,
827-
cookie: &Cookie{Name: "special-8", Value: ",", Raw: `special-8=","`},
857+
cookie: &Cookie{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`},
828858
},
829859
// Make sure we can properly read back the Set-Cookie headers
830860
// for names containing spaces:
831861
{
832862
line: `special-9 =","`,
833-
cookie: &Cookie{Name: "special-9", Value: ",", Raw: `special-9 =","`},
863+
cookie: &Cookie{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`},
834864
},
835865
{
836866
line: "",

src/net/http/cookiejar/jar.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func New(o *Options) (*Jar, error) {
9292
type entry struct {
9393
Name string
9494
Value string
95+
Quoted bool
9596
Domain string
9697
Path string
9798
SameSite string
@@ -220,7 +221,7 @@ func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
220221
return s[i].seqNum < s[j].seqNum
221222
})
222223
for _, e := range selected {
223-
cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
224+
cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value, Quoted: e.Quoted})
224225
}
225226

226227
return cookies
@@ -429,6 +430,7 @@ func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e e
429430
}
430431

431432
e.Value = c.Value
433+
e.Quoted = c.Quoted
432434
e.Secure = c.Secure
433435
e.HttpOnly = c.HttpOnly
434436

0 commit comments

Comments
 (0)