it-swarm.com.de

Wie codiere ich den Dateinamen-Parameter des Content-Disposition-Headers in HTTP?

Webanwendungen, die das Herunterladen einer Ressource erzwingen möchten und nicht das direkte Rendern in einem Webbrowser einen Content-Disposition -Header in der HTTP-Antwort des Formulars ausgeben:

Content-Disposition: attachment; filename=FILENAME

Mit dem Parameter filename kann ein Name für die Datei vorgeschlagen werden, in die die Ressource vom Browser heruntergeladen wird. RFC 218 (Content-Disposition) besagt jedoch in Abschnitt 2. (The Filename Parameter), dass der Dateiname nur US-ASCII-Zeichen verwenden kann:

Die aktuelle [RFC 2045] Grammatik beschränkt die Parameterwerte (und damit die Dateinamen für die Inhaltsdisposition) auf US-ASCII. Wir sind uns bewusst, dass es wünschenswert ist, beliebige Zeichensätze in Dateinamen zuzulassen, es würde jedoch den Rahmen dieses Dokuments sprengen, die erforderlichen Mechanismen zu definieren.

Es gibt jedoch empirische Beweise dafür, dass die meisten gängigen Webbrowser heutzutage offenbar Nicht-US-ASCII-Zeichen zulassen, die jedoch (mangels eines Standards) nicht mit dem Kodierungsschema und der Zeichensatzspezifikation des Dateinamens übereinstimmen. Die Frage ist dann, welche verschiedenen Schemata und Codierungen werden von den gängigen Browsern verwendet, wenn der Dateiname "naivefile" (ohne Anführungszeichen und mit dem dritten Buchstaben U + 00EF) in den Content-Disposition-Header codiert werden muss.

Im Sinne dieser Frage sind beliebte Browser :

  • Feuerfuchs
  • Internet Explorer
  • Safari
  • Google Chrome
  • Opera
498
Atif Aziz

Dies wird im vorgeschlagenen RFC 5987 "Zeichensatz- und Sprachcodierung für HTTP-Header-Feldparameter (HTTP = Hypertext Transfer Protocol)" diskutiert, einschließlich Links zu Browsertests und zur Abwärtskompatibilität.

RFC 218 gibt an, dass solche Header gemäß RFC 2184 codiert werden sollten, was durch RFC 2231 überholt wurde und durch den obigen RFC-Entwurf abgedeckt wird.

91
Jim

Ich weiß, dass dies ein alter Beitrag ist, aber er ist immer noch sehr relevant. Ich habe herausgefunden, dass moderne Browser rfc5987 unterstützen, was utf-8-Codierung und prozentuale Codierung (url-codiert) ermöglicht. Dann wird Naive file.txt:

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

Safari (5) unterstützt dies nicht. Verwenden Sie stattdessen den Safari-Standard, indem Sie den Dateinamen direkt in Ihren mit utf-8 codierten Header schreiben:

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

IE8 und ältere Versionen unterstützen dies ebenfalls nicht und Sie müssen den IE-Standard der utf-8-Codierung verwenden, prozentual codiert:

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

In ASP.Net verwende ich den folgenden Code:

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);

Ich habe das Obige mit IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5 getestet.

Update November 2013:

Hier ist der Code, den ich derzeit verwende. Ich muss noch IE8 unterstützen, damit ich den ersten Teil nicht loswerden kann. Es hat sich herausgestellt, dass Browser auf Android den integrierten Android -Download-Manager verwenden und Dateinamen nicht auf die übliche Weise zuverlässig analysieren können.

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);

Das oben Gesagte wurde jetzt in IE7-11, Chrome 32, Opera 12, FF25, Safari 6 mit folgendem Dateinamen zum Herunterladen getestet: 你好 abcABCæøåÆØÅäöüïëêîâéíáóúýñ½§! # ¤% & () = `@ £ $ € {[]} + ´¨ ^ ~ '-_,;. Txt

In IE7 funktioniert es für einige Zeichen, aber nicht für alle. Aber wen interessiert heutzutage der IE7?

