web-dev-qa-db-ja.com

HTTPでContent-Dispositionヘッダのfilenameパラメータをエンコードするにはどうすればいいですか?

Webブラウザで直接レンダリングするのではなく、リソースを強制的にダウンロードする(---)Webアプリケーションは、HTTPでContent-Dispositionヘッダーを発行しますフォームの応答:

Content-Disposition: attachment; filename=FILENAME

filenameパラメータは、リソースがブラウザによってダウンロードされるファイルの名前を示唆するために使用することができます。 RFC 2183 (Content-Disposition)、ただし、 セクション2.3 に記載(ファイル名パラメータ)ファイル名はUS-ASCII文字のみを使用できます。

現在の[RFC 2045]の文法は、パラメータ値(したがってContent-Dispositionのファイル名)をUS-ASCIIに制限しています。ファイル名に任意の文字セットを許可することが非常に望ましいことを認識していますが、必要なメカニズムを定義することはこのドキュメントの範囲を超えています。

それにもかかわらず、今日で最も人気のあるWebブラウザは、ファイル名の符号化方式と文字セットの指定に関して、US-ASCII以外の文字をまだ許可していないように見えるという経験的証拠があります。それでは、ファイル名「naïvefile」(引用符なし、3文字目がU + 00EF)をContent-Dispositionヘッダーにエンコードする必要がある場合、一般的なブラウザで採用されているさまざまな方式とエンコードは何ですか?

この質問の目的のために、人気のあるブラウザは、

  • Firefox
  • インターネットエクスプローラ
  • サファリ
  • グーグルクローム
  • オペラ
490
Atif Aziz

提案されている RFC 5987 の「HTTP(Hypertext Transfer Protocol)ヘッダーフィールドパラメータの文字セットと言語エンコーディング」に、ブラウザテストへのリンクや後方互換性など、これに関する議論があります。

RFC 2183 は、 RFC 2231 で廃止された RFC 2184 に従って、そのようなヘッダーをエンコードすることを示しています。上記のドラフトRFCでカバーされています。

89
Jim

私はこれが古い投稿であることを知っていますが、それはまだ非常に関連性があります。私は最近のブラウザがUTF-8エンコーディング、パーセンテージエンコード(URLエンコード)を可能にするrfc5987をサポートすることを発見しました。そしてナイーブなfile.txtは次のようになります。

Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt

Safari(5)はこれをサポートしていません。代わりに、あなたのutf-8でエンコードされたヘッダに直接ファイル名を書くというSafari標準を使うべきです:

Content-Disposition: attachment; filename=Naïve file.txt

IE8以前でもそれをサポートしていないので、IE標準のutf-8エンコーディング、パーセンテージエンコードを使用する必要があります。

Content-Disposition: attachment; filename=Na%C3%AFve%20file.txt

ASP.Netでは、私は次のコードを使用します。

string contentDisposition;
if (Request.Browser.Browser == "IE" && (Request.Browser.Version == "7.0" || Request.Browser.Version == "8.0"))
    contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName);
else if (Request.Browser.Browser == "Safari")
    contentDisposition = "attachment; filename=" + fileName;
else
    contentDisposition = "attachment; filename*=UTF-8''" + Uri.EscapeDataString(fileName);
Response.AddHeader("Content-Disposition", contentDisposition);

IE7、IE8、IE9、Chrome 13、Opera 11、FF5、Safari 5を使用して上記をテストしました。

更新2013年11月:

これが私が現在使っているコードです。私はまだIE8をサポートする必要があるので、最初の部分を取り除くことはできません。 Androidのブラウザは組み込みのAndroidダウンロードマネージャを使用しているため、ファイル名を標準的な方法で確実に解析することはできません。

string contentDisposition;
if (Request.Browser.Browser == "IE" && (Request.Browser.Version == "7.0" || Request.Browser.Version == "8.0"))
    contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName);
else if (Request.UserAgent != null && Request.UserAgent.ToLowerInvariant().Contains("Android")) // Android built-in download manager (all browsers on Android)
    contentDisposition = "attachment; filename=\"" + MakeAndroidSafeFileName(fileName) + "\"";
else
    contentDisposition = "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + Uri.EscapeDataString(fileName);
Response.AddHeader("Content-Disposition", contentDisposition);

上記は現在IE7-11、Chrome 32、Opera 12、FF25、Safari 6でテストされています。ダウンロードにはこのファイル名を使用しています。 ^〜 '-_、;。txt

