Come codificare il parametro filename dell’intestazione Content-Disposition in HTTP?

Le applicazioni Web che desiderano forzare il download di una risorsa anziché renderle direttamente in un browser Web emettono un’intestazione Content-Disposition nella risposta HTTP del modulo:

Content-Disposition: attachment; filename= FILENAME

Il parametro filename può essere utilizzato per suggerire un nome per il file in cui la risorsa viene scaricata dal browser. RFC 2183 (Content-Disposition), tuttavia, afferma nella sezione 2.3 (The Filename Parameter) che il nome del file può usare solo caratteri US-ASCII:

La grammatica corrente [RFC 2045] limita i valori dei parametri (e quindi i nomi dei file Content-Disposition) in US-ASCII. Riconosciamo la grande desiderabilità di consentire set di caratteri arbitrari nei nomi dei file, ma è oltre lo scopo di questo documento definire i meccanismi necessari.

Esistono prove empiriche, tuttavia, che i browser Web più diffusi oggi sembrano consentire i caratteri non-US-ASCII ancora (per la mancanza di uno standard) in disaccordo sullo schema di codifica e sulla specifica del set di caratteri del nome del file. La domanda è quindi, quali sono i vari schemi e codifiche utilizzate dai browser popolari se il nome del file “naïvefile” (senza virgolette e dove la terza lettera è U + 00EF) doveva essere codificato nell’intestazione Content-Disposition?

Ai fini di questa domanda, i browser più diffusi sono:

  • Firefox
  • Internet Explorer
  • Safari
  • Google Chrome
  • musica lirica

Si discute di questo, inclusi i collegamenti al test del browser e alla retrocompatibilità, nella proposta RFC 5987 , “Set di caratteri e codifica del linguaggio per i parametri del campo dell’intestazione HTTP (Hypertext Transfer Protocol)”.

La RFC 2183 indica che tali intestazioni dovrebbero essere codificate secondo la RFC 2184 , che era stata obsoleta dalla RFC 2231 , coperta dalla bozza RFC sopra riportata.

So che questo è un vecchio post ma è ancora molto pertinente. Ho trovato che i browser moderni supportano rfc5987, che consente la codifica utf-8, percentuale codificata (con codifica url). Quindi Naïve file.txt diventa:

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

Safari (5) non supporta questo. Invece dovresti usare lo standard Safari per scrivere il nome del file direttamente nell’intestazione codificata utf-8:

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

Anche IE8 e versioni precedenti non lo supportano ed è necessario utilizzare lo standard IE della codifica utf-8, percentuale codificata:

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

In ASP.Net utilizzo il seguente codice:

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

Ho provato quanto sopra usando IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.

Aggiornamento novembre 2013:

Ecco il codice che attualmente uso. Devo ancora supportare IE8, quindi non posso sbarazzarmi della prima parte. Si scopre che i browser su Android utilizzano il gestore di download Android integrato e non possono analizzare in modo affidabile i nomi dei file nel modo 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); 

Quanto sopra ora è stato testato in IE7-11, Chrome 32, Opera 12, FF25, Safari 6, utilizzando questo nome file per il download: 你好 abcABCæøåÆØÅäöüïëêîâéíáóúýñ½§! # ¤% & () = `@ £ $ € {[]} +’¨ ^ ~ ‘-_,;. txt

Su IE7 funziona per alcuni personaggi ma non per tutti. Ma a chi importa di IE7 al giorno d’oggi?

Questa è la funzione che utilizzo per generare nomi di file sicuri per Android. Nota che non so quali caratteri sono supportati su Android ma che ho provato che questi funzionano con certezza:

 private static readonly Dictionary 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); } 

@ Tom: Ho provato in IE7 e IE8 e ho scoperto che non avevo bisogno di uscire dall'apostrofo ('). Hai un esempio in cui fallisce?

@Dave Van den Eynde: Combinando i due nomi di file su una riga come in RFC6266 funziona tranne Android e IE7 + 8 e ho aggiornato il codice per riflettere questo. Grazie per il suggerimento.

@ Thilo: non ho idea di GoodReader o di qualsiasi altro non browser. Potresti avere un po 'di fortuna usando l'approccio Android.