Dies ist die Funktion, mit der ich sichere Dateinamen für Android generiere. Beachten Sie, dass ich nicht weiß, welche Zeichen von Android unterstützt werden, aber ich habe getestet, dass diese auf jeden Fall funktionieren:

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: Ich habe in IE7 und IE8 getestet und es stellte sich heraus, dass ich Apostroph nicht entkommen musste ('). Haben Sie ein Beispiel, bei dem es fehlschlägt?

@ Dave Van den Eynde: Das Kombinieren der beiden Dateinamen in einer Zeile gemäß RFC6266 funktioniert mit Ausnahme von Android und IE7 + 8, und ich habe den Code entsprechend aktualisiert. Vielen Dank für den Vorschlag.

@Thilo: Keine Ahnung über GoodReader oder andere Nicht-Browser. Vielleicht haben Sie etwas Glück mit dem Android -Ansatz.

@ Alex Zhukovskiy: Ich weiß nicht warum, aber wie am Connect besprochen, scheint es nicht besonders gut zu funktionieren.

343

Es gibt eine einfache und sehr robuste Alternative: Verwenden Sie eine URL, die den gewünschten Dateinamen enthält .

Wenn der Name nach dem letzten Schrägstrich der gewünschte ist, benötigen Sie keine zusätzlichen Überschriften!

Dieser Trick funktioniert:

/real_script.php/fake_filename.doc

Wenn Ihr Server das Umschreiben von URLs unterstützt (z. B. mod_rewrite in Apache), können Sie den Skriptteil vollständig ausblenden.

Zeichen in URLs sollten byteweise in UTF-8 urlenkodiert sein:

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

RFC 6266 beschreibt die " Verwendung des Content-Disposition-Header-Feldes im Hypertext Transfer Protocol (HTTP) ". Aus dem zitieren:

6. Überlegungen zur Internationalisierung

Mit dem Parameter "filename*" ( Abschnitt 4. ) und der in [ RFC5987 ] definierten Codierung kann der Server Zeichen außerhalb von ISO-8859-1 übertragen Zeichensatz und optional zur Angabe der verwendeten Sprache.

Und in ihrem Beispiele Abschnitt :

Dieses Beispiel ist dasselbe wie das oben angegebene, jedoch wird der Parameter "filename" hinzugefügt, um die Kompatibilität mit Benutzeragenten zu gewährleisten, die RFC 5987 nicht implementieren:

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

Hinweis: Benutzerprogramme, die die Codierung RFC 5987 nicht unterstützen, ignorieren "filename*", wenn sie nach "filename" auftreten.

In Anhang D gibt es auch eine lange Liste von Vorschlägen zur Verbesserung der Interoperabilität. Es zeigt auch auf eine Site, die Implementierungen vergleicht . Aktuelle All-Pass-Tests, die für gängige Dateinamen geeignet sind, umfassen:

  • attwithisofnplain : Einfacher ISO-8859-1-Dateiname mit doppelten Anführungszeichen und ohne Codierung. Dies erfordert einen Dateinamen, der ausschließlich ISO-8859-1 entspricht und keine Prozentzeichen enthält, zumindest nicht vor hexadezimalen Ziffern.
  • attfnboth : zwei Parameter in der oben beschriebenen Reihenfolge. Funktioniert für die meisten Dateinamen in den meisten Browsern, obwohl IE8 den Parameter "filename" verwendet.

Das RFC 5987 verweist wiederum auf RFC 2231 , das das tatsächliche Format beschreibt. 2231 ist hauptsächlich für E-Mails bestimmt, und 5987 gibt an, welche Teile auch für HTTP-Header verwendet werden können. Verwechseln Sie dies nicht mit MIME-Headern, die in einem multipart/form-data HTTP body verwendet werden, der von RFC 2388 ( section 4.4 insbesondere) und der HTML 5-Entwurf .

65
MvG

Das folgende Dokument, das von dem Entwurf des RFC verlinkt ist und von Jim in seiner Antwort erwähnt wird, geht weiter auf die Frage ein und ist auf jeden Fall eine direkte Anmerkung wert:

Testfälle für HTTP Content-Disposition-Header und RFC 2231/2047-Codierung

16
Atif Aziz

in asp.net mvc2 benutze ich so etwas:

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

Ich denke, wenn Sie nicht MVC (2) verwenden, können Sie einfach den Dateinamen mit codieren

HttpUtility.UrlPathEncode(fileName)
11
Elmer

Setzen Sie den Dateinamen in doppelte Anführungszeichen. Löste das Problem für mich. So was:

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

Ich habe mehrere Optionen getestet. Browser unterstützen die Spezifikationen nicht und verhalten sich anders. Ich halte doppelte Anführungszeichen für die beste Option.

10

Ich verwende die folgenden Codeausschnitte zum Codieren (vorausgesetzt fileName enthält den Dateinamen und die Erweiterung der Datei, d. H .: 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
Vasilen Donchev

In der ASP.NET-Web-API codiere ich den Dateinamen per URL:

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

Wenn Sie ein nodejs-Backend verwenden, können Sie den folgenden Code verwenden, den ich gefunden habe hier

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

Ich habe den folgenden Code in allen gängigen Browsern getestet, einschließlich älterer Explorer (über den Kompatibilitätsmodus), und er funktioniert überall gut:

$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

Ich habe den folgenden Code in meinem "download.php" -Skript gefunden (basierend auf diesem Blogpost und diesen Testfällen ).

$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) ));

