For the RFC 6265, the double-quotes are part of the cookie value but the functions and methods in the standard library that operates on cookies treat them as if they were not part of it.
The SetCookie
function does not allow to send a cookie, that conforms to the spec, with a double-quoted value and the (*Request).Cookie
method strips the quotes from the value despite the double-quotes are part of it.
The syntax in the RFC 6265 is
cookie-pair = cookie-name "=" cookie-value
...
cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
but it has been implemented in the standard library as
cookie-pair = cookie-name "=" ( cookie-value / ( DQUOTE cookie-value DQUOTE ) )
...
cookie-value = *cookie-octet
The author of the RFC 6265 has confirmed in https://lists.w3.org/Archives/Public/ietf-http-wg/2017JanMar/0229.html that this was the intent.
The draft https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02 added this note to the spec
Per the grammar above, the cookie-value MAY be wrapped in DQUOTE
characters. Note that in this case, the initial and trailing DQUOTE
characters are not stripped. They are part of the cookie-value, and
will be included in Cookie headers sent to the server.
and in the appendix reports this discussion https://issues.apache.org/jira/browse/HTTPCLIENT-1006.
Comment From: vdobler
For the RFC 6265, the double-quotes are part of the cookie value but the functions and methods in the standard library that operates on cookies treat them as if they were not part of it.
This is wrong. The optional double-quotes around a cookie are not part of the value. The standard library is correct.
Comment From: gazerro
What version of Go are you using (go version
)?
$ go version go version go1.16.4 darwin/amd64
Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (go env
)?
go env
Output
$ go env GO111MODULE="on" GOARCH="amd64" GOBIN="" GOCACHE="/Users/marco/Library/Caches/go-build" GOENV="/Users/marco/Library/Application Support/go/env" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOINSECURE="" GOMODCACHE="/Users/marco/go/pkg/mod" GOOS="darwin" GOPATH="/Users/marco/go" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64" GOVCS="" GOVERSION="go1.16.4" GCCGO="gccgo" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/z9/xrln_qks56jbzxjbhs04fpm80000gn/T/go-build950868117=/tmp/go-build -gno-record-gcc-switches -fno-common"
What did you do?
Call an HTTP server that returns cookies with double-quoted values using http.Client
.
https://play.golang.org/p/Ka8G_NWPu0n
What did you expect to see?
I expect to see
Cookie: [a=; b=v; c=""; d="v"]
The client sends the cookies to the server preserving the values without stripping the double-quotes.
I also tested it, using this server, with all major browsers (Chrome, Firefox, Safari, Edge, Opera) on MacOS, Windows, iOS and Android with the latest versions and outdated versions (as Internet Explorer 6 on Windows XP, Safari 4 on MacOS Snow Leopard, iPhone 4, default browser on Android 4.4.2) and all browsers display
Cookie: [a=; b=v; c=""; d="v"]
What did you see instead?
Cookie: [a=; b=v; c=; d=v]
The client sends the cookies stripping the double-quotes.
Comment From: networkimprov
cc @neild @empijei @fraenkel
Comment From: fraenkel
I believe there is some confusion over what the Cookie.Value represents. My reading is that its the cookie-octets
. The double quotes are inserted as necessary (spaces and commas). There is no way to force the value to always be double quoted.
Comment From: neild
RFC 6265 is quite clear that double-quotes are part of the cookie-value
when present. The standards-track RFC 6265-bis emphasizes this point, but the grammar in RFC 6265 is unambiguous.
Empirically, ("net/http".Cookie).Value
contains the unquoted cookie-octet
s from the cookie-value
. The (*Cookie.String)
function adds surrounding DQUOTE characters if the value contains a space or comma and strips any existing DQUOTE characters from the Value. (Interestingly, the only time (*Cookie).String
returns a quoted value is when the value is invalid under RFC 6265, which forbids spaces and commas in values.) The (*Request).Cookie
function removes DQUOTE characters from cookies.
Comment From: gazerro
After some investigation, with this message I will explain the source of the problem
- the
Cookie.Value
field does not represent the cookie value
and its consequences
- a
Cookie.Value
value cannot be used to represent cookies with values in the formDQUOTE *cookie-octet DQUOTE
- no implementation of the
http.CookieJar
interface can be standard compliant - the
http.Client
type does not conform the RFC 6265 when storing cookies
Also, I will propose some solutions.
Source of the problem
The Cookie.Value
field does not represent the cookie value (cookie-value
in the grammar of the RFC 6265) but *cookie-octect
(as @fraenkel and @neild also noted, although not necessarily as a problem).
Consequences
Consequently, a Cookie.Value
value, apart from non standard compliant cookies, cannot be used to represent cookies with values in the form DQUOTE *cookie-octet DQUOTE
.
A type that implements the http.CookieJar
interface, such as the cookiejar.Jar
type, cannot be used to store cookies with DQUOTE
because http.CookieJar
methods receive and return cookies represented with http.Cookie
values. The cookiejar.Jar
type is documented as an in-memory RFC 6265-compliant http.CookieJar
, but as I pointed out, no implementation of the http.CookieJar
interface can conform to the standard.
http.Client
uses the http.CookieJar
interface to "insert relevant cookies into every outbound Request and is updated with the cookie values of every inbound Response" as required for user agents by RFC 6265. It also requires that the value be stored as is but this is not the case because a http.CookieJar
cannot store cookies with value in the form DQUOTE *cookie-octet DQUOTE
.
So, if a http.Client
with a Jar is used, a cookie received as
Set-Cookie: name="value"
is sent to the server as
Cookie: name=value
instead of
Cookie: name="value"
Note that all the major browsers, latest and older versions, do not alter the cookie value sent to the server.
Solutions
I propose three alternative solutions
a) Standard compliant cookies received with a Set-Cookie
header with a double-quoted value are discarded, as allowed by RFC 6265.
b) Add a Cookie.DoubleQuoted
boolen field. It is set to true
if a double-quoted cookie is parsed from a Set-Cookie
header, and if it is true
the Cookie.String
adds double-quotes to the cookie value.
c) Change the meaning of the Cookie.Value
field to represent a cookie value as defined in RFC 6265.
Comment From: vdobler
This issue is about a simple question: Does net/http.Cookie.Value represent the "semantic value" of a cookie or does it represent the raw data that RFC 6265 calls the "cookie-value".
RFC 6265 is not clear here (as it make much statements about how values should be interpreted) but common interpretation has been that the semantic value of a cookie can be optionally enclosed in double quotes or not enclosed. See e.g. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie stating "A cookie-value can optionally be wrapped in double quotes". This interpretation is shared by net/http which treats the double quotes as not being part of the value.
Unfortunately net/http.Cookie mentions RFC 6265 and maybe this lead to this confusion here. I still think that a=
and a=""
are semantically equivalent Cookie and Set-Cookie headers, both setting a cookie named "a" to an empty (semantic) value and anybody who needs to distinguish between them should process the raw HTTP headers himself and just cannot use net/http.Cookie which provides an interface to the "semantic" cookies, not the raw cookies as sent on the wire.
Comment From: neild
a) Standard compliant cookies received with a Set-Cookie header with a double-quoted value are discarded, as allowed by RFC 6265.
This is obviously not something we can or should do.
b) Add a Cookie.DoubleQuoted boolen field. It is set to true if a double-quoted cookie is parsed from a Set-Cookie header, and if it is true the Cookie.String adds double-quotes to the cookie value.
This seems like the simplest way to preserve double-quoted cookie-value
s.
c) Change the meaning of the Cookie.Value field to represent a cookie value as defined in RFC 6265.
We could safely change (*Cookie).String
to preserve surrounding double quotes in the value when present. However, we clearly cannot change (*Request).Cookie
to return a doubled-quoted Cookie.Value
without breaking existing users. We could provide a way to configure cookie parsing (e.g., via a configuration flag on Request
), but this seems more complicated than option b above.
Preserving the ability to round-trip a cookie-value
seems worth the cost of a boolean on Cookie
to me. It would be nice to know what the practical negative impact of not preserving double quotes is, however.
Comment From: gazerro
@neild I agree, the only viable option is b. Even if we could break existing users, this option does not force you to manage surrounding DQUOTE characters if you don't want to, and also allows you to adds surrounding DQUOTE characters if you need to.
It would be nice to know what the practical negative impact of not preserving double quotes is, however.
It's a good question. I honestly think no one knows. I also found this similar old issue https://github.com/golang/go/issues/10195, closed but not solved.
Comment From: rsc
This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — rsc for the proposal review group
Comment From: rsc
By analogy with url.URL.ForceQuery it seems like this should be Cookie.ForceQuote. We do automatically quote when the value contains space or comma; I don't know if that's correct. (The grammar seems to say commas are never valid.)
Comment From: neild
We automatically quote when the value contains space or comma, which is weird because spaces and commas are never valid in cookie values. I don't know if automatic quoting is correct or incorrect, but it doesn't seem useful, and it is confusing.
ForceQuote
seems fine to me.
Comment From: vdobler
While spaces and commas are never valid in cookie values according to RFC 6265 all browsers (and even curl) support spaces and commas (if quoted) and people rely on this behaviour because a lot of systems, libraries and frameworks use them and they do work irl. (Firefox allows (almost) arbitrary UTF-8 strings as cookie values, at least when I tested it some years ago.)
I'm unsure about ForceQuote
as this field is not only used to force quoting a value when sending but it also reports whether the received value was quoted or not. If you just read the documentation of net/url.URL` it's basically impossible to infer that ForceQuery is two-way (at least for me). But I'm fine with a consistent naming.
Comment From: rsc
We can also just call it "Quoted". No need for Force.
Comment From: rsc
Have all remaining concerns about this proposal been addressed?
The proposal is to add a new field ‘Quoted bool’ in http.Cookie. When parsing a cookie, if double quotes are removed from the value, Quoted is set to true. When printing a cookie, if Quoted is set to true, double quotes are added back to the value. Double quotes are still also added implicitly for cookies that contain space or comma, for compatibility with older versions of Go.
Comment From: gazerro
Have all remaining concerns about this proposal been addressed?
The cookiejar.Jar
type should work with the new Quoted
field. So, when we use the SetCookies
method to add cookies, the Cookies
method should return the correct value for Quoted
. For everything else, it doesn't seem like there's anything missing
Comment From: rsc
Thanks for pointing that out @gazerro. We should definitely update the cookiejar implementation to preserve Quoted. I'm surprised it doesn't just use the Cookie type directly. Maybe that would make more sense. But yes, please consider updating cookiejar part of this proposal.
Comment From: rsc
Have all remaining concerns about this proposal been addressed?
The proposal is to add a new field ‘Quoted bool’ in http.Cookie. When parsing a cookie, if double quotes are removed from the value, Quoted is set to true. When printing a cookie, if Quoted is set to true, double quotes are added back to the value. Double quotes are still also added implicitly for cookies that contain space or comma, for compatibility with older versions of Go.
The net/http/cookiejar implementation also has to be updated to preserve the Quoted field.
Comment From: nunogoncalves03
Since all the concerns seem to have been addressed, can I take on this proposal?
Comment From: ianlancetaylor
@nunogoncalves03 The proposal has not been formally accepted yet. But, sure, you can send a patch for it that can be submitted when and if it is accepted. Thanks.
Comment From: rsc
Based on the discussion above, this proposal seems like a likely accept. — rsc for the proposal review group
The proposal is to add a new field ‘Quoted bool’ in http.Cookie. When parsing a cookie, if double quotes are removed from the value, Quoted is set to true. When printing a cookie, if Quoted is set to true, double quotes are added back to the value. Double quotes are still also added implicitly for cookies that contain space or comma, for compatibility with older versions of Go.
The net/http/cookiejar implementation also has to be updated to preserve the Quoted field.
Comment From: gopherbot
Change https://go.dev/cl/577755 mentions this issue: net/http: add field Cookie.Quoted bool
Comment From: rsc
No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — rsc for the proposal review group
The proposal is to add a new field ‘Quoted bool’ in http.Cookie. When parsing a cookie, if double quotes are removed from the value, Quoted is set to true. When printing a cookie, if Quoted is set to true, double quotes are added back to the value. Double quotes are still also added implicitly for cookies that contain space or comma, for compatibility with older versions of Go.
The net/http/cookiejar implementation also has to be updated to preserve the Quoted field.