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
http://www.ietf.org/rfc/rfc2109.txt
cookies = 1#cookie
cookie = NAME "=" VALUE *(";" cookie-av)
NAME = attr
VALUE = value
NAME, VALUEはそれぞれattr, valueに対応するようです。attr, valueについては以下の記述があります。
attr = token
http://www.ietf.org/rfc/rfc2109.txt
value = word
word = token | quoted-string
token, quoted-stringについては以下の記述があります。
token = 1*
http://www.ietf.org/rfc/rfc2068.txt
tspecials = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
quoted-string = ( <"> *(qdtext) <"> )
qdtext =>
ここまでの話をまとめると、NAME, VALUEで使用できる文字は以下となります。
NAME | 制御文字、特定記号 ()<>@,;:\"/[]?={} 、スペース、タブを含まないこと。 |
---|---|
VALUE | 制御文字、特定記号 ()<>@,;:\"/[]?={} 、スペース、タブを含まないこと。それらを1つでも含む場合はダブルクォートで囲むこと。 |
RFC2109に照らし合わせるとTomcat 5.5.26の仕様変更は妥当といえますが、影響範囲の大きさを考えるととんでもない間違いだと思います。