the KodeLab

Content-Disposition で Unicode ファイル名を扱う方法|Java / Spring の IllegalArgumentException 対策

2,072 文字 6 分で読めます
Content-Disposition で Unicode ファイル名を扱う方法|Java / Spring の IllegalArgumentException 対策

問題

Web バックエンドからファイルを直接配信する場面では、HTTP Response の Content-Disposition を設定します。Content-Disposition: inline ならブラウザで直接開き、Content-Disposition: attachment; filename="File.txt" ならダウンロードダイアログを開いて事前に指定したファイル名を提示します。

Spring MVC で GET API としてファイルをダウンロードさせる最小の例です。

@GetMapping
public ResponseEntity<Resource> download() {
    String disp = "attachment; filename=\"レポート.txt\"";
    String mime = "text/plain";
    Resource res = new UrlResource(...);
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, disp)
        .header(HttpHeaders.CONTENT_TYPE, mime)
        .body(res);
}

実行すると Java から以下のエラーが投げられます。

java.lang.IllegalArgumentException: The Unicode character [レ] at code point [12,460]
cannot be encoded as it is outside the permitted range of 0 to 255

解決方法

これは Java 固有の問題ではありません。HTTP 仕様では Header の値は ISO-8859-1 の文字しか受け付けないため、日本語・中国語・韓国語・キリル文字などの非 Latin-1 文字はそのまま渡せません。解決方法は、ファイル名を UTF-8 でパーセントエンコードし、filename*=utf-8'' パラメータで渡すことです。

つまり filename="レポート.txt"filename*=utf-8''%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88.txt に書き換えます(エンコード後の値はダブルクォートで囲まない点に注意)。変換用のヘルパーメソッドを用意します。

public static String filenameEncode(String name) {
    try {
        return java.net.URLEncoder.encode(name, "UTF-8").replace("+", "%20");
    } catch (java.io.UnsupportedEncodingException e) {
        e.printStackTrace();
        return name;
    }
}

そして GET メソッドを以下のように書き換えれば動きます。

@GetMapping
public ResponseEntity<Resource> download() {
    String disp = "attachment; filename*=utf-8''" + filenameEncode("レポート.txt");
    String mime = "text/plain";
    Resource res = new UrlResource(...);
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, disp)
        .header(HttpHeaders.CONTENT_TYPE, mime)
        .body(res);
}

上記の例は Java + Spring の組み合わせですが、他の言語やフレームワークでも URL エンコードを行えば同じパターンで動きます。注意点として、一部の URL Encoder(上記の java.net.URLEncoder を含む)は半角スペースを + に変換します。これはクエリ文字列としては有効ですが、Content-Disposition では動かないので、ヘルパー内で +%20 に置換しています。

参考リンク