2014/3/2 改版
IE9 より RFC 6266 (RFC 2231/RFC 5987) がサポートされたということで、その点を考慮してこの記事を全面書き換えました。
ASP.NET Web アプリでファイルをダウンロードする際、AppendHeader メソッドを使って Content-Disposition ヘッダフィールドの attachment; filename= に日本語のファイル名を設定し、受信側のブラウザに IE を使用すると、ファイル名が以下の画像に示すように文字化けします。(以下の画像の例では、オリジナルのファイル名を "日本語のファイル名.xls" としています)
その理由は MSDN Blog の記事 Downloads and International Filenames をそのまま引用すると "Early versions of Internet Explorer worked around this limitation by assuming that any non-ASCII characters within HTTP headers were encoded using the local system's Windows codepage" ということだそうです。
上記の英文を具体的に説明すると次の通りです。
例えば以下のコードのよう AppendHeader メソッドを使ってファイル名を設定すると、応答ヘッダで "日本語" の部分は UTF-8 のバイト列 e6 97 a5 e6 9c ac e8 aa 9e となります。(Fiddler2 の HexView で見ると分かります。ASP.NET Web アプリではデフォルトでは応答ヘッダはすべて UTF-8 になりますので、ファイル名の部分も UTF-8 のバイト列として送信されます)
Response.AppendHeader("Content-Disposition",
"attachment;filename=日本語.xls");
上の MSDN Blog に書いてあるように、応答ヘッダに ASCII コード以外の文字(上の例では e6 97 a5 e6 9c ac e8 aa 9e)がある場合、その部分は自分の PC の Windows codepage で、即ち日本語 OS の場合は Shift_JIS として解釈されます。
IE は e6 97 a5 e6 9c ac e8 aa 9e を Shift_JIS コードとして解釈しようとするので、正しくデコードできず文字化けするというわけです。(ちなみに、Shift_JIS では "日本語" は 93 fa 96 7b 8c ea になります)
解決策は、マイクロソフト サポートオンラインの記事 ファイルをダウンロードする ASP.NET ページで日本語ファイル名が文字化けする に以下の 3 つの方法が紹介されています。
-
HttpServerUtility.UrlEncode メソッドを使ってファイル名をエンコードする。
-
HttpResponse.HeaderEncoding プロパティを使って Shift_JIS のヘッダを送信する。
-
RFC 6266 に準拠して attachment; filename*= UTF-8''%e2%82%ac%20rates のようにする。
1 番目の方法は、一部のブラウザが対応していないという点が問題です。IE, Firefox, Chrome, Safari, Opera で検証した結果は以下の表の通りでした。
ブラウザ
|
UrlEncode
|
あり
|
なし
|
IE
|
OK
|
NG 文字化けする
|
Firefox
|
NG エンコードされた文字になる
|
OK
|
Chrome
|
OK
|
OK
|
Safari
|
NG エンコードされた文字になる
|
OK
|
Opera
|
OK
|
OK
|
上記の結果は、最初にこの記事を書いた 2011 年 3 月 20 日の時点で検証に使ったブラウザ IE8, Firefox 3.6.15, Chrome 10.0.648.151, Safari 5.0.4, Opera 11.01 でも、この記事を改版した 2014 年 3 月 2 日の時点で検証に使ったブラウザ IE9, Firefox 27.0.1, Chrome 33.0.1750.117, Safari 5.1.7, Opera 12.16 でも同じでした。
Firefox と Safari の場合は、UrlEncode するのはダメで、%e6%97%a5%e6%9c%ac%e8%aa%9e.xls のようなエンコードされた名前になってしまいます。Chrome と Opera は UrlEncode 有り/無しどちらも OK でした。
また、IE の場合でも、ファイル名に半角空白が含まれているとうまく行きません。UrlEncode メソッドは、半角空白を + に変換するので、例えば "file name.txt"(file と name の間に半角空白)" は "file+name.txt" になってしまいます。なので、半角空白は URL エンコードせず、そのままにしておく必要があります。
半角空白を + ではなく %20 に変換(パーセントエンコーディング)しても問題があります。IE 側で開く「ファイルのダウンロード」ダイアログで[開く(O)]をクリックするとメモ帳が開いて中身が表示されますが(もちろん拡張子 txt がメモ帳に関連付けられているとして)、メモ帳のウィンドウの左上に表示されるファイル名の空白は %20 のままになります。(上記の例では "file%20name.txt" となる) まぁ、これは大した問題ではないのかもしれませんが、気分がよくないです。(笑)
2 番目の方法(Shift_JIS のヘッダを送信)は IE ではうまく行きますが、当然、IE 以外のブラウザは対応できません。
また、ファイル名を Shift_JIS にすると、IE 自体は対応できても、それ以前にプロキシサーバーで文字化けして、結局、文字化けは回避できないという可能性があります。それに、単にファイル名が文字化けするだけでなく、他に予期しない好ましからざる副作用が出る恐れもあります。従って、2 番目の方法は、使用するブラウザが IE に限定される場合でも避けた方が賢明だと思います。
3 番目の方法は RFC 6266 をサポートしていないブラウザ(IE8-、Safari 5.1.7 など)には使えないという問題があります。また、これも、UrlEncode メソッドで、半角空白が + に変換されることにより、"file name.txt"(file と name の間に半角空白)" が "file+name.txt" になってしまうという問題があります。半角空白はパーセントエンコーディング("+" ではなくて "%20" に変換)しないとうまく行きません。
結局、現状ではファイル名には ASCII 文字以外は使わないというのが一番の解決策だと思います。
どうしても日本語のファイル名を使って、かつ IE, Firefox, Chrome 等の複数のブラウザに対応するなら、以下のようにブラウザによって応答ヘッダを切り替える他方法はなさそうです。
(注)別の記事 ダウンロードは HTTP ハンドラで で書きましたように、ファイルなどをダウンロードするのに .aspx ページを利用するといろいろ問題がありそうなので、以下のサンプルコードでは HTTP ハンドラを使用しています。
<%@ WebHandler Language="C#" Class="DownloadHandler" %>
using System;
using System.Web;
public class DownloadHandler : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
HttpResponse response = context.Response;
HttpRequest request = context.Request;
string fileName = "日 本 語 (+japanese+).txt";
// + は %2b に変換され、空白は + に変換される。
string encodedFileName = context.Server.UrlEncode(fileName);
encodedFileName = encodedFileName.Replace("+", "%20");
response.Clear();
response.ContentType = "text/plain";
// キャッシュを許可しない
response.Cache.SetCacheability(HttpCacheability.NoCache);
response.Cache.SetExpires(DateTime.Now.ToUniversalTime());
response.Cache.SetMaxAge(new TimeSpan(0, 0, 0, 0));
HttpBrowserCapabilities browser = request.Browser;
// IE の場合。
// IE11 では Browser プロパティは "Mozilla" (.NET 2.0)
// または "InternetExplorer" (.NET 4) になる。IE の場合
// User Agent には必ず "Trident" と言う文字列が入ってい
// るらしいので、そちらで判定した方がよさそう。
if (browser.Browser.ToUpper().IndexOf("IE") >= 0 ||
request.UserAgent.Contains("Trident"))
{
// IE8 以下(RFC 6266 未サポート)
// 空白を %20 のままにしておくと処理がうまくいかない。
// 「ファイルのダウンロード」ダイアログで[開く(O)]を
// クリックするとメモ帳が開いて中身が表示されるが %20
// がそのままウィンドウの左上に表示されるファイル名に
// 含まれる。それが気になる場合は、以下のように %20 を
// 空白に置き換える。(注:「ファイルのダウンロード」
// ダイアログで[保存(S)]をクリックすれば何故か %20
// は空白に変換されて保存される)
if (browser.MajorVersion < 9)
{
response.AppendHeader("Content-Disposition",
"attachment;filename=\"" +
encodedFileName.Replace("%20", " ") + "\"");
}
// IE9 以上(RFC 6266 サポート)
else
{
response.AppendHeader("Content-Disposition",
"attachment;filename*=utf-8''" + encodedFileName);
}
}
// IE 以外
else
{
// Safari 5.1.7 がまだ RFC 6266 未対応らしいので
// filename="xxxxx" の併記が必要。
response.AppendHeader("Content-Disposition",
"attachment;filename=\"" + fileName +
"\";filename*=utf-8''" + encodedFileName);
}
response.Write("こんにちは世界!");
}
public bool IsReusable
{
get
{
return false;
}
}
}
なお、IIS7 で試した限りですが(他のサーバーは不明)、以下のコードのように HyperLink で直リンクすれば、どのブラウザでも文字化けはありませんでした。
<asp:HyperLink
ID="HyperLink2"
runat="server"
NavigateUrl="~/日 本 語 (japanese).zip">
日 本 語 (japanese).zip
</asp:HyperLink>
その理由は、ブラウザから GET 要求する url のファイル名はパーセントエンコーディング(半角空白は "+" ではなく "%20")され、応答ヘッダには以下のように正しく Content-Type が指定されると共に Content-Disposition フィールドが含まれないからです。
HTTP/1.1 200 OK
Content-Type: application/x-zip-compressed
Last-Modified: Fri, 21 Feb 2014 14:39:36 GMT
Accept-Ranges: bytes
ETag: "752e46be122fcf1:0"
Server: Microsoft-IIS/7.0
X-Powered-By: ASP.NET
Date: Sun, 02 Mar 2014 07:42:01 GMT
Content-Length: 1956
なお、直リンクする場合、NavigateUrl に指定するファイル名に "+" を入れると、IIS7 では HTTP エラー 404.11(ダブルエスケープシーケンスを含む要求の拒否)になるので注意してください。