ソフトウェアなどを使いこなすために、ストレスを感じながらもしぶしぶ覚えなければならないようなノウハウ、「 バッドノウハウ」がテーマの本連載、第4回の今回はブラウザのBKを、<form>タグに関連するものに絞って取り上げたいと思います。
URLの+と%20の関係
HTMLの<form>タグを使うと、ブラウザからサーバにデータを送ることができます。<form>にmethod="GET"という属性が指定されている場合、ブラウザは、以下のように、キーと値のペアをURLの末尾に付加してサーバにリクエストを送ります。
http://example.com/webdb.cgi?key1=value1&key2=value2
これらのペアを「クエリ」と呼びます。このときキー、あるいは値に=などの予約記号が含まれている場合、%3Dのように%+16進数でエンコードします[1] 。
ところが、これには例外がありASCIIの空白 は「+」に変換されます。しかし、URI[2] を定義している仕様を見てもクエリ内の空白は+に変換せよとは書かれていません注3。では、どこから+がやってくるのかというとHTMLの規格の中で、フォームデータのエンコード形式であるapplication/x-www-form-urlencodedでは「空白は+で置き換えられる」と明記されています[4] 。つまり、空白を+に置き換えるという仕様は、URLのクエリ部分にだけ適用されます。
よって、空白を含むファイル名"foo bar.html"に対するURLのつもりで、リスト1 ①のようにすると、foo+bar.htmlというファイル名として解釈されてしまいます。空白を%20でエンコードするのが正解です(リスト1②) 。
%20と+を使い分けるのはややこしいので、個人的には常に%20を使えばいいような気がします。
リスト1 空白は%20でエンコード
①http://example.com/foo+bar.html
②http://example.com/foo%20bar.html
<form>のaccept-charsetパラメータ
ブラウザからサーバにデータを送る場合に問題になるのが、テキストデータに使われる文字エンコーディングです。通常、ブラウザはページで使われているのと同じエンコーディングでテキストデータをサーバに送信します。あるいはaccept-charsetという属性を<form>に指定すれば、ページと異なるエンコーディングで送信することもできます。しかし、Internet Explorer 7(以下、IE7)はaccept-charsetを無視します[5] 。
実験は簡単です。リスト2 のようなファイルをShift_JISで保存してIE7に読み込ませます。表示されるフォームに「あいう」と入力してcキーを押すと、図1 のような画面が表示されます。
リスト2 test.html
<meta http-equiv="content-type"
content="text/html; charset=Shift_JIS">
<form method="GET" accept-charset="UTF-8">
<input type="text" name="foo">
</form>
図1 Shift_JISでエンコードされた
URLの末尾に「?foo=%82%A0%82%A2%82%A4」が付加されました。これは「あいう」がShift_JISでエンコードされていることを意味します。accept-charsetに指定したUTF-8は無視されました。
IE7に限らず、他のプログラムもaccept-charsetを守ってくれるとは限りませんから、あくまでもaccept-charsetはおまじない程度に考えるのがよさそうです。
<form>の_charset_パラメータ
フォームから送られてきたテキストデータを正しく扱うには、どのエンコーディングが使われているかサーバ側のプログラムで把握する必要があります。一番簡単なのは、すべてのページをUTF-8にして、フォームからはすべてUTF-8でデータを受け取るとると決める方法です。しかし、場合によっては、歴史的な理由により、ページによってマチマチなエンコーディングが使われているかもしれません(古いページはすべてShift_JISなど) 。こんなときこそaccept-charsetが役立つはずなのですが、前述のとおりIEはこれを無視します。万事休す...と思いきや、ここでバッドノウハウの登場です。
リスト3 のように、フォームに_charset_というパラメータをhiddenで追加します。そして、先ほどと同様にIEからアクセスして「あいう」を入力してみると...(図2 ) 。
リスト3 _charset_パラメータをセット
<meta http-equiv="content-type"
content="text/html; charset=Shift_JIS">
<form method="GET" accept-charset="UTF-8">
<input type="text" name="foo">
<input type="hidden" name="_charset_">
</form>
図2 _charset_=shift_jis が追加された
なんと、URLに_charset_=shift_jisというエンコーディング名が入りました。サーバ側のプログラムは、この値からテキストデータのエンコーディングを特定できます。ただし、悪意のあるユーザが意図的に間違った_charset_をサーバに送ってくる恐れがあるので、_charset_を完全に信用すると文字化けなどの原因になります。厳密に処理するには、入力データが_charset_で指定されたエンコーディングのテキストとして正しいバイト列か検査するとよいでしょう。
実は、この_charset_の仕様は2002年の時点でMozillaに移植されていて、Firefoxからも利用できます 。Mozillaでは一時期、フォームにPOSTするときのHTTPリクエストのContent-Typeヘッダにcharsetを追加することを検討したようですが 、charsetパラメータを付加すると問題が起きるサーバプログラムがあまりに多かったため、結局中止したようです。
他のやり方としては、リスト4 のように隠しテキストを入れておいて、このテキストがどのようにエンコードされたかによってエンコーディングを判別するという方法もあります。
リスト4 隠しテキスト
<input type="hidden" name="test" value=" あ">
まとめ
今回は、<form>タグに関連するブラウザのバッドノウハウを3つ紹介しました。とりわけ、_charset_はバッド度の高いBKだと思います。次回は、その他のブラウザのBKを紹介する予定です。