BK通信 - ブラウザのバッドノウハウ <form> 編

最終更新日: 2008-09-15

WEB+DB PRESS Vol. 47 に向けて書いた記事の元の原稿です。


ソフトウェアなどを使いこなすために、ストレスを感じながらもしぶしぶ覚えなければならないようなノウハウ、「バッドノウハウ」がテーマの本連載、第4回の今回はブラウザのBK を、 <form> タグに関連するものに絞って取り上げたいと思います。

URL の + と %20 の関係

HTML の <form> タグを使うと、ブラウザからサーバにデータを送ることができます。<form> に method="GET" という属性が指定されている場合、ブラウザは

http://example.com/webdb.cgi?key1=value1&key2=value2

のように、キーと値のペアを URL の末尾に付加してサーバにリクエストを送ります。これらのペアをクエリーと呼びます。

このときキー、あるいは値に = などの予約記号が含まれている場合、 %3D のように % + 16進数でエンコードします*1

ところが、これには例外があり ASCIIの空白は + に変換されます。しかし、 URI *2 を定義している仕様を見てもクエリー内の空白は + に変換せよとは書かれていません*3

ではどこから、+ がやってくるのかというと HTML の規格の中で、フォームデータのエンコード形式である application/x-www-form-urlencoded では「空白は + で置き換えられる」と明記されています *4。つまり空白を+に置き換えるという仕様は URL のクエリー部分にだけ適用されます。

よって、空白を含むファイル名 "foo bar.html" に対する URL のつもりで、

http://example.com/foo+bar.html

を用いると、 foo+bar.html というファイル名として解釈されてしまいます。 空白を %20 でエンコードするのが正解です。

http://example.com/foo%20bar.html

%20 と + を使い分けるのはややこしいので、個人的には常に %20 を使えばいいような気がします。

+の存在意義って一体...
+の存在意義って一体...

<form> の accept-charset パラメータ

ブラウザからサーバにデータを送る場合になるのが、テキストデータに使われる文字エンコーディングです。

通常、ブラウザはページで使われているのと同じエンコーディングでテキストデータをサーバに送信します。あるいはaccept-charset という属性を <form> に指定すれば、ページと異なるエンコーディングで送信することもできます。しかし、 IE7 はaccept-charset を無視します*5

実験は簡単です。次のようなファイルを test.html と保存して Shift_JIS で保存して IE7 に読み込ませます。

<meta http-equiv="content-type"
      content="text/html; charset=Shift_JIS">
<form method="GET" accept-charset="UTF-8">
<input type="text" name="foo">
</form>

表示されるフォームに「あいう」と入力して Enter キーを押すと、次のような画面が表示されます。

Shift_JIS でエンコードされた
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 はこれを無視します。万事休す。。と思いきや、ここでバッドノウハウの登場です。

以下のようにフォームに _charset_ というパラメータを hidden で追加します。

<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_">     <- この1行を追加
</form>

そして、先ほどと同様にIEからアクセスして「あいう」を入力してみると。。

_charset_=shift_jis が追加された
_charset_=shift_jis が追加された

なんと、URL に _charset_=shift_jis というエンコーディング名が入りました。サーバ側のプログラムはこの値からテキストデータのエンコーディングを特定できます。

隠しパラメータとは裏技のごとし
隠しパラメータとは裏技のごとし

ただし、悪意のあるユーザが意図的に間違った _charset_ をサーバに送ってくるおそれがあるので、_charset_ を完全に信用すると文字化けなどの原因になります。厳密に処理するには、入力データが_charset_ で指定されたエンコーディングのテキストとして正しいバイト列か検査するとよいでしょう。

実はこの _charset_ の仕様は 2002 年の時点で Mozilla に移植されていて、Firefox からも利用できます *6。 Mozilla では一時期、フォームに POST するときの HTTP リクエストヘッダの Content-Type に charset を追加することを検討したようですが *7、 charset パラメータを付加すると問題が起きるサーバプログラムがあまりに多かったため結局中止したようです。

他のやり方としては

<input type="hidden" name="test" value="あ">

のように隠しテキストを入れておいて、このテキストがどのようにエンコードされたかによってエンコーディングを判別するという方法もあります。

まとめ

今回は <form> タグに関連するブラウザのバッドノウハウを 3つ紹介しました。とりわけ _charset_ はバッド度の高い BK だと思います。次回はその他のブラウザのBKを紹介する予定です。


*1これをパーセントエンコードと呼びます。RFC 3986 2.1. Percent-Encoding
*2Uniform Resource Identifier の略。URL は URI の一種
*33.4. Query の部分
*4<http://www.w3.org/TR/html401/interact/forms.html> の 17.13.4 Form content types
*5IE 7.0.5730.13 で動作検証しました。IE6 でもおそらく同様ですが、手元に動作検証できる環境がないため今回は IE7 に絞って話を進めます。
*6<https://bugzilla.mozilla.org/show_bug.cgi?id=18643>
*7<https://bugzilla.mozilla.org/show_bug.cgi?id=7533>