@Alex Zhukovskiy: Non so perché ma come discusso su Connect non sembra funzionare molto bene.

  • Non esiste un modo interoperabile per codificare nomi non ASCII in Content-Disposition . La compatibilità del browser è un casino .

  • La syntax teoricamente corretta per l’uso di UTF-8 in Content-Disposition è molto strana: filename*=UTF-8''foo%c3%a4 (sì, questo è un asterisco, e nessuna virgola eccetto una virgoletta singola vuota nel mezzo)

  • Questa intestazione non è abbastanza standard (la specifica HTTP / 1.1 riconosce la sua esistenza , ma non richiede che i client la supportino).

C’è un’alternativa semplice e molto robusta: usa un URL che contiene il nome del file che vuoi .

Quando il nome dopo l’ultima barra è quello che vuoi, non hai bisogno di intestazioni extra!

Questo trucco funziona:

 /real_script.php/fake_filename.doc 

E se il tuo server supporta la riscrittura degli URL (ad esempio mod_rewrite in Apache), puoi hide completamente la parte dello script.

I caratteri negli URL devono essere in UTF-8, byte codificati in byte:

 /mot%C3%B6rhead # motörhead 

RFC 6266 descrive l’ uso del campo intestazione Content-Disposition nel protocollo HTTP (Hypertext Transfer Protocol) “. Citando da quello:

6. Considerazioni di internazionalizzazione

Il parametro ” filename* ” ( Sezione 4.3 ), che utilizza la codifica definita in [ RFC5987 ], consente al server di trasmettere caratteri al di fuori del set di caratteri ISO-8859-1 e anche di specificare facoltativamente la lingua in uso.

E nella loro sezione degli esempi :

Questo esempio è uguale a quello precedente, ma aggiungendo il parametro “filename” per la compatibilità con gli agenti utente che non implementano la RFC 5987 :

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

Nota: quei programmi utente che non supportano la codifica RFC 5987 ignorano ” filename* ” quando si verifica dopo ” filename “.

Nell’appendice D c’è anche una lunga lista di suggerimenti per aumentare l’interoperabilità. Indica inoltre un sito che mette a confronto le implementazioni . Gli attuali test “all-pass” adatti ai nomi di file comuni includono:

  • attwithisofnplain : semplice nome file ISO-8859-1 con virgolette doppie e senza codifica. Ciò richiede un nome di file che è tutto ISO-8859-1 e non contiene segni di percentuale, almeno non davanti alle cifre esadecimali.
  • attfnboth : due parametri nell’ordine sopra descritto. Dovrebbe funzionare per la maggior parte dei nomi di file sulla maggior parte dei browser, sebbene IE8 userà il parametro ” filename “.

Che RFC 5987 a sua volta fa riferimento a RFC 2231 , che descrive il formato effettivo. 2231 è principalmente per la posta e 5987 ci dice quali parti possono essere usate anche per le intestazioni HTTP. Non confondere questo con le intestazioni MIME utilizzate all’interno di un corpo HTTP multipart/form-data , che è governato da RFC 2388 ( sezione 4.4 in particolare) e dalla bozza HTML 5 .

Il seguente documento collegato alla bozza di RFC menzionata da Jim nella sua risposta affronta ulteriormente la domanda e vale sicuramente una nota diretta qui:

Casi di test per l’intestazione Content-Disposition HTTP e codifica RFC 2231/2047

in asp.net mvc2 io uso qualcosa come questo:

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

Immagino che se non usi mvc (2) potresti semplicemente codificare il nome del file usando

 HttpUtility.UrlPathEncode(fileName) 

Uso i seguenti frammenti di codice per la codifica (supponendo che fileName contenga il nome file e l’estensione del file, ovvero: 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 ) ); } 

Giava:

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

In API Web ASP.NET, url codifico il nome file:

 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 Non risolto
IE 9 corretto

Metti il ​​nome del tuo file tra virgolette. Risolto il problema per me. Come questo:

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

Ho testato il seguente codice in tutti i principali browser, compresi quelli più vecchi (tramite la modalità di compatibilità), e funziona bene ovunque:

 $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.'"'); 

Se stai utilizzando un backend nodejs puoi usare il seguente codice che ho trovato qui

 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). // ie, %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); } 

Ho finito con il seguente codice nel mio script “download.php” (basato su questo blogpost e su questi casi di 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) )); 

