3
votes

I see some sites, like stackoverflow, use packed cookies where multiple cookies are packed into one. Here's an example:

Set-Cookie: acct=t=&s=; domain=.stackapps.com; expires=Mon, 30-May-2016 20:16:22 GMT; path=/; HttpOnly

Is this just to save sending multiple set-cookie headers, and to avoid sending comma separated cookies on the one set-cookie header? That's allowed--but is it not recommended?

Should the packed cookie just be treated as a single cookie, or does it need to be unpacked and sent back as individual cookies?

1

1 Answers

2
votes

I do not know from where the idea of "packed" came about. Those are just cookies with the = sign in the value, or at least should be according to the specs. Let us go through the RFCs and see that:

Set-Cookie: acct=t=&s=; domain=.stackapps.com; expires=...

is exactly the same as

Set-Cookie: acct="t=&s="; domain=.stackapps.com; expires=...

Therefore, it is a single cookie and shall be treated as such.

The answer is rather long, sorry for that. I tried to aim it at people who find the grammar rules found in the RFCs difficult to understand. If you believe that some piece of the grammar is still difficult to understand please point it to me in a comment.


Through the RFCs

The current RFC for the Set-Cookie header is RFC6265, in section 4.1 it has the formal syntax for Set-Cookie:

set-cookie-header = "Set-Cookie:" SP set-cookie-string
set-cookie-string = cookie-pair *( ";" SP cookie-av )
cookie-pair       = cookie-name "=" cookie-value
cookie-name       = token
cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
                      ; US-ASCII characters excluding CTLs,
                      ; whitespace DQUOTE, comma, semicolon,
                      ; and backslash
token             = <token, defined in [RFC2616], Section 2.2>

cookie-av         = expires-av / max-age-av / domain-av /
                    path-av / secure-av / httponly-av /
                    extension-av
expires-av        = "Expires=" sane-cookie-date
sane-cookie-date  = <rfc1123-date, defined in [RFC2616], Section 3.3.1>
max-age-av        = "Max-Age=" non-zero-digit *DIGIT
                      ; In practice, both expires-av and max-age-av
                      ; are limited to dates representable by the
                      ; user agent.
non-zero-digit    = %x31-39
                      ; digits 1 through 9
domain-av         = "Domain=" domain-value
domain-value      = <subdomain>
                      ; defined in [RFC1034], Section 3.5, as
                      ; enhanced by [RFC1123], Section 2.1
path-av           = "Path=" path-value
path-value        = <any CHAR except CTLs or ";">
secure-av         = "Secure"
httponly-av       = "HttpOnly"
extension-av      = <any CHAR except CTLs or ";">

That is a little terse but we do not need to got through it all. For a start we have the Set-Cookie: header and a space (SP), then the set-cookie-string which is defined further.

set-cookie-header = "Set-Cookie:" SP set-cookie-string

set-cookie-string is composed of a cookie-pair (defined further), which is the grammar part that interests us, and optionally a set of any number of cookie-av prefixed with ; and a space. The *() construct allows for any number of occurrences (including zero) of the grammar part.

set-cookie-string = cookie-pair *( ";" SP cookie-av )

cookie-av defines the metadata that can be used in the cookie but it is not needed for our proof, therefore we will abandon its discussion.

The cookie-pair on the other hand is a very simple construct: one cookie-name one mandatory = sign and one cookie-value.

cookie-pair       = cookie-name "=" cookie-value

The cookie-name is defined as a token which leads us to another RFC, RFC2616. In the section 2.2 of that RFC we find the basic rules that define the token.

cookie-name       = token
token             = <token, defined in [RFC2616], Section 2.2>

token definition:

CTL            = <any US-ASCII control character
                 (octets 0 - 31) and DEL (127)>
...
token          = 1*<any CHAR except CTLs or separators>
separators     = "(" | ")" | "<" | ">" | "@"
               | "," | ";" | ":" | "\" | <">
               | "/" | "[" | "]" | "?" | "="
               | "{" | "}" | SP | HT

The 1*<> syntax means any number of occurrences but at least one occurrence. To find the CTLs use man ascii and check the Dec column, SP is space (as we already saw) and HT is the horizontal tab (9 in the ascii table).

The interesting part for us is the fact that a token cannot contain an = character.

Back to RFC6265:

cookie-pair       = cookie-name "=" cookie-value

cookie-name stops at the first = character, that first = character is always the = explicit in the grammar. Now, let's finally define the cookie-value

cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
                      ; US-ASCII characters excluding CTLs,
                      ; whitespace DQUOTE, comma, semicolon,
                      ; and backslash

We already saw that the * there means any occurrences including zero (note that empty cookies are allowed by the RFC!). The interesting part the entire cookie-value can be enclosed by double quotes (DQUOTE is the double quote character as you might have guessed).

But the most interesting part is that the = sign (x3D in the ascii) table is allowed as a cookie-octet

/ %x3C-5B / <- right there!

Yet the space (x20) and semicolon (x3B) are disallowed.

Conclusion

Therefore this Set-Cookie header shall be interpreted as

Set-Cookie: acct=t=&s=; domain=.stackapps.com; expires=...

cookie-set-header = "Set-Cookie:" SP set-cookie-string
set-cookie-string = cookie-pair *(";" cookie-av)
cookie-pair       = cookie-name "=" cookie-value
cookie-name       = "acct"
cookie-value      = "t=&s="

And the header sending it back to the server shall be

Cookie: acct=t=&s=

Sending it as follows violates the RFC:

Cookie: acct=t&; s=