BK通信 ―Bad Knowhow Tsushin―

#05ブラウザのバッドノウハウ コンテンツ編

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

IEのContent sniffing

通常、ブラウザはHTTPのレスポンスのContent-Typeヘッダに応じて、コンテンツをどのように処理するか決めますが、Internet Explorer 7(IE7)はこのヘッダを無視するときがあります[1]⁠。

たとえば、リスト1のようなファイルをtest.txtという名前でWebサーバに置いて、IE7からアクセスすると、サーバからContent-Type:text/plain(ただのテキストファイル)として送られてきているにもかかわらず、HTMLとして解釈されてしまいます図1⁠。

リスト1 test.txt
#! /usr/bin/perl
print "<html><h1>hello, world</h1></html>"
図1 HTMLとして解釈された
図1 HTMLとして解釈された

同様の現象はtext/plainに限らず、image/jpegなどの画像のContent-Typeが指定されているときにも起きます。このような挙動はContent sniffingと呼ばれています[2]⁠。

おそらく、この挙動はContent-Typeが間違っていてもコンテンツを正しく表示したい、という意図のもとに実装されているのだと思われます。しかし、Webアプリケーションの脆弱性につながる恐れがある挙動としても知られています。

先のリスト1の例では無害なHTMLタグが使われているだけですが、<script>タグでJavaScriptを埋め込むこともできます。これはユーザが任意のファイルをアップロードできるサイトで、とくに問題になります。テキストファイルや画像と見せかけて悪意のあるJavaScriptをアップロードして他人に開かせる、という攻撃が可能であるためです。

この自動判別の挙動は、IE8ではX-Content-Type-Options:nosniffというヘッダをHTTPのレスポンスに入れることで無効にできます。このような変な挙動は単に撤去すればいい気がしますが、この挙動に依存しているサイトを壊さないために互換性を維持しているのだと思います。

Content-Dispositionヘッダで解決?

前述した、本来のContent-Typeと異なる形式でコンテンツが解釈されてしまうという問題を回避するには、ブラウザにファイルを開かせる代わりに保存させればいいのでは、というアイディアがあります。具体的にはHTTPのレスポンスに、リスト2を加えます。このContent-Dispositionヘッダにより、ブラウザの中でコンテンツを開くのではなく、foo.txtというファイルとして保存せよ、とブラウザに伝えることができます。

リスト2 Content-Dispositionヘッダ
Content-Disposition: attachment;
 filename="foo.txt"

しかし、IEにはContent-Dispositionヘッダの扱いに過去に問題がいろいろあり[3]⁠、最近のバージョンでも問題が発見されているので[4]⁠、Content sningを防ぐためにContent-Dispositionに頼るのは安全ではありません。

危険性のあるテキストファイル[5]の内容をどうしても表示させたい場合は、Content sningの影響を受けないように先頭の256バイトを空白で埋める[6]⁠、HTMLの特別な文字をすべて&lt;、&gt;などにエスケープした上でHTMLとして表示させる、といった方法があります。

ところで、Content-Dispositionというヘッダは元々、メールで使われているMIMEというフォーマットを拡張するために導入されたものであり、正式にはHTTPの仕様には含まれません。HTTP 1.1の標準であるRFC 2616を見ると、Content-DispositionはHTTPの標準の一部ではないと明記されています[7]⁠。しかし、大半のブラウザはContent-Dispositionヘッダに対応しているので、RFC 2616の中でもこのヘッダの扱い方について言及されています。

日本語ファイル名の問題

前節のリスト2で、Content-Dispositionヘッダのlenameパラメータで、ファイル名を指定できることを示しました。では、日本語のファイル名はどのように扱えばいいのでしょうか。これは、実は奥が深い問題です。

単純に考えると、リスト3のように日本語の文字をそのまま使えばいいような気がしますが、この場合、どの文字エンコーディングを使うかが問題になります。

たとえば、UTF-8を使うと、IE7では「縺ゅ>縺・txt」のようにファイル名が文字化けしてしまいます。

リスト3 日本語の文字そのまま
Content-Disposition: attachment;
 filename=" あいう.txt"

日本語ファイル名とメールの世界

メールの世界でも、添付ファイルの日本語ファイル名を扱うのはややこしい問題として知られています。多くのメーラはリスト4のようにエンコードします。

先頭の=?UTF-8?B?は文字エンコーディングがUTF-8であること、BASE64でデータがエンコードされていることを意味しています。このようなエンコード方式はRFC 2047で定義されており、encodedwordと呼ばれています。

リスト4 よくあるエンコード
Content-Disposition: attachment;
 filename="=?UTF-8?B?44GC44GE44GGLnR4dA==?="

実は、上のContent-Dispositionは厳密にはRFC 2047に違反しています。RFC 2047によると、encoded-wordは"(ダブルクォート)で囲まれた文字列の中に含めるのは禁止されているためです[8]⁠。

