PHP の $_SERVER['REQUEST_URI'] と parse_url() の予想外な動作について。
REQUEST_URI と HTTP_HOST
PHP のサーバ変数 $_SERVER['REQUEST_URI'] には、ふつうパスとクエリが設定される。
'REQUEST_URI'
PHP: $_SERVER - Manual
ページにアクセスするために指定された URI。例えば、 '/index.html'![]()
ただし、常にパスから始まると保証されているわけではない。以下のように、 GET に続けて絶対 URL を書いたリクエストを送ると、
GET http://localhost/test.php?query=value#fragment HTTP/1.1 Host: localhost
$_SERVER['REQUEST_URI'] の値は、
http://localhost/test.php?query=value#fragment
になる。
ポイント: REQUEST_URI は / から始まるとは限らない。リクエスト次第で http://... からの値を設定できる。
PHP では HTTP コンテキストオプションで request_fulluri を設定すると、こういう(不正な) *1リクエストを送れる。
request_fulluri boolean
TRUE を指定すると、リクエストを生成する際に完全な URI (GET http://www.example.com/path/to/file.html HTTP/1.0) が用いられます。これは標準のリクエストフォーマットではありませんが、 このようなフォーマットを要求するプロキシサーバも存在します。
デフォルトは FALSE です。
PHP: HTTP コンテキストオプション - Manual![]()
よく見るコードで、
$url = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
こんな風に書いてはいけない。これは、 REQUEST_URI だけでなく HTTP_HOST にも問題がある。
'HTTP_HOST'
PHP: $_SERVER - Manual
現在のリクエストに Host: ヘッダが もしあればその内容。![]()
ということは、
GET http://localhost/test.php?query=value#fragment HTTP/1.0
という Host ヘッダが無い HTTP/1.0 のリクエストを送ると、上の $url の値は、
http://http://localhost/test.php?query=value#fragment
になる。HTTP_HOST は空になり、 REQUEST_URI には http:// から始まる値が設定される。
ポイント: HTTP_HOST は値が存在しないかもしれないし、何でも好きな値を設定されるかもしれない。
Apache だとネームベースのバーチャルホストを設定していなければ、あるいは設定していても、適当な Host ヘッダで PHP をリクエストできる。
parse_url()
$_SERVER['REQUEST_URI'] を parse_url() でパースすれば、ホスト・パス・クエリ・フラグメントに分離できてハッピーだ。
と思ったのだが。
この関数は、指定された URL が有効かどうかを調べるためのもの ではなく、単に URL を上で示した 要素に分解するだけのものです。不完全な URL であっても受け入れられますし、 そのような場合でも parse_url() は可能な限り 正しく解析しようとします。
PHP: parse_url - Manual![]()
注意:
PHP: parse_url - Manual
この関数は相対 URL では動作しません。![]()
これ正確には、「相対URLを渡したらどうなっても知らんよ」という意味だった。
相対URLを渡しても大抵パースできるのだが、たまに予想外な失敗の仕方をする。
<?php var_dump(parse_url("/abc?a=x&time=09:00&x=y")); var_dump(parse_url("/abc?a=x&time=09:00"));
この結果は、
array(2) { ["path"]=> string(4) "/abc" ["query"]=> string(18) "a=x&time=09:00&x=y" } bool(false)
になる。上は成功するけど下は失敗するのですよ。驚きだよ。
ポイント: parse_url()に相対URLを渡しても動くように見えるが、渡してはいけない。
しかも、バグレポートが出てるのに却下してる。
ひどくね?
まとめ
PHP はひどい。