Mark Nottingham

Friday Fun: I Hate Cookies

Friday, 27 October 2006

HTTP

There are plenty of reasons to hate HTTP Cookies, but there’s one thing that especially annoys me; their syntax.

Specifically, the Netscape spec allowed an “expires” field that contains the date the cookie should be discarded;

Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT

What’s wrong with that? Well, HTTP uses commas to delimit different instances of the same header, saying:

It MUST be possible to combine the multiple header fields into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.

Notice something about the above? It uses a comma inside of the date, without quoting the value. This makes it difficult for generic processors to handle the Set-Cookie header.

Still, it should be possible to parse a combined Set-Cookie header, as long as the implementation is careful.

If At First You Don’t Succeed, Bake Some More Cookies

RFC2109 tried to fix this by moving from expires to Max-Age, and also by allowing values to be quoted, making them easier to parse. That was in 1997.

Then, in 2000, RFC2965 stepped in and defined a new header, Set-Cookie2, containing a bunch more options than the old one.

My question is how well-supported these different mechanisms are by browsers, and do they support setting more than one cookie on the same header line?

To test this, I set up a simple page that sets a bunch of different cookies*;

Set-Cookie: NS=yes; expires=Wednesday, 01-Jan-10 0:0:00 GMT
Set-Cookie: NS_1=a; expires=Wednesday, 01-Jan-10 0:0:00 GMT, NS_2=b; expires=Wednesday, 01-Jan-10 00:00:00 GMT
Set-Cookie: RFC2109="yes"; Version="1"; Max-Age="60"
Set-Cookie: RFC2109_1="a"; Version="1"; Max-Age="60", RFC2109_2="b"; Version="1"; Max-Age="60"
Set-Cookie2: RFC2965="yes"; Version="1"; Max-Age="60"
Set-Cookie2: RFC2965_1="a"; Version="1"; Max-Age="60", RFC2965_2="b"; Version="1"; Max-Age="60"

Notice that each cookie style is set, and then set twice on the same line, to see if that’s supported.

Then, it tries to read them using JavaScript, and makes the raw Cookie request header available (in Apache’s CGI environment) from a separate link. There, you can see not only which cookies were set, but also which of the latter two include a $Version parameter to differentiate them from older-style cookies.

So Far, Not Good (updated)

You’d think that after so much time and two standards, we’d have this stuff nailed. Not so much.

  1. Safari 2, Firefox 2, IE6 and 7, and Opera 9 all work with Netscape and RFC2109 cookies, but none except Opera know anything about RFC2965. I didn’t have much expectation of broad adoption there, but it’s still disappointing.
  2. Opera and Safari both seem to be OK with combining cookies on the same line, but Firefox and IE only see the first one. This is a big pain for header libraries, and I’ve filed a bug where I can.
  3. Of all of them, only Opera sends the $Version parameter to indicate that it understands anything more recent than the Netscape spec.

So far, Opera seems like the only fully-featured browser cookie implementation out there. It would be interesting to test other cookie jars (e.g., in Python, Perl, Ruby).