#navi_header|技術| お仕事の関係で、HTTPリクエストのCookieヘッダの値はどう解釈されているのか調べてみました。 通常はCookieヘッダの値部分は、空白以外の値で、一般的には余計な記号など入り込まないように言語やライブラリ、フレームワーク、あるいはプログラマが処理してます。 しかし、特殊な要件を満たすため、普段は設定しない記号系の文字もCookieで使う必要がありました。 そこでいくつかのメジャーなWebアプリ実行環境について、実際にCookieに特殊な記号を入れて送ってみて、Webアプリケーション側ではどのように処理されて、見えるようになるのか調べてみました。また、逆に、特殊な記号を入れた値をアプリケーションからCookieとして設定すると、最終的なSet-Cookieでどのようにエンコードやエスケープされるかも確認してみました。 2014-02-08時点での確認状況 |言語/プラットフォーム|Cookieヘッダの解釈|Set-Cookieでのエンコード|h |PHP5|URLデコードする|setcookie():URLエンコード, setrawcookie():そのまま| |Tomcat7|一部記号を除きそのまま|基本そのまま、一部記号が入ると""囲み| |Ruby(Rackベース)|URLデコードする|URLエンコードする| |ASP|(未検証)|(未検証)| |ASP.NET|(未検証)|(未検証)| |ASP.NET MVC|(未検証)|(未検証)| |Python(WSGIベース)|(未検証)|(未検証)| |node.js|(未検証)|(未検証)| |Play(Nettyベース)|(未検証)|(未検証)| |(その他)|(未検証)|(未検証)| - Cookieのname=valueのvalue部分に、";" や "," がそのまま入ると、いずれもname=valueの区切り文字としてRFCにある文字なので、そこで分割してしまう処理系が多いようです。 - 記号を含めた値でCookieを発行しようとすると、PHP5とRackの場合は、処理系の方で自動的にURLエンコードしてくれました。Javaの場合は""で囲んでくれました。 -- ただし本記事で確認しているのは記号系の扱いであり、日本語やバイナリデータの場合の挙動までは未確認です。Javaの場合は、アプリケーション側でURLエンコードやBase64エンコードするなど、何らかの対応が必要になるかもしれません。 - 参考:[Studying HTTP] HTTP Cookies -- http://www.studyinghttp.net/cookies 細かい状況については、以下の個別の詳細確認状況を御覧ください。 #more|| ---- #outline|| ---- * 検証に使用した記号 検証に使用した文字種: #pre||> (0x20) ! " # $ % & ' ( ) = ~ | - ^ \ @ [ ] { } ; + : * , . < > ? _ ||< HTTPリクエストのCookieヘッダの検証では、上記をエンコードせずにそのままサーバに対して送信したケースと、URLエンコードしたケースの2パターンを確認した。 サーバからのSet-Cookieの検証では、上記を含んだ値を言語処理系やライブラリ・フレームワークの提供する機能を用いてSet-Cookieした時の挙動を確認した。 * PHP 環境: #pre||> CentOS 6.5 (x64) PHP 5.3.3 + Apache/2.2.15 (Apache 2.0 Handler) magic_quotes_gpc : off ||< サンプルコード:cookies.php (HTTPリクエストのCookie値の解釈を確認) #pre||> Cookie: abc=123 DEF; def=456bGHI -> array(2) { ["abc"]=> string(7) "123 DEF" ["def"]=> string(7) "456bGHI" } Cookie: abc=123!DEF; def=456bGHI -> string(7) "123!DEF" Cookie: abc=123%DEF; def=456bGHI -> string(5) "123(0xDE)F" Cookie: abc=123+DEF; def=456bGHI -> string(7) "123 DEF" Cookie: abc=123,DEF; def=456bGHI -> string(7) "123,DEF" ||< ";"がそのまま混入すると、そこでCookie値が分割されるようだ。 #pre||> Cookie: abc=123;DEF; def=456bGHI -> array(3) { ["abc"]=> string(3) "123" ["DEF"]=> string(0) "" ["def"]=> string(7) "456bGHI" } ||< Cookie値がURLエンコードされていると、URLデコードされてアプリ空間から参照できた。 #pre||> Cookie: abc=123%20DEF; def=456bGHI -> array(2) { ["abc"]=> string(7) "123 DEF" ["def"]=> string(7) "456bGHI" } Cookie: abc=123%25DEF; def=456bGHI -> string(7) "123%DEF" Cookie: abc=123%3bDEF; def=456bGHI -> string(7) "123;DEF" Cookie: abc=123%2bDEF; def=456bGHI -> string(7) "123+DEF" Cookie: abc=123%3aDEF; def=456bGHI -> string(7) "123:DEF" Cookie: abc=123%2aDEF; def=456bGHI -> string(7) "123*DEF" Cookie: abc=123%2cDEF; def=456bGHI -> string(7) "123,DEF" Cookie: abc=123%2eDEF; def=456bGHI -> string(7) "123.DEF" ||< ** Set-Cookieするときの特徴 setcookie()を使う場合は、URLエンコードする。 #pre||> (setcookie() にセットした値) -> (実際にサーバから送信されたSet-Cookieヘッダ) 123 DEF -> Set-Cookie: abc=123+DEF 123_DEF -> Set-Cookie: abc=123_DEF 123?DEF -> Set-Cookie: abc=123%3FDEF 123.DEF -> Set-Cookie: abc=123.DEF 123,DEF -> Set-Cookie: abc=123%2CDEF 123*DEF -> Set-Cookie: abc=123%2ADEF 123;DEF -> Set-Cookie: abc=123%3BDEF 123+DEF -> Set-Cookie: abc=123%2BDEF 123-DEF -> Set-Cookie: abc=123-DEF ||< setrawcookie()を使う場合は、URLエンコードされない。ただし、いくつかの文字は使えず、Set-Cookieヘッダは発行されずに、Warningが発生してしまう。 Warning: Cookie values cannot contain any of the following ',; \t\r\n\013\014' in ... #pre||> (setrawcookie() にセットした値) -> (実際にサーバから送信されたSet-Cookieヘッダ) 123 DEF -> (上記Warning発生) 123_DEF -> Set-Cookie: abc=123_DEF 123?DEF -> Set-Cookie: abc=123?DEF 123.DEF -> Set-Cookie: abc=123.DEF 123,DEF -> (上記Warning発生) 123*DEF -> Set-Cookie: abc=123*DEF 123;DEF -> (上記Warning発生) 123+DEF -> Set-Cookie: abc=123+DEF 123-DEF -> Set-Cookie: abc=123-DEF ||< * Tomcat7 (Servlet 3.0) 環境: #pre||> CentOS 6.5 (x64) Oracle JDK 1.7.0_51 (x64), Server VM Apache Tomcat 7.0.50 ||< サンプルコード:cookies.jsp (HTTPリクエストのCookie値の解釈を確認) #pre||> <%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8" %> Cookie Viewer <% Cookie[] cookies = request.getCookies(); if (null != cookies) { for (Cookie c : cookies) { out.println("Cookie[" + c.getName() + "]=[" + c.getValue() + "]
"); } } %> ||< サンプルコード:setcookie.jsp (Set-Cookieの挙動を確認) #pre||> <%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8" %> <% String cn = request.getParameter("cn"); String cv = request.getParameter("cv"); if (null != cn && null != cv) { response.addCookie(new Cookie(cn, cv)); } %> ||< ** HTTPリクエストのCookie値の解釈 - URLデコードされず、そのまま取得される。 - ただし、"(" や ")" など、いくつかの記号が混入すると、それ以降の値は削除されてしまう。 - "" で囲むことで、削除されなくなる。 URLデコードされず、そのまま取得されるケース: #pre||> Cookie: abc=123%DEF; def=456bGHI -> Cookie[abc]=[123%DEF]
Cookie: abc=123.DEF; def=456bGHI -> Cookie[abc]=[123.DEF]
Cookie: abc=123+DEF; def=456bGHI -> Cookie[abc]=[123+DEF]
Cookie: abc=123%2FDEF; def=456bGHI -> Cookie[abc]=[123%2FDEF]
他: !, #, $, &, ', ~, |, -, ^, *, _ ||< 以降の値が削除されてしまったケース: #pre||> Cookie: abc=123"DEF; def=456bGHI -> Cookie[abc]=[123]
Cookie: abc=123,DEF; def=456bGHI -> Cookie[abc]=[123]
他: (, ), =, \, @, [, ], {, }, ;, :, <, >, ? ||< ""で囲むことで、削除されなくなる例: #pre||> Cookie: abc="123,DEF"; def=456bGHI -> Cookie[abc]=[123,DEF]
Cookie: abc="123@DEF"; def=456bGHI -> Cookie[abc]=[123@DEF]
Cookie: abc="123(DEF"; def=456bGHI -> Cookie[abc]=[123(DEF]
ただし " は変化なし。 Cookie: abc="123"DEF"; def=456bGHI -> Cookie[abc]=[123]
→ \" としてバックスラッシュエスケープすれば残る。 Cookie: abc="123\"DEF"; def=456bGHI -> Cookie[abc]=[123"DEF]
また、 \ はその文字だけ削除される。 Cookie: abc="123\DEF"; def=456bGHI -> Cookie[abc]=[123DEF]
→ \\ としてバックスラッシュエスケープすれば残る。 Cookie: abc="123\\DEF"; def=456bGHI -> Cookie[abc]=[123\DEF]
||< ** Set-Cookieするときの特徴 基本的にはそのままSet-Cookieヘッダに出力される。 #pre||> (response.addCookie(new Cookie("abc", ...)) にセットした値) -> (実際にサーバから送信されたSet-Cookieヘッダ) 123%DEF -> Set-Cookie: abc=123%DEF 123&DEF -> Set-Cookie: abc=123&DEF 123+DEF -> Set-Cookie: abc=123+DEF 他、このケースで確認した記号: !, #, $, ', ~, |, -, ^, *, ., _ ||< 一部の記号は "" で囲われ、 " 自体はバックスラッシュエスケープされる。また、"Version=1"が自動で追加された。 #pre||> (response.addCookie(new Cookie("abc", ...)) にセットした値) -> (実際にサーバから送信されたSet-Cookieヘッダ) 123\DEF -> Set-Cookie: abc="123\DEF"; Version=1 123,DEF -> Set-Cookie: abc="123,DEF"; Version=1 123;DEF -> Set-Cookie: abc="123;DEF"; Version=1 123 DEF -> Set-Cookie: abc="123 DEF"; Version=1 123"DEF -> Set-Cookie: abc="123\"DEF"; Version=1 他、このケースで確認した記号: (, ), =, @, [, ], {, }, :, <, >, ? ||< * Ruby(Rackベースアプリ) 環境: #pre||> ruby-2.0.0-p353-i386-mingw32 Windows7 Pro SP1 64bit > gem environment RubyGems Environment: - RUBYGEMS VERSION: 2.0.14 - RUBY VERSION: 2.0.0 (2013-11-22 patchlevel 353) [i386-mingw32] (...) - RUBYGEMS PLATFORMS: - ruby - x86-mingw32 (...) > gem install sinatra rack-1.5.2 tilt-1.4.1 rack-protection-1.5.2 sinatra-1.4.4 ||< サンプルコード: #pre||> require 'sinatra' get '/cookies' do request.cookies.inspect end get '/setcookie' do response.set_cookie(params[:cn], params[:cv]) end get '/hi' do "Hello World!" end ||< ** HTTPリクエストのCookie値の解釈 URLデコードする。ただしURLエンコードされていない文字についてはそのまま素通しになる。 #pre||> Cookie: abc=123 DEF; def=456bGHI -> {"abc"=>"123 DEF", "def"=>"456bGHI"} Cookie: abc=123!DEF; def=456bGHI -> {"abc"=>"123!DEF", "def"=>"456bGHI"} Cookie: abc=123%DEF; def=456bGHI -> {"abc"=>"123\xDEF", "def"=>"456bGHI"} Cookie: abc=123+DEF; def=456bGHI -> {"abc"=>"123 DEF", "def"=>"456bGHI"} ||< ";"や","がそのまま混入すると、そこでCookie値が分割されるようだ。 #pre||> Cookie: abc=123;DEF; def=456bGHI -> {"abc"=>"123", "DEF"=>nil, "def"=>"456bGHI"} Cookie: abc=123,DEF; def=456bGHI -> {"abc"=>"123", "DEF"=>nil, "def"=>"456bGHI"} ||< Cookie値がURLエンコードされていると、URLデコードされてアプリ空間から参照できた。 #pre||> Cookie: abc=123%20DEF; def=456bGHI -> {"abc"=>"123 DEF", "def"=>"456bGHI"} Cookie: abc=123%25DEF; def=456bGHI -> {"abc"=>"123%DEF", "def"=>"456bGHI"} Cookie: abc=123%3bDEF; def=456bGHI -> {"abc"=>"123;DEF", "def"=>"456bGHI"} Cookie: abc=123%2bDEF; def=456bGHI -> {"abc"=>"123+DEF", "def"=>"456bGHI"} Cookie: abc=123%3aDEF; def=456bGHI -> {"abc"=>"123:DEF", "def"=>"456bGHI"} Cookie: abc=123%2aDEF; def=456bGHI -> {"abc"=>"123*DEF", "def"=>"456bGHI"} Cookie: abc=123%2cDEF; def=456bGHI -> {"abc"=>"123,DEF", "def"=>"456bGHI"} Cookie: abc=123%2eDEF; def=456bGHI -> {"abc"=>"123.DEF", "def"=>"456bGHI"} ||< ** Set-Cookieするときの特徴 URLエンコードする。 #pre||> (response.set_cookie("abc", ...) にセットした値) -> (実際にサーバから送信されたSet-Cookieヘッダ) 123 DEF -> Set-Cookie: abc=123+DEF 123_DEF -> Set-Cookie: abc=123_DEF 123?DEF -> Set-Cookie: abc=123%3FDEF 123.DEF -> Set-Cookie: abc=123.DEF 123,DEF -> Set-Cookie: abc=123%2CDEF 123*DEF -> Set-Cookie: abc=123*DEF 123;DEF -> Set-Cookie: abc=123%3BDEF 123+DEF -> Set-Cookie: abc=123%2BDEF 123-DEF -> Set-Cookie: abc=123-DEF ||< #navi_footer|技術|