web-dev-qa-db-fra.com

Comment encoder le paramètre filename de l'en-tête Content-Disposition dans HTTP?

Les applications Web qui veulent forcer une ressource à être téléchargée plutôt que directement dans un navigateur Web, émettez un en-tête Content-Disposition dans la réponse HTTP du formulaire:

Content-Disposition: attachment; filename=FILENAME

Le paramètre filename peut être utilisé pour suggérer un nom au fichier dans lequel la ressource est téléchargée par le navigateur. RFC 218 (Content-Disposition) indique toutefois dans section 2. (paramètre de nom de fichier) que le nom de fichier ne peut utiliser que des caractères US-ASCII:

La grammaire actuelle [RFC 2045] limite les valeurs de paramètre (et donc les noms de fichier Content-Disposition) à US-ASCII. Nous reconnaissons qu'il est hautement souhaitable de permettre des jeux de caractères arbitraires dans les noms de fichiers, mais il est au-delà de la portée de ce document de définir les mécanismes nécessaires.

Il existe néanmoins des preuves empiriques que la plupart des navigateurs Web actuels semblent autoriser les caractères non-US-ASCII, mais (en l'absence de norme) en désaccord sur le schéma de codage et la spécification du jeu de caractères du nom de fichier. La question est alors de savoir quels sont les différents schémas et codages utilisés par les navigateurs populaires si le nom de fichier "naïvefile" (sans guillemets et où la troisième lettre est U + 00EF) devait être encodé dans l'en-tête Content-Disposition.

Pour les besoins de cette question, les navigateurs populaires étant:

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

Il existe une discussion à ce sujet, y compris des liens vers les tests de navigateur et la compatibilité ascendante, dans la proposition RFC 5987 , "Codage du jeu de caractères et du langage pour les paramètres de champ d'en-tête HTTP (Hypertext Transfer Protocol)". "

RFC 218 indique que ces en-têtes doivent être codés conformément à RFC 2184 , qui est obsolète par RFC 2231 , couvert par le projet de RFC ci-dessus.

91
Jim

Je sais que ceci est un ancien post, mais il est toujours très pertinent. J'ai constaté que les navigateurs modernes prennent en charge la norme rfc5987, qui permet le codage utf-8, codé en pourcentage (codé en url). Naïve file.txt devient alors:

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

Safari (5) ne le supporte pas. Au lieu de cela, vous devez utiliser la norme Safari d’écriture du nom de fichier directement dans votre en-tête encodé utf-8:

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

IE8 et les versions antérieures ne le prennent pas en charge non plus et vous devez utiliser le standard IE de codage utf-8, codé en pourcentage:

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

Dans ASP.Net, j'utilise le code suivant:

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

J'ai testé ce qui précède avec IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.

Mise à jour Novembre 2013:

Voici le code que j'utilise actuellement. Je dois toujours supporter IE8, donc je ne peux pas me débarrasser de la première partie. Il s'avère que les navigateurs sur Android utilisent le gestionnaire de téléchargement intégré Android et ne peuvent pas analyser de manière fiable les noms de fichiers de manière standard.

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

Les éléments ci-dessus sont maintenant testés dans IE7-11, Chrome 32, Opera 12, FF25, Safari 6, en utilisant ce nom de fichier pour le téléchargement: `@ £ $ € {[]} + ´¨ ^ ~ '-_,;. Txt

Sur IE7, cela fonctionne pour certains personnages mais pas pour tous. Mais qui se soucie de IE7 de nos jours?

C’est la fonction que j’utilise pour générer des noms de fichiers sûrs pour Android. Notez que je ne sais pas quels caractères sont pris en charge sur Android, mais que j’ai vérifié que ceux-ci fonctionnent à coup sûr:

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: J'ai testé dans IE7 et IE8 et il s'est avéré que je n'avais pas besoin d'échapper à l'apostrophe ('). Avez-vous un exemple où cela échoue?

@ Dave Van den Eynde: La combinaison des deux noms de fichier sur une ligne, conformément au RFC6266, fonctionne à l'exception de Android et de IE7 + 8, et j'ai mis à jour le code en conséquence. Merci pour la suggestion.

@Thilo: Aucune idée de GoodReader ni d'aucun autre navigateur. Vous pourriez avoir un peu de chance en utilisant l'approche Android.

@ Alex Zhukovskiy: Je ne sais pas pourquoi mais, comme indiqué dans le chapitre Connexion , cela ne semble pas très bien fonctionner.

343

Il existe une alternative simple et très robuste: utilisez une URL contenant le nom du fichier souhaité .

Lorsque le nom après la dernière barre oblique est celui que vous souhaitez, vous n'avez pas besoin d'en-têtes supplémentaires!

Cette astuce fonctionne:

/real_script.php/fake_filename.doc

Et si votre serveur prend en charge la réécriture d'URL (par exemple, mod_rewrite dans Apache), vous pouvez masquer entièrement la partie script.

Les caractères dans les URL doivent être en UTF-8, codés url par octet:

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

RFC 6266 décrit le “ utilisation du champ d'en-tête Content-Disposition dans le protocole de transfert d'hypertexte (HTTP) ”. Citant cela:

6. Considérations d'internationalisation

Le paramètre "filename*" ( chapitre 4. ), utilisant le codage défini dans [ RFC5987 ], permet au serveur de transmettre des caractères en dehors de la norme ISO-8859-1. jeu de caractères, ainsi que de spécifier éventuellement la langue utilisée.

Et dans leur section exemples :

Cet exemple est identique à l'exemple ci-dessus, mais ajoute le paramètre "nom de fichier" pour assurer la compatibilité avec les agents d'utilisateur ne mettant pas en œuvre RFC 5987 :

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

Remarque: les agents utilisateurs qui ne prennent pas en charge le codage RFC 5987 ignorent "filename*" lorsqu'il survient après "filename".

Dans Annexe D , une longue liste de suggestions visant à accroître l’interopérabilité est également proposée. Il pointe également sur n site qui compare les implémentations . Les tests actuels "passe-tout" adaptés aux noms de fichiers courants incluent:

  • attwithisofnplain : nom de fichier simple ISO-8859-1 avec guillemets et sans codage. Cela nécessite un nom de fichier qui est entièrement ISO-8859-1 et qui ne contient pas de signe de pourcentage, du moins pas devant les chiffres hexadécimaux.
  • attfnboth : deux paramètres dans l'ordre décrit ci-dessus. Cela devrait fonctionner pour la plupart des noms de fichiers sur la plupart des navigateurs, bien que IE8 utilise le paramètre “filename”.

Ce RFC 5987 référence à son tour RFC 2231 , qui décrit le format réel. 2231 est principalement destiné au courrier, et 5987 nous indique également quelles parties peuvent être utilisées pour les en-têtes HTTP. Ne confondez pas ceci avec les en-têtes MIME utilisés dans un corps multipart/form-data HTTP , régi par RFC 2388 ( section 4.4 en particulier) et le HTML 5 draft .

65
MvG

Le document suivant lié à le projet de RFC mentionné par Jim dans sa réponse répond plus en détail à la question et mérite certainement une note directe ici:

Cas de test pour l'en-tête HTTP Content-Disposition et le codage RFC 2231/2047

16
Atif Aziz

dans asp.net mvc2 j'utilise quelque chose comme ceci:

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

Je suppose que si vous n’utilisez pas mvc (2), vous pouvez simplement encoder le nom du fichier en utilisant

HttpUtility.UrlPathEncode(fileName)
11
Elmer

Mettez le nom du fichier entre guillemets. Résolu le problème pour moi. Comme ça:

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

J'ai testé plusieurs options. Les navigateurs ne supportent pas les spécifications et agissent différemment, je crois que les guillemets sont la meilleure option.

10
Dmitry Kaigorodov

J'utilise les extraits de code suivants pour l'encodage (en supposant que NomFichier contient le nom du fichier et l'extension du fichier, c'est-à-dire: 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

Dans l’API Web ASP.NET, j’URL code le nom du fichier:

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

Si vous utilisez un backend nodejs, vous pouvez utiliser le code suivant que j'ai trouvé ici

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
Emanuele Spatola

J'ai testé le code suivant dans tous les principaux navigateurs, y compris les anciens Explorateurs (via le mode de compatibilité), et cela fonctionne bien partout:

$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

Je me suis retrouvé avec le code suivant dans mon script "download.php" (basé sur this blogpost et ces cas de test ).

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

Ceci utilise la méthode standard filename = "..." tant qu'il n'y a que des caractères iso-latin1 et "safe" utilisés; sinon, il ajoute le nom de fichier * = UTF-8 '' de manière encodée dans l'URL. Selon ce cas de test spécifique , il devrait fonctionner à partir de MSIE9, ainsi que sur les versions récentes de FF, Chrome, Safari; sur la version inférieure de MSIE, il devrait offrir un nom de fichier contenant la version ISO8859-1 du nom de fichier, avec des traits de soulignement sur les caractères ne figurant pas dans cet encodage.

Note finale: le max. La taille de chaque champ d'en-tête est de 8190 octets sur Apache. UTF-8 peut contenir jusqu'à quatre octets par caractère. après rawurlencode, c'est x3 = 12 octets par un caractère. Assez inefficace, mais il devrait encore être théoriquement possible d'avoir plus de 600 "sourires"% F0% 9F% 98% 81 dans le nom du fichier.

4
renergy

Dans PHP, cela s’est fait pour moi (en supposant que le nom du fichier est codé en UTF8):

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

Testé contre IE8-11, Firefox et Chrome.
Si le navigateur peut interpréter nom du fichier * = utf-8, il utilisera la version UTF8 du nom du fichier, sinon il utilisera le nom du fichier décodé. Si votre nom de fichier contient des caractères qui ne peuvent pas être représentés dans ISO-8859-1, vous voudrez peut-être envisager d'utiliser iconv à la place.

3
Gustav

Le framework PHP Symfony 4 a $filenameFallback dans HeaderUtils::makeDisposition. Vous pouvez regarder dans cette fonction pour plus de détails - c'est semblable aux réponses ci-dessus.

Exemple d'utilisation:

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

Juste une mise à jour puisque j'essayais tout ça aujourd'hui en réponse à un problème client

  • À l'exception de Safari configuré pour le japonais, tous les navigateurs testés par nos clients fonctionnent mieux avec filename = text.pdf - où text est une valeur client sérialisée par ASP.Net/IIS dans utf-8 sans codage d'URL. Pour une raison quelconque, Safari configuré pour l'anglais accepterait et enregistrerait correctement un fichier avec le nom japonais utf-8, mais ce même navigateur configuré pour le japonais enregistrerait le fichier avec les caractères utf-8 non interprétés. Tous les autres navigateurs testés semblaient fonctionner mieux/bien (quelle que soit la configuration de la langue) avec le nom de fichier utf-8 codé sans codage d’URL.
  • Je ne pouvais pas trouver un seul navigateur implémentant Rfc5987/8187 du tout. J'ai testé avec la dernière version de Chrome, les versions de Firefox plus IE 11 et Edge. J'ai essayé de définir l'en-tête avec juste le nom de fichier * = utf-8''texturlencoded.pdf, en le configurant avec les deux noms de fichier = text.pdf; filename * = utf-8''texturlencoded.pdf. Aucune caractéristique de Rfc5987/8187 ne semble avoir été traitée correctement dans aucune des solutions ci-dessus.
1
user1664043

Classic ASP Solution

La plupart des navigateurs modernes prennent en charge le passage de la Filename comme UTF-8 maintenant, mais comme ce fut le cas avec une solution de téléchargement de fichier que j'utilise, qui était basée sur FreeASPUpload.Net (le site n'existe plus, le lien pointe vers archive.org ) il ne fonctionnerait pas car l'analyse du binaire reposant sur la lecture d'un octet unique ASCII chaînes codées, qui fonctionnaient bien lorsque vous transmettiez des données codées UTF-8 jusqu'à ce que vous obteniez les caractères ASCII ne sont pas pris en charge.

Cependant, j'ai pu trouver une solution pour obtenir le code à lire et analyser le binaire en 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

Le crédit va à Pure ASP Upload de fichier en implémentant la fonction BytesToString() de include_aspuploader.asp dans mon propre code, j'ai pu obtenir UTF-8 noms de fichiers. travail.


Liens utiles

1
Lankymart