読者です 読者をやめる 読者になる 読者になる

元Web系エンジニアのごはんブログ

JavaとかKotlinとかのごはん関係のブログです。

(今は)意外と問題なかった名前が日本語のファイル名のダウンロードのはなし

昔検証したこともあったのですが、現在アクティブなブラウザでは名前が日本語のファイルのダウンロードがどうなっているのか気になりました。

少し前までは、ユーザーエージェントによる分岐等をしないといけないような状態でした。

qiita.com

実際に昔からの方法や、新しい?方法等でいろいろ試した結果をまとめましたのでそれを紹介します。

確認に使用したファイル名は「あ①Ⅰ + 🐸.txt」(あ、まるいち、ローマ数字のいち、半角スペース、プラス記号、全角スペース、かえる)です。

動作確認した環境と結果は次のとおりです。

OS ブラウザ バージョン ①URLエンコードのみ ②RFC6266 filnename*=utf-8'' MIME B ④ISO-8859-1によるエンコード
macOS Safari 10.1
Chrome 57
Firefox 52
Windows 10 Edge 15
IE 11
Chrome 57
Firefox 52
Windows 7 IE 9 △(🐸は化ける) △(🐸は化ける)
Android 7.1.2 Chrome 57
Firefox 52

結論的に2017年4月7日現在サポートされているブラウザーは基本的に②のRFC6266の方法で実装すれば、日本語は文字化けしなさそうです。幾つかのパターンに分岐しないときちんと処理できないだろうと思っていたので、いささか拍子抜けしてしましました。

最後に、コードの主要な部分を紹介します。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.mail.internet.MimeUtility;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;

public class ResponseUtil {
    public static String urlEncode(String fileName) {
        // Java標準では、半角スペースが+にエンコードされる
        // String encodeFileName = URLEncoder.encode(fileName, "UTF-8");
        // Commons Codecでは、半角スペースが+に、+が%2Bにエンコードされる
        URLCodec codec = new URLCodec("UTF-8");
        String encodeFileName = "";
        try {
            encodeFileName = codec.encode(fileName);
        } catch (EncoderException e) {
            // 来ないはず
            return "bad_fine_name";
        }
        return encodeFileName.replaceAll("\\+", "%20").replaceAll("%2B", "+");
    }

    // ①URLエンコードのみ
    public static void download1(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();
        String encodeFileName = urlEncode(fileName);

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition", "attachment; filename=\"" + encodeFileName + "\"");
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

    // ②RFC6266
    public static void download2(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();
        String encodeFileName = urlEncode(fileName);

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition", "attachment; filename*=utf-8''" + encodeFileName);
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

    // ③MIME B
    public static void download3(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition",
                "attachment; filename=\"" + MimeUtility.encodeWord(fileName, "UTF-8", "B") + "\"");
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

    // ④ISO-8859-1によるエンコード
    public static void download4(File file, HttpServletResponse response) throws IOException {
        String fileName = file.getName();

        response.setContentType("application/octet-stream");
        response.setContentLength((int) file.length());
        response.addHeader("Content-Disposition",
                "attachment; filename=" + new String(fileName.getBytes(), "ISO-8859-1"));
        IoUtil.copy(new FileInputStream(file), response.getOutputStream());
    }

}

Gradleで次のライブラリーを使用しています。

compile 'commons-codec:commons-codec:1.10'
compile 'javax.mail:mail:1.4.7'

全体の動くサンプルはGitHubにあります。(GitHub - webarata3/FileDownload: Java Servletによる日本語ファイルのダウンロードのサンプルです