Major bug with BlackBerry browser and multiple cookies

Posted on August 27th, 2008 in Bugs, Mobile | 5 Comments »

This is a long post.

Having investigated this issue for the last few days, I believe that there is a significant issue with the cookie implementation in BlackBerry browsers using the default Internet Browser. I haven’t been able to test the recently-released BlackBerry Bold (9000), which is bundled with the new BlackBerry Browser, but as far as I can determine, devices like the 8800 and 8820 are affected.

The problem occurs when a site attempts to set multiple cookies. Although they are stored on the device, the cookies are returned to the server haphazardly. Sometimes all cookies are returned; more often it’s only the first or last cookie that was set that is passed back.

The following is a TCP dump of traffic to a particular server. I’ve bolded the relevant parts and changed a few names. The server is not load-balanced, and the headers are not altered on my end in any way.

GET / HTTP/1.0
profile: http://www.blackberry.net/go/mobile/profiles/uaprof/8820/4.2.2.rdf
x-wap-profile: "http://www.blackberry.net/go/mobile/profiles/uaprof/8820/4.2.2.rdf"
Accept: application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,image/gif;anim=1,application/vnd.rim.jscriptc;v=0-8-8,application/x-javascript,application/vnd.rim.css;v=1,text/css;media=handheld,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,*/*;q=0.5
Accept-Charset: ISO-8859-1,UTF-8,US-ASCII,UTF-16BE,windows-1252,UTF-16LE,windows-1254,KOI8-R,windows-1251,windows-1255,windows-1256,windows-1250
Accept-Language: en-US,en;q=0.5
User-Agent: BlackBerry8820/4.2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/100
Host: cookie.example.com
Via: BISB_3.3.0.45, 1.1 pmds76.bisb1.blackberry:3128 (squid/2.5.STABLE12)
X-Forwarded-For: unknown
Cache-Control: max-age=259200
Connection: keep-alive

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 15:56:19 GMT
Set-Cookie: cookie1=o4s9o298mbqjf5qnjmgad76rs0; path=/; domain=cookie.example.com
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: cookie2=en_us; expires=Thu, 27-Aug-2009 15:56:19 GMT; path=/; domain=cookie.example.com
Content-Language: en_us
Set-Cookie: cookie3=2d14a216211bc7f1a1b03fa747d1087e; expires=Mon, 01-Sep-2008 06:59:59 GMT; path=/; domain=cookie.example.com
Vary: Accept-Encoding
Content-Length: 7975
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8

Here we set three cookies: cookie1, cookie2, and cookie3.

GET /subsequent/request HTTP/1.0
User-Agent: BlackBerry8820/4.2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/100
profile: http://www.blackberry.net/go/mobile/profiles/uaprof/8820/4.2.2.rdf
Referer: http://cookie.example.com/
Host: cookie.example.com
Cookie: cookie1=o4s9o298mbqjf5qnjmgad76rs0
Via: 1.1 pmds76.bisb1.blackberry:3128 (squid/2.5.STABLE12)
X-Forwarded-For: unknown
Cache-Control: max-age=259200
Connection: keep-alive

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 15:56:21 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: cookie2=en_us; expires=Thu, 27-Aug-2009 15:56:21 GMT; path=/; domain=cookie.example.com
Content-Language: en_us
Set-Cookie: cookie3=a082e222cde7b4aedb6a8b42f2723849; expires=Mon, 01-Sep-2008 06:59:59 GMT; path=/; domain=cookie.example.com
Vary: Accept-Encoding
Content-Length: 3701
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/css

The BlackBerry (an 8820) returns only one cookie back (cookie1), so the server tries to resend the cookies it did not receive. Keep in mind the expiration times here were set in the future for this request.

GET / HTTP/1.0
User-Agent: BlackBerry8820/4.2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/100
profile: http://www.blackberry.net/go/mobile/profiles/uaprof/8820/4.2.2.rdf
Referer: http://cookie.example.com/
Host: cookie.example.com
Cookie: cookie1=o4s9o298mbqjf5qnjmgad76rs0
Via: 1.1 pmds76.bisb1.blackberry:3128 (squid/2.5.STABLE12)
X-Forwarded-For: unknown
Cache-Control: max-age=259200
Connection: keep-alive

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 15:56:21 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: cookie2=en_us; expires=Thu, 27-Aug-2009 15:56:22 GMT; path=/; domain=cookie.example.com
Content-Language: en_us
Set-Cookie: cookie3=270773eec992a03fd63a7b74b2f5ef9a; expires=Mon, 01-Sep-2008 06:59:59 GMT; path=/; domain=cookie.example.com
Vary: Accept-Encoding
Connection: close
Content-Type: text/html; charset=utf-8

This is the same as above. The next GET is really interesting:

GET / HTTP/1.0
User-Agent: BlackBerry8820/4.2.2 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/100
profile: http://www.blackberry.net/go/mobile/profiles/uaprof/8820/4.2.2.rdf
Referer: http://cookie.example.com/
Host: cookie.example.com
Cookie: cookie2=en_us
Cookie: expires=Thu, 27-Aug-2009 15:57:03 GMT
Via: 1.1 pmds76.bisb1.blackberry:3128 (squid/2.5.STABLE12)
X-Forwarded-For: unknown
Cache-Control: no-cache, max-age=259200
Connection: keep-alive

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 15:57:05 GMT
Set-Cookie: cookie1=32mrdio18lvpg6cduhnt1prmj5; path=/; domain=cookie.example.com
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: cookie2=en_us; expires=Thu, 27-Aug-2009 15:57:05 GMT; path=/; domain=cookie.example.com
Content-Language: en_us
Set-Cookie: cookie3=2d0b160a97e20469aad34e01890adbfb; expires=Mon, 01-Sep-2008 06:59:59 GMT; path=/; domain=cookie.example.com
Vary: Accept-Encoding
Connection: close
Content-Type: text/html; charset=utf-8

The BlackBerry sends back cookie2 and “expires” in two headers, as if they are two separate cookies!

For comparison, here’s a similar request from the same device accessing the same URL, but using Opera Mini instead. It performs as expected.

GET / HTTP/1.1
User-Agent: Opera/9.50 (J2ME/MIDP; Opera Mini/4.1.11355/546; U; en)
Host: cookie.example.com
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Connection: Keep-Alive
X-OperaMini-Features: advanced, file_system, folding
X-OperaMini-Phone-UA: BlackBerry
X-OperaMini-Phone: RIM #
x-forwarded-for: 206.53.144.82, unknown

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 17:14:47 GMT
Set-Cookie: cookie1=dpr07co4i5dchtgmk8g3gdt0b6; path=/; domain=cookie.example.com
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: cookie2=en_us; expires=Thu, 27-Aug-2009 17:14:47 GMT; path=/; domain=cookie.example.com
Content-Language: en_us
Set-Cookie: cookie3=1ce651a62bfe62a25faff44e5e5d500b; expires=Mon, 01-Sep-2008 06:59:59 GMT; path=/; domain=cookie.example.com
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 2176
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8

GET /image/example.gif HTTP/1.1
User-Agent: Opera/9.50 (J2ME/MIDP; Opera Mini/4.1.11355/546; U; en)
Host: cookie.example.com
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Referer: http://cookie.example.com/
Cookie: cookie1=dpr07co4i5dchtgmk8g3gdt0b6; cookie3=1ce651a62bfe62a25faff44e5e5d500b; cookie2=en_us
Cookie2: $Version=1
Connection: Keep-Alive, TE
TE: deflate, gzip, chunked, identity, trailers
X-OperaMini-Features: advanced, file_system, folding
X-OperaMini-Phone-UA: BlackBerry
X-OperaMini-Phone: RIM #
x-forwarded-for: 206.53.144.82, unknown

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 17:14:49 GMT
Last-Modified: Wed, 20 Aug 2008 19:36:32 GMT
ETag: "2c"
Accept-Ranges: bytes
Content-Length: 44
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: image/gif

GET / HTTP/1.1
User-Agent: Opera/9.50 (J2ME/MIDP; Opera Mini/4.1.11355/546; U; en)
Host: cookie.example.com
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Cookie: cookie1=dpr07co4i5dchtgmk8g3gdt0b6; cookie3=1ce651a62bfe62a25faff44e5e5d500b; cookie2=en_us
Cookie2: $Version=1
Cache-Control: no-cache
Connection: Keep-Alive, TE
TE: deflate, gzip, chunked, identity, trailers
X-OperaMini-Features: advanced, file_system, folding
X-OperaMini-Phone-UA: BlackBerry
X-OperaMini-Phone: RIM #
x-forwarded-for: 206.53.144.186, unknown

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 17:16:08 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Language: en_us
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 2283
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8

GET /subsequent/request HTTP/1.1
User-Agent: Opera/9.50 (J2ME/MIDP; Opera Mini/4.1.11355/546; U; en)
Host: cookie.example.com
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Referer: http://cookie.example.com/
Cookie: cookie1=dpr07co4i5dchtgmk8g3gdt0b6; cookie3=1ce651a62bfe62a25faff44e5e5d500b; cookie2=en_us
Cookie2: $Version=1
Connection: Keep-Alive, TE
TE: deflate, gzip, chunked, identity, trailers
X-OperaMini-Features: advanced, file_system, folding
X-OperaMini-Phone-UA: BlackBerry
X-OperaMini-Phone: RIM #
x-forwarded-for: 206.53.144.186, unknown

HTTP/1.1 200 OK
Date: Wed, 27 Aug 2008 17:16:09 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Language: en_us
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 958
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/css

GET /image/example.gif HTTP/1.1
User-Agent: Opera/9.50 (J2ME/MIDP; Opera Mini/4.1.11355/546; U; en)
Host: cookie.example.com
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en
Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1
Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0
Referer: http://cookie.example.com/
If-Modified-Since: Wed, 20 Aug 2008 19:36:32 GMT
If-None-Match: "2c"
Cookie: cookie1=dpr07co4i5dchtgmk8g3gdt0b6; cookie3=1ce651a62bfe62a25faff44e5e5d500b; cookie2=en_us
Cookie2: $Version=1
Connection: Keep-Alive, TE
TE: deflate, gzip, chunked, identity, trailers
X-OperaMini-Features: advanced, file_system, folding
X-OperaMini-Phone-UA: BlackBerry
X-OperaMini-Phone: RIM #
x-forwarded-for: 206.53.144.186, unknown

HTTP/1.1 304 Not Modified
Date: Wed, 27 Aug 2008 17:16:10 GMT
Connection: Keep-Alive
Keep-Alive: timeout=5, max=98
ETag: "2c"

So what’s the solution in this situation? It looks like your best option is to set just one cookie—but shove all your values into it and delimit them in some way (and make sure it’s less than 1024 bytes). If you have multiple expire times, though, you will also have to handle expiration manually. Google does this:

Set-Cookie: PREF=ID=xxxxxxxxxxxxxxxx:TM=1219863220:LM=1219863220:S=yyyyyyyyyyyyyyyy; expires=Fri, 27-Aug-2010 18:53:40 GMT; path=/; domain=.google.com

Can everyone just buy an iPhone already?


Quick fixes for installing MySQL on OS X Leopard

Posted on March 31st, 2008 in Bugs, MySQL | 1 Comment »

MySQL 5.0.51 on Mac OS X 10.5 (Leopard) is usually a model of how to port to Mac: an easy-to-use .dmg, a sensible install location, and a preference pane normally mean It Just Works™. On my last couple of installs, though, I’ve ran into some minor headaches. Here’s how I got around them.

First, the preference pane. When you try to either start or stop the MySQL server, it just kind of thinks for a second and then does nothing. It’s not your fault; the preferences pane just doesn’t work in Leopard. The good news is that there’s a fix out, located at the URL below:

ftp://ftp.mysql.com/pub/mysql/download/gui-tools/MySQL.prefPane-leopardfix.zip

Remove your old preference pane first (command-click and select “Remove ‘MySQL’ Preference Pane”), then doubleclick on the new one to install.

The second problem is that the MySQL daemon doesn’t launch on boot. MySQLCOM (the startup script) isn’t even in /Library/StartupItems! In fact, it’s just been installed to the wrong place. Run this command to move it to the right spot:

sudo mv /usr/local/MySQLCOM /Library/StartupItems/MySQLCOM

Type in your password and you’re good to go.

All things considered, both are pretty painless. Now if only MySQL would test their releases on OS X before releasing them, that would be delightful.

A fix for Suckerfish dropdowns in IE 7

Posted on October 23rd, 2006 in Bugs, CSS, Internet Explorer | 173 Comments »

Internet Explorer 7 is a welcome upgrade from IE 6, but it still has its share of display issues—and some new ones besides. Recently, I ran across one that falls in the latter category. Although Patrick Griffiths and Dan Webb’s Suckerfish dropdowns (and their follow-up, Son of Suckerfish) work fine in IE 6, they more or less choke and die in IE 7.

The problem is this: if you change focus within the browser (by clicking in a text field, for example, or somewhere on the background), then move back across the dropdowns, you’ll notice that all the menus “stick”—they don’t go away. In other words, they don’t refresh when the onmouseout event is fired, even though the special “hover” class is removed.

Luckily, the fix for this is pretty simple. Assuming your unordered list menu ID is “menu”, you just need to add the following code to your stylesheet:

#menu li:hover, #menu li.hover {
    position: static;
}

Voilà, he says with proper grave accent! Problem fixed.