Dies verwendet die Standardmethode von filename = "...", solange nur iso-latin1- und "safe" -Zeichen verwendet werden. Andernfalls wird der Dateiname * = UTF-8 '' URL-codiert hinzugefügt. Entsprechend diesem speziellen Testfall sollte es ab MSIE9 und auf den neuesten Versionen von FF, Chrome, Safari funktionieren. In der niedrigeren MSIE-Version sollte der Dateiname die ISO8859-1-Version des Dateinamens enthalten und Unterstriche für Zeichen enthalten, die nicht in dieser Codierung enthalten sind.

Schlussbemerkung: die max. Größe für jedes Header-Feld ist 8190 Bytes auf Apache. UTF-8 kann bis zu vier Bytes pro Zeichen enthalten. nach rawurlencode sind es x3 = 12 bytes pro zeichen. Ziemlich ineffizient, aber es sollte theoretisch immer noch möglich sein, mehr als 600 "Lächeln"% F0% 9F% 98% 81 im Dateinamen zu haben.

4
renergy

In PHP hat dies für mich funktioniert (vorausgesetzt, der Dateiname ist UTF8-codiert):

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

Getestet gegen IE8-11, Firefox und Chrome.
Wenn der Browser Dateiname * = utf-8 interpretieren kann, wird die UTF8-Version des Dateinamens verwendet, andernfalls wird der dekodierte Dateiname verwendet. Wenn Ihr Dateiname Zeichen enthält, die in ISO-8859-1 nicht dargestellt werden können, sollten Sie stattdessen iconv verwenden.

3
Gustav

PHP Framework Symfony 4 hat $filenameFallback in HeaderUtils::makeDisposition. Sie können in diese Funktion nach Einzelheiten schauen - sie ähnelt den obigen Antworten.

Anwendungsbeispiel:

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

Nur ein Update, seit ich all diese Dinge heute als Antwort auf ein Kundenproblem ausprobiert habe

  • Mit Ausnahme von Safari, das für Japanisch konfiguriert wurde, funktionierten alle von unserem Kunden getesteten Browser am besten mit filename = text.pdf - wobei text ein von ASP.Net/IIS in utf-8 ohne URL-Codierung serialisierter Kundenwert ist. Aus irgendeinem Grund würde Safari, das für Englisch konfiguriert wurde, eine Datei mit dem japanischen Namen utf-8 akzeptieren und ordnungsgemäß speichern, aber der gleiche Browser, der für Japanisch konfiguriert wurde, würde die Datei mit den nicht interpretierten Zeichen utf-8 speichern. Alle anderen getesteten Browser schienen (unabhängig von der Sprachkonfiguration) am besten mit dem Dateinamen utf-8 zu funktionieren, der ohne URL-Codierung codiert wurde.
  • Ich konnte keinen einzigen Browser finden, der Rfc5987/8187 implementiert überhaupt. Ich habe mit den neuesten Chrome-, Firefox-Builds sowie IE 11 und Edge getestet. Ich habe versucht, den Header nur mit dem Dateinamen * = utf-8''texturlencoded.pdf zu setzen, und zwar mit beiden Dateinamen = text.pdf; Dateiname * = utf-8''texturlencoded.pdf. In keinem der oben genannten Fälle wurde eine Funktion von Rfc5987/8187 ordnungsgemäß verarbeitet.
1
user1664043

Klassische ASP Lösung

Die meisten modernen Browser unterstützen die Übergabe von Filename als UTF-8, aber wie es bei einer von mir verwendeten Datei-Upload-Lösung der Fall war, die auf FreeASPUpload.Net(site nicht mehr vorhanden, Link verweist auf archive.org ) es würde nicht funktionieren, da das Parsen der Binärdatei auf dem Lesen von Zeichenfolgen mit Einzelbyte-ASCII-Codierung beruhte, was gut funktionierte Wenn Sie UTF-8-codierte Daten übergeben haben, bis Sie zu den Zeichen ASCII gelangen, wird dies nicht unterstützt.

Ich konnte jedoch eine Lösung finden, um den Code zum Lesen und Parsen der Binärdatei als UTF-8 zu erhalten.

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

Gutschrift geht an Pure ASP File Upload durch Implementierung der BytesToString() -Funktion von include_aspuploader.asp in meinem eigenen Code konnte ich UTF-8 Dateinamen erhalten Arbeiten.


Nützliche Links

1
Lankymart