IE7では、一部の文字では機能しますが、すべてではありません。しかし、今日誰がIE7を気にかけているのですか?

これは私がAndroid用の安全なファイル名を生成するために使用する関数です。どの文字がAndroidでサポートされているのかはわかりませんが、これらが確実に機能することをテストしました。

private static readonly Dictionary<char, char> AndroidAllowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-+,@£$€!½§~'=()[]{}0123456789".ToDictionary(c => c);
private string MakeAndroidSafeFileName(string fileName)
{
    char[] newFileName = fileName.ToCharArray();
    for (int i = 0; i < newFileName.Length; i++)
    {
        if (!AndroidAllowedChars.ContainsKey(newFileName[i]))
            newFileName[i] = '_';
    }
    return new string(newFileName);
}

@TomZ:私はIE7とIE8でテストし、アポストロフィ( ')をエスケープする必要はないことがわかりました。失敗する例がありますか。

@Dave Van den Eynde:AndroidおよびIE7 + 8を除き、RFC 6266に準拠して2つのファイル名を1行に組み合わせることで、これを反映するようにコードを更新しました。ご提案ありがとうございます。

@Thilo:GoodReaderや他のブラウザ以外のものについては全く考えていません。あなたはAndroidのアプローチを使って運があるかもしれません。

@Alex Zhukovskiy:理由はわかりませんが、 Connect で説明したように、うまく動作しないようです。

340

単純で非常に堅牢な方法があります。必要なファイル名を含むURLを使用します

最後のスラッシュの後の名前が欲しいものであれば、余分なヘッダは必要ありません。

このトリックはうまくいきます:

/real_script.php/fake_filename.doc

そしてあなたのサーバーがURL書き換えをサポートしているなら(例えば、Apacheのmod_rewrite)、あなたはスクリプト部分を完全に隠すことができます。

URLの文字はUTF-8で、バイトごとにURLエンコードされている必要があります。

/mot%C3%B6rhead   # motörhead
161
Kornel

RFC 6266 ハイパーテキスト転送プロトコル(HTTP)でのContent-Dispositionヘッダーフィールドの使用 」について説明しています。それから引用する:

6.国際化に関する考察

[ RFC5987 ]で定義されているエンコーディングを使用する“ filename*”パラメータ( セクション4. )を使用すると、サーバーはISO-8859-1文字セット以外の文字を送信したり、オプションで言語を指定できます使用中で。

そして彼らの 例のセクション

この例は上の例と同じですが、 RFC 5987 を実装していないユーザエージェントとの互換性のために "filename"パラメータを追加します。

Content-Disposition: attachment;
                     filename="EURO rates";
                     filename*=utf-8''%e2%82%ac%20rates

注: RFC 5987 エンコーディングをサポートしていないユーザーエージェントは、 "filename"の後にある場合は "filename*"を無視します。

付録D 相互運用性を高めるための提案の長いリストもあります。それはまた指摘します 実装を比較するサイト 。一般的なファイル名に適した現在の全パステストには、次のものがあります。

  • attwithisofnplain :二重引用符でエンコードされていないプレーンのISO-8859-1ファイル名。これには、すべてISO-8859-1で、パーセント記号を含まない、少なくとも16進数の前にないファイル名が必要です。
  • attfnboth :上記の順序で2つのパラメータ。 IE8は“ filename”パラメータを使用しますが、ほとんどのブラウザでほとんどのファイル名で機能するはずです。

それ RFC 5987 次に参照する RFC 2231 、これは実際のフォーマットを記述しています。 2231は主にメール用で、5987はどの部分がHTTPヘッダーにも使用できるかを教えてくれます。これをmultipart/form-data HTTP body の中で使われているMIMEヘッダと混同しないでください。 RFC 2388セクション4.4HTML 5ドラフト

64
MvG

Jim が彼の答えで述べた ドラフトRFC からリンクされた次の文書は、さらに質問に対処しており、ここに直接メモする価値があることは間違いありません。

HTTP Content-DispositionヘッダおよびRFC 2231/2047エンコーディング のテストケース

16
Atif Aziz

asp.netのmvc2で私はこのようなものを使う:

return File(
    tempFile
    , "application/octet-stream"
    , HttpUtility.UrlPathEncode(fileName)
    );

Mvc(2)を使用していないのであれば、単にファイル名を使用してエンコードできます。

HttpUtility.UrlPathEncode(fileName)
11
Elmer

ファイル名を二重引用符で囲みます。私のために問題を解決しました。このような:

Content-Disposition: attachment; filename="My Report.doc"

http://kb.mozillazine.org/ファイル名_with_spaces_are_truncated_upon_download

私は複数のオプションをテストしました。ブラウザは仕様をサポートしていないため、動作が異なります。二重引用符が最善の選択肢であると考えています。

10

エンコードには次のコードスニペットを使用します(fileNameにファイルのファイル名と拡張子、つまりtest.txtが含まれているとします)。


PHP:

if ( strpos ( $_SERVER [ 'HTTP_USER_AGENT' ], "MSIE" ) > 0 )
{
     header ( 'Content-Disposition: attachment; filename="' . rawurlencode ( $fileName ) . '"' );
}
else
{
     header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode ( $fileName ) );
}

