WordPressのテーマを変更しました!

【CakePHP3.6】ファイル名にマルチバイトが含まれるファイルをダウンロードした際に遭遇するトラブルと回避策

CakePHP3.6で開発を行っていると、ファイル名にマルチバイトを含むファイルをダウンロードするとブラウザによっては、正しいファイル名でダウンロードされません。そういった経験はないでしょうか。

今回はその回避策を紹介します。

条件

ファイル名マルチバイトを含むファイル名
ファイルの中身CSVデータ
拡張子hoge(独自拡張子)
ブラウザEdge、Internet Explorer 11、Chrome、Safari、Firefox
※2018年09月現在最新

ソース

PHP
public function download()
{
    //レンダリングを解除
    $this->autoRender = false;

    //ダウンロードファイルのファイル名
    //※hoge:CSVデータ。独自拡張子
    $fileName = 'マルチバイトファイル.hoge';

    //ダウンロードファイルのパス
    $path = "/tmp/$fileName";

    //User-Agentの取得
    $userAgent = strtolower(env('HTTP_USER_AGENT'));

    //Edge、Internet Explorerに対応
    if (strpos($userAgent , 'edge') || strpos($userAgent , 'trident') || strpos($userAgent , 'msie')) 
    {
        //ファイル名をURLエンコードする
        $fileName = rawurlencode($fileName);
    }

    //Locationを指定する
    setlocale(LC_ALL, 'ja_JP.UTF-8');

    //CakePHP3のResponseに設定する
    $response = $this->response->withFile(
        $path, [
            'name' => $fileName, 
            'download' => true
        ]
    );

    //Safari対応
    if (strpos($userAgent , 'edge') === false &&
        strpos($userAgent , 'trident') === false &&
        strpos($userAgent , 'msie') === false &&
        strpos($userAgent , 'chrome') === false &&
        strpos($userAgent , 'firefox') === false &&
        strpos($userAgent , 'safari')) 
    {
        //mimeTypeによって勝手に拡張子が付与されるのを回避
        $response = $response->withType('text/hoge');
    }
    else
    {
        //その他のブラウザの場合は中身がCSVで有ることを明記
        $response = $response->withType('text/csv'); 
    }
    
    return $response;
}

解説

上記のソースのポイントです。

Edge、Internet Explorer11でダウンロードしたファイル名が文字化けする

PHP
if (strpos($userAgent , 'edge') || strpos($userAgent , 'trident') || strpos($userAgent , 'msie')) 
{
    $fileName = rawurlencode($fileName);
}

EdgeやInternet Explorer 11からファイルをダウンロードする場合、
マニュアルどおりに実装すると、マルチバイトの部分が文字化けしたファイル名でダウンロードされます。

その文字化けを回避するには、User-AgentからアクセスしたブラウザがEdgeなのかInternet Explorer 11なのか判別し、ダウンロード時のファイル名をURLエンコードする必要があります。

SplFileInfoの5C問題

PHP
setlocale(LC_ALL, 'ja_JP.UTF-8');

$this->response->withFileの処理内で呼ばれる「SplFileInfo」は、マルチバイトを含むファイル名が正しく取得できません。
そのため、ダウンロード対象のファイルが存在しているにもかかわらず、取得したファイル名が正しくないためファイルの存在チェックで失敗します。

ファイル名を正しく取得し、この事象を回避するには、ロケールを『UTF-8』に設定することで、マルチバイトを含むファイル名も正しく取得できるようになり、ファイルの存在チェックが正しく行われるようになります。

独自拡張子への対応

PHP
if (strpos($userAgent , 'edge') === false &&
    strpos($userAgent , 'trident') === false &&
    strpos($userAgent , 'msie') === false &&
    strpos($userAgent , 'chrome') === false &&
    strpos($userAgent , 'firefox') === false &&
    strpos($userAgent , 'safari')) 
{
    $response = $response->withType('text/hoge');
}
else
{
    $response = $response->withType('text/csv'); 
}

独自の拡張子を使用する場合、注意が必要です。

SafariではダウンロードするファイルのMimeに対応する拡張子が付いていない場合、自動的に付加する仕様になっています。

また、CakePHP3.6では明示的にMimeを指定しない場合、『text/html』がMimeとして設定されるため、Safariでダウンロードすると「.html」が必ず付加されてしまいます。

この「.html」の付与を回避するには、自動で付加されないようにMimeを明示的に設定し、独自拡張子のままダウンロードできるようにする必要があります。