GeekFactory

int128.hatenablog.com

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*
tspecials = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
quoted-string = ( <"> *(qdtext) <"> )
qdtext = >

http://www.ietf.org/rfc/rfc2068.txt

ここまでの話をまとめると、NAME, VALUEで使用できる文字は以下となります。

NAME 制御文字、特定記号 ()<>@,;:\"/[]?={} 、スペース、タブを含まないこと。
VALUE 制御文字、特定記号 ()<>@,;:\"/[]?={} 、スペース、タブを含まないこと。それらを1つでも含む場合はダブルクォートで囲むこと。

RFC2109に照らし合わせるとTomcat 5.5.26の仕様変更は妥当といえますが、影響範囲の大きさを考えるととんでもない間違いだと思います。