Java:

fileName = request.getHeader ( "user-agent" ).contains ( "MSIE" ) ? URLEncoder.encode ( fileName, "utf-8") : MimeUtility.encodeWord ( fileName );
response.setHeader ( "Content-disposition", "attachment; filename=\"" + fileName + "\"");
9

ASP.NET Web APIでは、ファイル名をエンコードします。

public static class HttpRequestMessageExtensions
{
    public static HttpResponseMessage CreateFileResponse(this HttpRequestMessage request, byte[] data, string filename, string mediaType)
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
        var stream = new MemoryStream(data);
        stream.Position = 0;

        response.Content = new StreamContent(stream);

        response.Content.Headers.ContentType = 
            new MediaTypeHeaderValue(mediaType);

        // URL-Encode filename
        // Fixes behavior in IE, that filenames with non US-ASCII characters
        // stay correct (not "_utf-8_.......=_=").
        var encodedFilename = HttpUtility.UrlEncode(filename, Encoding.UTF8);

        response.Content.Headers.ContentDisposition =
            new ContentDispositionHeaderValue("attachment") { FileName = encodedFilename };
        return response;
    }
}

IE 9 Not fixed
IE 9 Fixed

8
martinoss

もしnodejsバックエンドを使っているのなら、私が見つけた次のコードを使うことができます ここで

var fileName = 'my file(2).txt';
var header = "Content-Disposition: attachment; filename*=UTF-8''" 
             + encodeRFC5987ValueChars(fileName);

