Come codificare il parametro del nome file dell'intestazione Content-Disposition in HTTP?
-
01-07-2019 - |
Domanda
Applicazioni Web che vogliono forzare l'esistenza di una risorsa scaricato piuttosto che direttamente reso in un problema del browser Web a Content-Disposition
intestazione nella risposta HTTP del modulo:
Content-Disposition: attachment; filename=NOME DEL FILE
IL filename
Il parametro può essere utilizzato per suggerire un nome per il file in cui la risorsa viene scaricata dal browser. RFC2183 (Contenuto-Disposizione), tuttavia, precisa in sezione 2.3 (Il parametro del nome file) che il nome del file può utilizzare solo caratteri US-ASCII:
L'attuale [RFC 2045] la grammatica limita i valori dei parametri (e quindi i nomi di file di disposizione del contenuto) a US-ASCII.Riconosciamo la grande desiderabilità di consentire set di caratteri arbitrari nei file di file, ma è oltre lo scopo di questo documento definire i meccanismi necessari.
Esistono prove empiriche, tuttavia, che i browser Web più popolari oggi sembrano consentire caratteri non-US-ASCII ma (per la mancanza di uno standard) non sono d'accordo 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 utilizzati dai browser più diffusi se il nome del file "naïvefile" (senza virgolette e dove la terza lettera è U+00EF) dovesse essere codificato nell'intestazione Content-Disposition?
Ai fini di questa domanda, browser più diffusi essendo:
- Firefox
- Internet Explorer
- Safari
- Google Chrome
- musica lirica
Soluzione
Si discute di questo, inclusi i collegamenti ai test dei browser e alla compatibilità con le versioni precedenti, nella proposta RFC5987, "Set di caratteri e codifica della lingua per i parametri del campo di intestazione HTTP (Hypertext Transfer Protocol)."
RFC2183 indica che tali intestazioni dovrebbero essere codificate secondo RFC2184, che è stato reso obsoleto da RFC2231, coperto dal progetto RFC di cui sopra.
Altri suggerimenti
So che questo è un vecchio post ma è ancora molto attuale.Ho scoperto che i browser moderni supportano rfc5987, che consente la codifica utf-8, codificata in percentuale (codificata URL).Quindi Naïve file.txt diventa:
Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt
Safari (5) non lo supporta.Dovresti invece utilizzare lo standard Safari di 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, codifica percentuale:
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 testato quanto sopra utilizzando IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.
Aggiornamento Novembre 2013:
Ecco il codice che utilizzo attualmente.Devo ancora supportare IE8, quindi non posso liberarmi della prima parte.Si scopre che i browser su Android utilizzano il gestore dei download integrato in Android e non è in grado di 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 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 caratteri ma non per tutti.Ma chi se ne frega di IE7 al giorno d'oggi?
Questa è la funzione che utilizzo per generare nomi di file sicuri per Android.Tieni presente che non so quali caratteri siano supportati su Android ma che ho testato che funzionino di sicuro:
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:Ho provato in IE7 e IE8 e ho scoperto che non avevo bisogno di evitare l'apostrofo (').Hai un esempio in cui fallisce?
@Dave Van den Eynde:La combinazione dei due nomi di file su una riga secondo RFC6266 funziona tranne che per Android e IE7+8 e ho aggiornato il codice per riflettere questo.Grazie per il suggerimento
@Thilo:Nessuna 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 Collegare non sembra funzionare molto bene.
Non esiste un modo interoperabile per codificare i nomi non ASCII
Content-Disposition
. La compatibilità del browser è un disastro.IL sintassi teoricamente corretta per l'utilizzo di UTF-8 in
Content-Disposition
è molto strano:filename*=UTF-8''foo%c3%a4
(sì, è un asterisco e senza virgolette tranne una virgoletta singola vuota nel mezzo)Questa intestazione non è del tutto standard (Le specifiche HTTP/1.1 ne riconoscono l'esistenza, ma non richiede che i client lo supportino).
Esiste un'alternativa semplice e molto robusta: utilizza un URL che contenga il nome file desiderato.
Quando il nome dopo l'ultima barra è quello che desideri, non hai bisogno di intestazioni aggiuntive!
Questo trucco funziona:
/real_script.php/fake_filename.doc
E se il tuo server supporta la riscrittura degli URL (ad es. mod_rewrite
in Apache) allora puoi nascondere completamente la parte dello script.
I caratteri negli URL devono essere in UTF-8, codificati urlen byte per byte:
/mot%C3%B6rhead # motörhead
RFC6266 descrive il “Utilizzo del campo Content-Disposition Header nell'Hypertext Transfer Protocol (HTTP)”.Citando da ciò:
6.Considerazioni sull'internazionalizzazione
IL "
filename*
"parametro (Sezione 4.3), usando 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 nel loro sezione esempi:
Questo esempio è lo stesso di quello sopra, ma aggiungendo il parametro "nome file" per la compatibilità con gli agenti utente che non sono implementati RFC5987:
Content-Disposition: attachment; filename="EURO rates"; filename*=utf-8''%e2%82%ac%20rates
Nota:Quegli user agent che non supportano il file RFC5987 la codifica ignora "
filename*
" quando si verifica dopo "filename
”.
In Appendice D c'è anche un lungo elenco di suggerimenti per aumentare l'interoperabilità.Indica anche un sito che confronta le implementazioni.Gli attuali test passa-tutto adatti per nomi di file comuni includono:
- attwithisofnplain:nome file ISO-8859-1 semplice con virgolette doppie e senza codifica.Ciò richiede un nome file che sia tutto ISO-8859-1 e non contenga segni di percentuale, almeno non davanti a cifre esadecimali.
- attfnentrambi:due parametri nell'ordine sopra descritto.Dovrebbe funzionare per la maggior parte dei nomi di file sulla maggior parte dei browser, anche se IE8 utilizzerà il "
filename
"parametro.
Quello RFC5987 a loro volta riferimenti RFC2231, che descrive il formato effettivo.2231 è principalmente per la posta e 5987 ci dice quali parti possono essere utilizzate anche per le intestazioni HTTP.Non confonderlo con le intestazioni MIME utilizzate all'interno di a multipart/form-data
HTTP corpo, che è disciplinato da RFC2388 (sezione 4.4 in particolare) e il Bozza HTML5.
Il seguente documento collegato da il progetto di RFC menzionato da Jim nella sua risposta affronta ulteriormente la domanda e merita sicuramente una nota diretta qui:
Casi di test per l'intestazione HTTP Content-Disposition e la codifica RFC 2231/2047
in asp.net mvc2 utilizzo qualcosa del genere:
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)
Metti il nome del file tra virgolette doppie.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 più opzioni.I browser non supportano le specifiche e si comportano diversamente, credo che le virgolette doppie siano l'opzione migliore.
Utilizzo i seguenti frammenti di codice per la codifica (assumendo nome del file contiene il nome del file e l'estensione del file, ovvero:prova.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 + "\"");
Nell'API Web ASP.NET, codifico l'URL del 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;
}
}
Ho testato il seguente codice in tutti i principali browser, inclusi i meno recenti Explorer (tramite la modalità 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 utilizzare 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). // 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);
}
Alla fine ho ottenuto il seguente codice nel mio script "download.php" (basato su questo post sul blog E questi casi di prova).
$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 utilizza il modo standard di filename="..." fintanto che vengono utilizzati solo caratteri iso-latin1 e "safe";in caso contrario, aggiunge il modo filename*=UTF-8'' con codifica URL.Secondo questo specifico caso di prova, dovrebbe funzionare da MSIE9 in su e sui recenti FF, Chrome, Safari;nella versione MSIE inferiore, dovrebbe offrire un nome file contenente la versione ISO8859-1 del nome file, con trattini bassi sui caratteri non presenti in questa codifica.
Nota finale:il massimola dimensione per ciascun campo di intestazione è 8190 byte su Apache.UTF-8 può contenere fino a quattro byte per carattere;dopo rawurlencode, è x3 = 12 byte per un carattere.Abbastanza inefficiente, ma teoricamente dovrebbe essere comunque possibile avere più di 600 "sorrisi" %F0%9F%98%81 nel nome del file.
In PHP questo lo ha fatto per me (supponendo 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 nome file*=utf-8 utilizzerà la versione UTF8 del nome file, altrimenti utilizzerà il nome file decodificato.Se il nome del tuo file contiene caratteri che non possono essere rappresentati in ISO-8859-1 potresti prendere in considerazione l'utilizzo iconv
Invece.
Soluzione ASP classica
La maggior parte dei browser moderni supporta il passaggio del file Filename
COME UTF-8
now ma, come nel caso della soluzione di caricamento file che utilizzo, era basata su FreeASPUload.Net (il sito non esiste più, il link punta a archivio.org) non funzionerebbe poiché l'analisi del binario si basava sulla lettura di stringhe codificate ASCII a byte singolo, che funzionava correttamente quando si passavano dati codificati UTF-8 fino ad arrivare ai caratteri che ASCII non supporta.
Tuttavia sono riuscito a trovare una soluzione per far sì che il codice leggesse e analizzasse il 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 a Caricamento file ASP puro implementando il BytesToString()
funzione da include_aspuploader.asp
nel mio codice sono riuscito a ottenere UTF-8
nomi di file funzionanti.
link utili
Solo un aggiornamento poiché stavo provando tutte queste cose oggi in risposta a un problema del cliente
- Ad eccezione di Safari configurato per il giapponese, tutti i browser testati dai nostri clienti hanno funzionato meglio con filename=text.pdf, dove text è un valore cliente serializzato da ASP.Net/IIS in utf-8 senza codifica URL.Per qualche motivo, Safari configurato per l'inglese accetterebbe e salverebbe correttamente un file con nome giapponese utf-8, ma lo stesso browser configurato per il giapponese salverebbe il file con i caratteri utf-8 non interpretati.Tutti gli altri browser testati sembravano funzionare meglio/bene (indipendentemente dalla configurazione della lingua) con il nome file utf-8 codificato senza codifica URL.
- Non sono riuscito a trovare un singolo browser che implementi Rfc5987/8187 affatto.Ho testato con le ultime build di Chrome e Firefox oltre a IE 11 ed Edge.Ho provato a impostare l'intestazione solo con filename*=utf-8''texturlencoded.pdf, impostandola sia con filename=text.pdf;nomefile*=utf-8''texturlencoded.pdf.Nessuna caratteristica di Rfc5987/8187 sembrava essere elaborata correttamente in nessuna delle precedenti.
Abbiamo riscontrato un problema simile in un'applicazione Web e alla fine abbiamo letto il nome del file dall'HTML <input type="file">
, e impostandolo nel formato con codifica URL in un nuovo HTML <input type="hidden">
.Ovviamente abbiamo dovuto rimuovere il percorso come "C:\fakepath\" restituito da alcuni browser.
Ovviamente questo non risponde direttamente alla domanda dei PO, ma potrebbe essere una soluzione per altri.
Normalmente codifico tramite URL (con %xx) i nomi dei file e sembra funzionare in tutti i browser.Potresti comunque fare qualche prova.