メールの標準的に正しい方法はRFC 2231に則ったリスト5のような形式です。

リスト5 RFC 2231に則ったエンコード
Content-Disposition: attachment;
 filename*=UTF-8''%E3%81%82%E3%81%84%E3%81%86.txt

しかし、この「正しい」方法をサポートしていないメーラもあるため、歴史的に、前述の「正しくない」方法のほうが普及しています。

日本語ファイル名とWebの世界

本題に戻って、Webの世界ではどうすべきかというと、これは厄介な問題です。以下の6パターンを調査してみました。

%+ UTF-8
filename=%E3%81%82%E3%81%84%E3%81%86.txt
%+ Shift_JIS
filename=%82%A0%82%A2%82%A4.txt
生UTF-8
filename= あいう.txt
生Shift_JIS
filename= あいう.txt
RFC 2231
filename*=UTF-8''%E3%81%82%E3%81%84%E3%81%86.txt
RFC 2047
filename="=?UTF-8?B?44GC44GE44GGLnR4dA==?="

結果は以下のとおりです[9]⁠。

 %+
UTF-8
%+
SJIS
生UTF-8生SJISRFC
2231
RFC
2047
FF3【Win】××
FF3【Mac】×××
IE7××××
Chrome×××
Safari3【Mac】××××××

ひとまず、6パターン全滅しているSafariを無視して考えれば、FirefoxのときはRFC 2231形式、IE7とChromeのときはファイル名をUTF-8でパーセントエンコードすればよさそうです。生のShift_JISを使うのは、日本語ロカール(locale)以外のWindowsが使われている可能性を考えると、避けたいところです(他の言語のWindowsにShift_JISでファイル名を送ると文字化けします⁠⁠。

IE7が長いファイル名を切り詰める問題

ところが、パーセントエンコードには落とし穴がありました。

IEには一定の条件を満たすと長いファイル名を短く切り詰めるという仕様があり、パーセントエンコードを使った場合、この条件が比較的簡単に満たされてしまいます。

たとえば、⁠日本語の長いファイル名をつけたいときもあります.txt」というファイル名をパーセントエンコードして、リスト6のようなlenameパラメータを送ると、手元の環境では「%81%84ファイル名をつけたいときもあります.txt」というファイル名に切り詰められてしまいました図2⁠。

リスト6 パーセントエンコードで送る
filename=%E6%97%A5%E6%9C%AC%E8%AA%9E%
E3%81%AE%E9%95%B7%E3%81%84%E3%83%95%
E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D%
E3%82%92%E3%81%A4%E3%81%91%E3%81%9F%
E3%81%84%E3%81%A8%E3%81%8D%E3%82%82%E3
%81%82%E3%82%8A%E3%81%BE%E3%81%99.txt
図2 切り詰められた
図2 切り詰められた

一方、生のShift_JISを使って次のようなlenameパラメータを送った場合はファイル名は切り詰められません。

  • filename=日本語の長いファイル名をつけたいときもあります.txt

Microsoftのフォーラムの情報によると、IEは「インターネット一時ファイル内のキャッシュ」のファイル名の長さに制限があり、パーセントエンコードを使うとこの制限を簡単に超えてしまうというのが問題のようです。

というわけで、長い日本語ファイル名を使いたい場合(かつ日本語ロカールのWindowsを想定してもいい場合⁠⁠、IEには生のShift_JISを送るのがいいようです。

MacのSafari 3の対処法

前述のとおり、MacのSafari 3ではContent-Dispositionヘッダのlenameを使って日本語のファイル名を指定するのは諦めるしかなさそうです。

ただし、まったく対処法がないわけではなく、Safari 3はContent-Dispositionにlenameが含まれない場合はURLのパスの部分を元にファイル名を決めるので、Content-Dispositionヘッダにはattachmentパラメータだけを含め、次のようなURLにすれば「あいう.txt」を保存させることができます図3⁠。

  • http://localhost/webapp/%E3%81%82%E3%81%84%E3%81%86.txt
図3 やっとできた
図3 やっとできた

残念ながら、次のようにURLのクエリ部分(?の後ろ)に指定しても無視されてしまいます。

  • http://localhost/webapp/foo.cgi?dummy=%E3%81%82%E3%81%84%E3%81%86.txt

Webアプリケーションの作り方によっては、Safari 3の問題を回避するURLを提供するのは難しいかもしれません。

まとめ

今回は、コンテンツの扱いに関連するブラウザのバッドノウハウを紹介しました。ブラウザでファイルをダウンロードするなど簡単なことに思えますが、実は膨大なるBKが必要な高度な技であることがわかりました。とくに、日本語ファイル名の扱いはWebの時代になっても相変わらずややこしく、永遠のBKテーマなのではないかと思います。

おすすめ記事

記事・ニュース一覧