XMLHttpRequest で same origin から cross origin にリダイレクトする際の挙動について
現時点では XMLHttpRequest を使った際に same origin から cross origin へリダイレクトが発生するリクエストがエラーにならないのは, Firefox と IE10 (Platform Preview 4) くらいだけれど (参考:http://samples.msdn.microsoft.com/ietestcenter/#cors), その2つの間にも若干挙動の違いがあったのでメモ.
確認の為に用いたのは,
- Firefox 10: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0.1) Gecko/20100101 Firefox/10.0.1
- Internet Explorer 10: Windows Internet Explorer Platform Preview, version 2.10.8103.0 (Internet Explorer version 10.0.8103.0)
具体的に違いがあるのは,カスタムヘッダを追加してリクエストを出したり, XMLHttpRequestUpload オブジェクトにイベントリスナを登録しているような場合. 要は CORS で preflight が必要だとされている条件を満たしている場合.
いろいろ説明するよりも,実際に試した方が早いのでテストケースを挙げる. -> http://misc-xkyrgyzstan.dotcloud.com/xhr/redirect-from-same-origin
このページが何をするかというと,アクセスした際に
- カスタムヘッダなし, XMLHttpRequestUpload オブジェクトにイベントリスナが登録されていない場合
X-Requested-With: XMLHttpRequestというカスタムヘッダを追加してリクエストした場合req.upload.addEventListener('load', function (e) {}, false)として XMLHttpRequestUpload オブジェクトにイベントリスナを登録してからリクエストを出した場合
のそれぞれで, XMLHttpRequest のリクエスト先が same origin から cross origin へリダイレクトする場合の挙動を調べている.
リダイレクト先のページは,
Access-Control-Allow-Origin: * をヘッダに出力し,
またリクエストの際に Access-Control-Request-Headers で与えられたヘッダに対して
Access-Control-Allow-Headers で許可を出すようにしている.
Firefox でのアクセスした結果は,
normal (w/o custom header, w/o upload event):success
with custom header:failure
with upload event:failure
となった.
X-Requested-With: XMLHttpRequest のようなカスタムヘッダが付いていたり,
req.upload.addEventListener('load', funciton (e) {/* do something */}, false)
のように XMLHttpRequestUpload オブジェクトにイベントリスナが登録されている状態で,
リクエスト先が same origin から cross origin へリダイレクトした場合に
エラーが生じるようになっている.
また,カスタムヘッダ付きだったり XMLHttpRequestUpload オブジェクトにイベントリスナが登録されている場合には,
リダイレクト先に preflight リクエストを送っていない(OPTIONS メソッドによるリクエストが発生していない).
一方 Internet Explorer 10 では,
normal (w/o custom header, w/o upload event):success
with custom header:success
with upload event:success
となり,いずれの場合もリクエストが成功する. カスタムヘッダが付いていたり,XMLHttpRequestUpload オブジェクトにイベントリスナがついている場合は, GET -> リダイレクト先に OPTIONS (preflight) -> リダイレクト先に GET, という流れでリクエストが処理される.
どちらの挙動が正しいのか
調べた限りでは,
- preflight が必要な cross origin に対するリクエストがリダイレクト時にエラーになることは CORS の working draft に書いてあるけれど(http://www.w3.org/TR/2010/WD-cors-20100727/#cross-origin-request-with-preflight0), preflight が必要な条件を満たしている same origin から cross origin に対するリクエストがエラーになるのかは書かれていない.
- XMLHttpRequest では, same origin から cross origin にリダイレクトした場合は Location ヘッダで指示されている URL を request URL として CORS の cross-origin request を行うように書かれていて(http://www.w3.org/TR/XMLHttpRequest2/#infrastructure-for-the-send-method), そのリクエストが preflight が必要な条件を満たしている場合のことは特に書かれていない.
といった感じで, same origin から cross origin に対するリダイレクトが発生した場合にエラーにする理由を見つけられなかった.
あと,Firefox の XMLHttpRequest 周りのコードも見たのだけれど, https://hg.mozilla.org/mozilla-central/file/78fde7e54d92/content/base/src/nsXMLHttpRequest.cpp#l3238には
// Disable redirects for preflighted cross-site requests entirely for now // Note, do this after the call to CheckChannelForCrossSiteRequest // to make sure that XML_HTTP_REQUEST_USE_XSITE_AC is up-to-date if ((mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT)) { return NS_ERROR_DOM_BAD_URI; }
とあり, コメントでは実際に preflight が発生した場合に cross origin へのリダイレクトをやめるように書いてあるけれど, コードでは preflight が必要な条件を満たしているのかをチェックしていて, preflight が行われたのかどうかはチェックしていない. なのでコメントとコードが一致していないように思えた.
という訳で,自分は IE 10 の挙動が正しいのではないかと考えている.
以下,あまり関係のない話を書く.
- もし Firefox の挙動が正しいのであれば, XMLHttpRequest のリクエスト先を外部入力により決定してその内容を表示するような場合に, リクエスト先が same origin であることを確認して カスタムヘッダを追加しさえすれば, たとえオープンリダイレクタがあったとしても cross origin のリソースを読み込みを禁止できるわけなので, Firefox の挙動が正しい方が嬉しい場合が多いのではないかとは思っている.
現在の XMLHttpRequest の editor’s draft を見る限りでは,
req.responseType = 'document'の場合であれば, XMLHttpRequest で取得した document の URL プロパティを確認することで, リダイレクトのチェックができるようになると思う. http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#document-response-entity-body に8. Set document’s URL to request URL.
とあるので. なので,カスタムヘッダを付けたりしても same origin から cross origin へのリダイレクトがエラーにならない場合でも, 将来的にはリダイレクトしたかを確認すれば済む話になりそう.