結論
Content-Type: text/plain
な入力を Content-Type: text/html
な環境の中に正しく出力させるため。
もう議論され尽くした内容だが、このような言い回しの人は見たことがなかったので敢えて記事にしてみる。
解説
- ユーザがテキストボックスを通じて入力するのは
text/plain
、つまりただのテキスト。ただのテキストであるから装飾する機能は無い。 - プログラマがユーザ入力を元にPHPで出力するのは
text/html
、つまりHTML。HTMLには見出しを示したり、太字にしたり、下線を引いたり、CSSやJavaScriptを呼び出す機能もある。 -
htmlspecialchars は
text/plain
をtext/html
に変換する。<
>
を<
>
に変換するというのはあくまで一例に過ぎない、と考えるべきである。
通常、ユーザ入力は text/plain
であるべきだ。但し、一部のタグや一部の属性のみを許可するといった処理を厳重なコーディングのもとに行っている場合、ユーザ入力がtext/html
であると見なすことも許されるであろう。実績のある代表的なライブラリとしては HTML Purifier が挙げられる。
余談
- 上記では簡略化のため説明を割愛したが、htmlspecialcharsは事実上第3引数の文字コード指定までは必須と言われる。以下のコードは
text/plain; charset=UTF-8
からtext/html; charset=UTF-8
への変換を行う。
$out = htmlspecialchars($in, ENT_QUOTES, 'UTF-8');
- Content-Typeのうちcharsetのみを変換する場合も考えられる。以下のコードは
text/plain; charset=Shift_JIS
からtext/plain; charset=UTF-8
、あるいはtext/html; charset=Shift_JIS
からtext/html; charset=UTF-8
への変換を行う。
$out = mb_convert_encoding($in, 'UTF-8', 'SJIS');
- SQL文にユーザ入力を埋め込む際にhtmlspecialcharsを用いるのは不適切である。求められる変換は
text/plain
からtext/html
ではなく、text/plain
からapplication/sql
(における文字列リテラル) だ。この処理はプリペアドステートメントとそれに付随するプレースホルダが役割として担うものである。
$stmt = $pdo->prepare('SELECT * FROM table WHERE name = :name');
$stmt->bindValue(':name', $name);
$stmt->execute();
- PHPで作るWebページは必ず
text/html
になるのか?と言われればそうではなく、text/plain
として出力することも可能である。この場合htmlspecialcharsは不要になる。
<?php
header('Content-Type: text/plain; charset=UTF-8');
echo '<script>alert(1)</script>'; // IE以外ではJavaScriptは実行されない (コメント欄参照)