function encodeRFC5987ValueChars (str) {
    return encodeURIComponent(str).
        // Note that although RFC3986 reserves "!", RFC5987 does not,
        // so we do not need to escape it
        replace(/['()]/g, escape). // i.e., %27 %28 %29
        replace(/\*/g, '%2A').
            // The following are not required for percent-encoding per RFC5987, 
            // so we can allow for a little better readability over the wire: |`^
            replace(/%(?:7C|60|5E)/g, unescape);
}
5

私は、古いエクスプローラを含むすべての主要ブラウザで(互換モードを介して)次のコードをテストしましたが、どこでも問題なく動作します。

$filename = $_GET['file']; //this string from $_GET is already decoded
if (strstr($_SERVER['HTTP_USER_AGENT'],"MSIE"))
  $filename = rawurlencode($filename);
header('Content-Disposition: attachment; filename="'.$filename.'"');
5
Stano

私は "download.php"スクリプトに次のコードを書いてしまいました( このブログポストこれらのテストケースに基づいています )。

$il1_filename = utf8_decode($filename);
$to_underscore = "\"\\#*;:|<>/?";
$safe_filename = strtr($il1_filename, $to_underscore, str_repeat("_", strlen($to_underscore)));

header("Content-Disposition: attachment; filename=\"$safe_filename\""
.( $safe_filename === $filename ? "" : "; filename*=UTF-8''".rawurlencode($filename) ));

これは、iso-latin1と "safe"の文字しか使用されていない限り、標準のfilename = "..."の方法を使用します。そうでなければ、ファイル名* = UTF-8 ''をURLエンコードした方法を追加します。 この特定のテストケース によれば、それはMSIE9から、そして最近のFF、Chrome、Safariで動作するはずです。それより前のMSIEバージョンでは、ISO8859-1バージョンのファイル名を含むfilenameを提供し、このエンコーディングにない文字にはアンダースコアを付けます。

最後の注意:最大。各ヘッダーフィールドのサイズは、Apacheでは8190バイトです。 UTF-8は1文字あたり最大4バイトです。 rawurlencodeの後は、1文字あたりx 3 = 12バイトです。かなり非効率的ですが、それでもファイル名に600以上の "微笑み"%F0%9F%98%81を含めることは理論的に可能です。

4
renergy

PHPでこれは私のためにそれをしました(ファイル名がUTF8でエンコードされていると仮定して):

header('Content-Disposition: attachment;'
    . 'filename="' . addslashes(utf8_decode($filename)) . '";'
    . 'filename*=utf-8\'\'' . rawurlencode($filename));

IE8-11、FirefoxおよびChromeに対してテスト済み。
ブラウザがfilename * = utf-8と解釈できる場合は、UTF8バージョンのファイル名が使用され、それ以外の場合はデコードされたファイル名が使用されます。ファイル名。ファイル名にISO-8859-1で表現できない文字が含まれている場合は、代わりにiconvを使用することを検討してください。

3
Gustav

PHPフレームワークSymfony 4には、$filenameFallbackHeaderUtils::makeDispositionがあります。詳細については、この関数を調べることができます-上記の答えに似ています。

使用例:

$filenameFallback = preg_replace('#^.*\.#', md5($filename) . '.', $filename);
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename, $filenameFallback);
$response->headers->set('Content-Disposition', $disposition);
1
luchaninov

私が顧客の問題に対応して私が今日これらすべてのことを試みていたのでちょうど更新

  • 日本語用に設定されたSafariを除いて、私達の顧客がテストしたすべてのブラウザはfilename = text.pdfでうまくいった - ここでtextはURLエンコードなしでutf-8でASP.Net/IISによってシリアライズされた顧客値です。何らかの理由で、英語用に設定されたSafariは、utf-8日本語名でファイルを受け入れて正しく保存しますが、日本語用に設定された同じブラウザは、utf-8文字が未解釈で保存されます。テストされた他のすべてのブラウザは、URLエンコードなしでエンコードされたファイル名utf-8で(言語設定に関係なく)最高/正常に動作するようでした。
  • Rfc5987/8187を実装しているブラウザがまったく見つかりませんでした。私は最新のChromeでテストし、Firefoxはプラス[IE 11とEdgeをビルドします。私はちょうどfilename * = utf-8 '' texturlencoded.pdfでヘッダーを設定しようとしました、両方filename = text.pdfでそれを設定しました。 filename * = utf-8 'のtexturlencoded.pdf。 Rfc5987/8187の1つの機能が上記のいずれでも正しく処理されているようには見えませんでした。
1
user1664043

クラシックASPソリューション

最近のほとんどのブラウザはFilenameUTF-8として渡すことをサポートしていますが、私が使用しているファイルアップロードソリューションの場合と同様に FreeASPUpload.Net(このサイトはもう存在しません。 archive.org )へのリンクは、シングルバイトの読み取りに依存したバイナリの解析としては機能しませんASCIIエンコードされた文字列。UTF-8でエンコードされたデータを文字に到達するまで渡すとうまく機能しましたASCIIはサポートしていません。

しかし、私はコードを読み取ってバイナリをUTF-8として解析するための解決策を見つけることができました。

Public Function BytesToString(bytes)    'UTF-8..
  Dim bslen
  Dim i, k , N 
  Dim b , count 
  Dim str

  bslen = LenB(bytes)
  str=""

  i = 0
  Do While i < bslen
    b = AscB(MidB(bytes,i+1,1))

    If (b And &HFC) = &HFC Then
      count = 6
      N = b And &H1
    ElseIf (b And &HF8) = &HF8 Then
      count = 5
      N = b And &H3
    ElseIf (b And &HF0) = &HF0 Then
      count = 4
      N = b And &H7
    ElseIf (b And &HE0) = &HE0 Then
      count = 3
      N = b And &HF
    ElseIf (b And &HC0) = &HC0 Then
      count = 2
      N = b And &H1F
    Else
      count = 1
      str = str & Chr(b)
    End If

    If i + count - 1 > bslen Then
      str = str&"?"
      Exit Do
    End If

    If count>1 then
      For k = 1 To count - 1
        b = AscB(MidB(bytes,i+k+1,1))
        N = N * &H40 + (b And &H3F)
      Next
      str = str & ChrW(N)
    End If
    i = i + count
  Loop

  BytesToString = str
End Function

クレジットは 純粋なASPファイルアップロード に自分のコードでinclude_aspuploader.aspからBytesToString()関数を実装することでUTF-8ファイル名を機能させることができました。


便利なリンク

1
Lankymart