Questo usa il modo standard di filename = “…” finché ci sono solo caratteri iso-latin1 e “safe” usati; in caso contrario, aggiunge il nome file * = UTF-8 ” modo codificato in url. Secondo questo specifico caso di test , dovrebbe funzionare da MSIE9 in su, e su FF recenti, Chrome, Safari; nella versione MSIE inferiore, dovrebbe offrire un nome file contenente la versione ISO8859-1 del nome file, con caratteri di sottolineatura sui caratteri non presenti in questa codifica.

Nota finale: il max. la dimensione per ogni campo di intestazione è 8190 byte su apache. UTF-8 può avere fino a quattro byte per carattere; dopo rawurlencode, è x3 = 12 byte per un carattere. Abbastanza inefficiente, ma dovrebbe essere teoricamente ansible avere più di 600 “sorrisi”% F0% 9F% 98% 81 nel nome file.

In PHP questo lo ha fatto per me (assumendo che il nome del file sia codificato UTF8):

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

Testato contro IE8-11, Firefox e Chrome.
Se il browser può interpretare filename * = utf-8 userà la versione UTF8 del nome file, altrimenti userà il nome file decodificato. Se il tuo nome file contiene caratteri che non possono essere rappresentati in ISO-8859-1, ti consigliamo di utilizzare invece iconv .

Soluzione ASP classica

La maggior parte dei browser moderni supporta il passaggio del nome Filename come UTF-8 ora, ma come nel caso di una soluzione di caricamento file che utilizzo basata su FreeASPUpload.Net (il sito non esiste più, i link point per archive.org ) non funzionerebbe come l’analisi del binario si basava sulla lettura di stringhe codificate ASCII a singolo byte, che funzionava bene quando si passavano dati codificati in UTF-8 fino a quando non si arrivava ai caratteri che ASCII non supportava.

Tuttavia sono stato in grado di trovare una soluzione per ottenere il codice da leggere e analizzare il file binario come 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 

Il merito va al caricamento di file ASP puri implementando la funzione BytesToString() da include_aspuploader.asp nel mio codice personale, riuscendo a far funzionare i nomi dei file UTF-8 .


link utili

  • Multipart / form-data e UTF-8 in un’applicazione ASP Classic

  • Differenze di formato Unicode, UTF, ASCII, ANSI

Abbiamo avuto un problema simile in un’applicazione web e abbiamo finito con la lettura del nome file da HTML , e l’impostazione nella forma con codifica url in un nuovo HTML . Ovviamente abbiamo dovuto rimuovere il percorso come “C: \ fakepath \” che viene restituito da alcuni browser.

Naturalmente questo non risponde direttamente alla domanda dei PO, ma potrebbe essere una soluzione per gli altri.

Io normalmente codifico URL (con% xx) i nomi dei file, e sembra funzionare in tutti i browser. Potresti voler fare alcuni test comunque.

Ho trovato una soluzione, che funziona per tutti i miei browser (cioè tutti i browser che ho installato – IE8, FF16, Opera 12, Chrome 22).

La mia soluzione è descritta in altri thread: Servlet Java scarica i caratteri speciali del nome file

La mia soluzione si basa sul fatto, come i browser cercano di leggere il valore dal parametro filename . Se non è specificato alcun set di caratteri nel parametro filename (ad esempio filename*=utf-8''test.xml ) i browser si aspettano che il valore sia codificato nella codifica nativa del browser.

Browser diversi prevedono una codifica nativa diversa. Di solito la codifica nativa del browser è utf-8 (FireFox, Opera, Chrome). Ma la codifica nativa di IE è Win-1250. (Non so nulla di altri browser.)

Quindi, se mettiamo il valore nel parametro filename , che è codificato da utf-8 / win-1250 secondo il browser dell’utente, dovrebbe funzionare. Almeno, funziona per me.

In breve, se abbiamo un file chiamato omáčka.xml ,
per FireFox, Opera e Chrome ho risposto a questa intestazione (codificata in utf-8):

 Content-Disposition: attachment; filename="omáčka.xml" 

e per IE I rispondo a questa intestazione (codificata in win-1250):

 Content-Disposition: attachment; filename="omáèka.jpg" 

Esempio di Java è nel mio post che è menzionato sopra.