ファイルダウンロード時のファイル名が文字化けする対処法

ファイルをダウンロードするときのヘッダーは次のようになります。

header('Last-Modified: '. gmdate('D, d M Y H:i:s') .' GMT');
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$file_name.'"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file_path));

[参考記事] SSL(HTTPS)でファイルのダウンロードができない場合

ダウンロード時のファイル名の指定方法の仕様

ここでダウンロード時に保存されるファイル名として指定するのがContent-Disposition: filenameの部分です。

これはRFCではRFC1806

Content-Disposition: attachment; filename=filename.ext

を使用するように定義されています。
RFC2616の『15.5 Content-Disposition Issues』でも取り上げられています。

ファイル名を「"(ダブルクオート)」で囲うことも可能です。

Content-Disposition: attachment; filename="filename.ext"

さらにこのファイル名の指定についてはRFC2231で定義されています。

RFC2231の仕様としてはファイル名のエンコードを指定することもできます。

Content-Disposition: attachment;filename*=iso-2022-jp'ja'%1B%24B%24%5B%244%24%5B%242%1B%28B.jpeg

ファイル名を分割することもできます。

Content-Disposition: attachment;filename*0*=iso-2022-jp'ja'%1B%24B%24%5B%244%24%5B%242%1B%28B;filename*1=.jpeg

ブラウザによる違い

FirefoxやSafariではこのRFC2231に準拠していますが、Internet Explorerでは準拠していません。
これはIEのサポートページに明記されています。
http://support.microsoft.com/kb/436616/
ファイルをダウンロードする ASP.NET Web ページで日本語ファイル名が文字化けする問題が発生する。

Internet Explorerでは、Content-Disposition ヘッダが送信された場合、送られてきたコンテンツをそのままブラウザで開かずにファイルダウンロードダイアログを表示するようになっています。 その際にこのヘッダの filename パラメータを利用している場合、このパラメータで渡されたファイル名をファイルの保存時の既定のファイル名にします。 ただし、クライアントコンピュータのロケールを日本語にしている場合、Internet Explorer ではこのパラメータで渡された文字列 (ファイル名) を Shift-JIS でエンコードされているものとして処理します。
Content-Disposition ヘッダのパラメータの文字コードのエンコード方式に関しては、RFC2231 に基づくべきですが、現行の Internet Explorer はこのエンコード方式をサポートしていません。

ここでIneternet Explorerではファイル名をShift-JISとするように書かれています。

しかしIEでは文字コードに5cや7cを含むものは文字化けします。
http://support.microsoft.com/kb/930938/ja
5Cや7Cを含む場合、その下位バイト(トレイルバイト)が5Fに変わり、文字化けします。

これを避けるためにURLエンコードをする方法もありますが、URLエンコードをするとファイル名(エンコード時の文字数)が189文字を超える場合には、ファイル名の先頭から文字が欠損します。

Mac OS Xはファイル名が日本語を含む場合、トラブルが続出します。
ファイル名が一般的でないUTF-8の形式(例えば「が」を「か」+「゛」として扱っている)になっているかららしい。

対処法としてはContent-Dispositionにファイル名を指定しないのが一般的のようです。
どうしてもファイル名を指定したい場合には、HTTPヘッダーではなく、URLを変更して、次のようにしてファイル名を渡す方法があります。

http://www.example.com/download.php/ファイル名.pdf

またMacOS Xでは、Content-Dispositionにファイル名を指定しても

Content-Type: application/octet-stream

とすると、プログラムなどでファイルをダウンロードさせる場合には拡張子が「.html」になります。 このためファイルの種類ごと拡張子ごとにContent-Typeを指定します。

使用できない文字

またASCII文字であっても次の文字は問題が発生します。

文字説明
;HTMLヘッダーの区切り文字のため、ファイル名がここで途切れる
/保存するPCのファイルシステムでフォルダ階層を表す区切り文字のため、このファイル名を使用することはできない
?Windowsでは、保存ボタンを押しても反応しなくなる
#Internet Explorerで『_(アンダーバー)』に変わったり、文字が消えたりする
半角空白ファイル名がここで途切れる場合がある
これはファイル名を「"(ダブルクオート)」で囲うとよい

サンプルコード

これらを踏まえると、ファイルのダウンロード時のHTTPヘッダーを次のようにすると文字化けは回避されます。

$file_name = '【ファイル名】';
$ua = $_SERVER['HTTP_USER_AGENT'];

$file_name = str_replace("?","",$file_name);
$file_name = str_replace("/","",$file_name);
$file_name = str_replace(";","",$file_name);

$file_ext = strtolower(preg_replace("/^.*\.([^\.]+)$/","$1",$file_name));

if(!preg_match("/safari/i",$ua)){
  header('Content-Type: application/octet-stream');
}else{
  if($ext == 'pdf'){
    header('Content-Type: application/pdf');
  }elseif($ext == 'xls'){
    header('Content-Type: application/vnd.ms-excel');
  }elseif($ext == 'doc'){
    header('Content-Type: application/msword');
  }elseif($ext == 'ppt'){
    header('Content-Type: application/vnd.ms-powerpoint');
  }elseif($ext == 'csv'){
    header('Content-Type: text/csv');
  }elseif($ext == 'txt'){
    header('Content-Type: text/plain');
  }else{
    header('Content-Type: application/octet-stream');
  }
}

if(preg_match("/MSIE/i",$ua)){
  if (strlen(rawurlencode($file_name)) > 21 * 3 * 3) {
    $file_name = mb_convert_encoding($file_name, "SJIS-win","UTF-8");
    $file_name = str_replace('#', '%23', $file_name);
  }else{
    $file_name = rawurlencode($file_name);
  }
}elseif(preg_match("/chrome/i",$ua)){// Google Chromeには『Safari』という字が含まれる
}elseif(preg_match("/safari/i",$ua)){// Safariでファイル名を指定しない場合
  $file_name = "";
}
header('Content-Disposition: attachment; filename="'.$file_name.'"');

関連記事

スポンサーリンク

Javaをコマンドラインから実行する

ホームページ製作・web系アプリ系の製作案件募集中です。

上に戻る