2008-10-08
Apache TomcatとHTTPクッキーにまつわる騒動
Apache Tomcat 5.5.26(6.0.16も同じ)で、HTTPクッキーの取り扱いについて大きな仕様変更が行われました。ここでは仕様変更の内容と影響範囲を考察します。
HTTPクッキー
簡単に復習しましょう。WebブラウザがWebサーバから以下のHTTPヘッダを受信したとき、Webブラウザは test というクッキーを記憶します。
Set-Cookie: test=nullpo; Expires=Wed, 08-Oct-2008 14:03:16 GMT; Path=/
クッキーは NAME=VALUE という形で表現されます。連想配列と同じ。
| NAME | VALUE |
|---|---|
| test | nullpo |
一度クッキーを受信すると、ブラウザは当該URLにアクセスする度に、以下のHTTPヘッダを送信するようになります。
Cookie: test=nullpo
このように、クッキーはWebサーバがブラウザに情報を記憶させるために使用されます。例えば、Webアプリケーションがログイン情報をブラウザに記憶させておく場合はクッキーが使用されています。
Apache Tomcat 5.5.26での仕様変更
本題に入ります。Webアプリケーションで以下のようなクッキーを使用したいとします。
| NAME | VALUE |
|---|---|
| test | nullpo=hoge |
Tomcat 5.5.25は以下のHTTPヘッダを送信可能でした。
Set-Cookie: test=nullpo=hoge
また、Tomcat 5.5.25がWebブラウザから以下のHTTPヘッダを受信した場合も正しく解釈できていました。
Cookie: test=nullpo=hoge
Tomcat 5.5.26が上記のヘッダを受信した場合、VALUE中のイコール以降を無視してしまいます。正しく解釈させるには以下のようにダブルクォートで囲む必要があります。
Cookie: test="nullpo=hoge"
Tomcat 5.5.26でイコールを含むVALUEをセットすると、以下のようにVALUEがダブルクォートで囲まれたHTTPヘッダが送信されてしまいます。
Set-Cookie: test="nullpo=hoge"
特定の記号を含むクッキーを扱い場合、HTTPヘッダ上のVALUEをダブルクォートで囲むという仕様に変更されました。
既存のWebアプリケーションへの影響と暫定対処法
イコール等を含むクッキーを使用しているWebアプリケーションはTomcat 5.5.26以降で正常に動作しないようになります。改修可能な場合はまだマシなのですが、改修不可能なパッケージ等の場合は深刻な影響が考えられます。
ダブルクォートで囲まずにクッキーを出力するには、Cookie#setVersion()でバージョン0を指定します。
Cookie cookie = new Cookie("test", "nullpo=hoge"); cookie.setVersion(0); response.addCookie(cookie);
ダブルクォートで囲まれたクッキーを取得するには、HttpServletRequest#getHeaders()を使ってHTTPヘッダを直接読みます。
Enumeration cookies = request.getHeaders("cookie"); if(cookies.hasMoreElements()) { String cookieRaw = (String) cookies.nextElement(); for(String part : cookieRaw.split("; *")) { String key = part.substring(0, part.indexOf('=')); String value = part.substring(part.indexOf('=')+1); } }
実用上はこんなコードでいいと思いますが、RFC 2109に従って厳密に解釈すると結構面倒ですね。まあRFC 2109に違反したクッキーを出力しようとしているので元も子もないですが。
RFC 2109
クッキーの形式は NAME=VALUE であり、細かい仕様がRFCで規定されています。ここで着目するのは NAME, VALUE で使用できる文字です。
まず、Set-Cookieヘッダについて以下の記述があります。
The syntax for the Set-Cookie response header is
set-cookie = "Set-Cookie:" cookies
cookies = 1#cookie
cookie = NAME "=" VALUE *(";" cookie-av)
NAME = attr
VALUE = value
http://www.ietf.org/rfc/rfc2109.txt
NAME, VALUEはそれぞれattr, valueに対応するようです。attr, valueについては以下の記述があります。
attr = token
value = word
word = token | quoted-string
http://www.ietf.org/rfc/rfc2109.txt
token, quoted-stringについては以下の記述があります。
token = 1*<any CHAR except CTLs or tspecials>
tspecials = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
quoted-string = ( <"> *(qdtext) <"> )
qdtext = <any TEXT except <">>
http://www.ietf.org/rfc/rfc2068.txt
ここまでの話をまとめると、NAME, VALUEで使用できる文字は以下となります。
| NAME | 制御文字、特定記号 ()<>@,;:\"/[]?={} 、スペース、タブを含まないこと。 |
|---|---|
| VALUE | 制御文字、特定記号 ()<>@,;:\"/[]?={} 、スペース、タブを含まないこと。それらを1つでも含む場合はダブルクォートで囲むこと。 |
RFC2109に照らし合わせるとTomcat 5.5.26の仕様変更は妥当といえますが、影響範囲の大きさを考えるととんでもない間違いだと思います。
- 397 http://b.hatena.ne.jp/hotentry
- 181 http://reader.livedoor.com/reader/
- 161 http://secure.ddo.jp/~kaku/tdiary/
- 108 http://d.hatena.ne.jp/
- 94 http://d.hatena.ne.jp/x002dc/20081010/1223612088
- 63 http://b.hatena.ne.jp/entry/d.hatena.ne.jp/int128/20081008/1223480443
- 56 http://www.google.co.jp/search?q=Tomcat+Cookie&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&hl=ja&client=firefox-a
- 53 http://www.google.com/reader/view/
- 50 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CCYQFjAA&url=http://d.hatena.ne.jp/int128/20081008/1223480443&ei=yXomT_aeMvDzmAXSv6muDA&usg=AFQjCNH4OYgoIvL6nK4CSYA-GkMfZ1NRJA&sig2=WB5OV0D7P007nZii0eZK8Q
- 47 http://b.hatena.ne.jp/entrylist?sort=hot
