browser - form - ¿Cómo codificar el parámetro de nombre de archivo del encabezado Content-Disposition en HTTP?
content-disposition pdf (17)
Las aplicaciones web que desean forzar la descarga de un recurso en lugar de representarse directamente en un navegador web emiten un encabezado de Content-Disposition
en la respuesta HTTP del formulario:
Content-Disposition: attachment; filename= FILENAME
El parámetro de filename
se puede usar para sugerir un nombre para el archivo en el que el navegador descarga el recurso. RFC 2183 (Content-Disposition), sin embargo, establece en la sección 2.3 (El parámetro de nombre de archivo) que el nombre de archivo solo puede usar caracteres US-ASCII:
La gramática actual [RFC 2045] restringe los valores de los parámetros (y, por lo tanto, los nombres de archivos de Disposición de Contenido) a US-ASCII. Reconocemos la gran conveniencia de permitir conjuntos de caracteres arbitrarios en los nombres de archivo, pero está fuera del alcance de este documento definir los mecanismos necesarios.
Existe evidencia empírica, sin embargo, de que los navegadores web más populares hoy en día parecen permitir caracteres que no son ASCII de EE. UU., Pero (por falta de un estándar) están en desacuerdo con el esquema de codificación y la especificación del conjunto de caracteres del nombre del archivo. La pregunta es, entonces, ¿cuáles son los diversos esquemas y codificaciones empleados por los navegadores populares si el nombre de archivo "naïvefile" (sin comillas y donde la tercera letra es U + 00EF) se debe codificar en el encabezado Content-Disposition?
Para el propósito de esta pregunta, los navegadores populares son:
- Firefox
- explorador de Internet
- Safari
- Google Chrome
- Ópera
Solución ASP clásica
La mayoría de los navegadores modernos admiten pasar el Filename
como UTF-8
ahora, pero como fue el caso con una solución de FreeASPUpload.Net archivos que utilizo que se basó en FreeASPUpload.Net (el sitio ya no existe, los puntos de enlace a archive.org ) no funcionaría como el análisis del binario se basó en la lectura de cadenas codificadas en ASCII de un solo byte, que funcionaron bien cuando pasó los datos codificados en UTF-8 hasta que llegó a los caracteres que ASCII no admite.
Sin embargo, pude encontrar una solución para obtener el código para leer y analizar el binario como 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
El crédito va a Pure ASP File Upload al implementar la función BytesToString()
de include_aspuploader.asp
en mi propio código que pude hacer funcionar los nombres UTF-8
archivo UTF-8
.
Enlaces útiles
Multipart / form-data y UTF-8 en una aplicación ASP Classic
Diferencias de formato Unicode, UTF, ASCII, ANSI
Descubrí una solución que funciona para todos mis navegadores (es decir, todos los navegadores que he instalado: IE8, FF16, Opera 12, Chrome 22).
Mi solución se describe en otro hilo: caracteres especiales de descarga de archivos de servlet de Java
Mi solución se basa en el hecho de cómo los navegadores intentan leer el valor del parámetro de filename
de filename
. Si no se especifica ningún conjunto de caracteres en el parámetro de filename
(por ejemplo, filename*=utf-8''''test.xml
), los navegadores esperan que el valor esté codificado en la codificación nativa del navegador.
Diferentes navegadores esperan diferentes codificaciones nativas. Por lo general, la codificación nativa del navegador es utf-8 (FireFox, Opera, Chrome). Pero la codificación nativa de IE es Win-1250. (No sé nada sobre otros navegadores.)
Por lo tanto, si ponemos valor en filename
parametr, que está codificado por utf-8 / win-1250 de acuerdo con el navegador del usuario, debería funcionar. Al menos, funciona para mí.
En resumen, si tenemos un archivo llamado omáčka.xml
,
Para FireFox, Opera y Chrome respondo este encabezado (codificado en utf-8):
Content-Disposition: attachment; filename="omáčka.xml"
y para IE respondo este encabezado (codificado en win-1250):
Content-Disposition: attachment; filename="omáèka.jpg"
El ejemplo de Java está en mi publicación que se menciona arriba.
El siguiente documento vinculado al borrador del RFC mencionado por en su respuesta aborda la pregunta y definitivamente vale la pena una nota directa aquí:
Casos de prueba para encabezado de disposición de contenido HTTP y codificación RFC 2231/2047
En PHP esto lo hizo por mí (asumiendo que el nombre del archivo está codificado en UTF8):
header(''Content-Disposition: attachment;''
. ''filename="'' . addslashes(utf8_decode($filename)) . ''";''
. ''filename*=utf-8/'/''' . rawurlencode($filename));
Probado contra IE8-11, Firefox y Chrome.
Si el navegador puede interpretar el nombre de archivo * = utf-8 , utilizará la versión UTF8 del nombre de archivo, de lo contrario, utilizará el nombre de archivo descodificado. Si su nombre de archivo contiene caracteres que no se pueden representar en ISO-8859-1, puede considerar usar iconv
lugar.
En la API web de ASP.NET, url codifica el nombre del archivo:
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;
}
}
Hay una discusión de esto, incluidos los enlaces a las pruebas del navegador y la compatibilidad con versiones anteriores, en el RFC 5987 propuesto, "Parámetros de juego de caracteres y codificación de idioma para el protocolo de transferencia de hipertexto (HTTP)".
RFC 2183 indica que dichos encabezados deben codificarse de acuerdo con RFC 2184 , que fue obsoleto por RFC 2231 , cubierto por el borrador de RFC anterior.
Normalmente codifico en URL (con% xx) los nombres de archivo, y parece funcionar en todos los navegadores. Es posible que desee hacer algunas pruebas de todos modos.
Ponga su nombre de archivo entre comillas dobles. Resolví el problema por mí. Me gusta esto:
Content-Disposition: attachment; filename="My Report.doc"
http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download
Probé el siguiente código en todos los navegadores principales, incluidos los Exploradores anteriores (a través del modo de compatibilidad), y funciona bien en todas partes:
$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.''"'');
Sé que este es un post antiguo pero sigue siendo muy relevante. He encontrado que los navegadores modernos admiten rfc5987, que permite la codificación utf-8, el porcentaje codificado (codificado en url). Entonces Naïve file.txt se convierte en:
Content-Disposition: attachment; filename*=UTF-8''''Na%C3%AFve%20file.txt
Safari (5) no soporta esto. En su lugar, debe usar el estándar de Safari para escribir el nombre del archivo directamente en su encabezado codificado utf-8:
Content-Disposition: attachment; filename=Naïve file.txt
IE8 y versiones anteriores tampoco lo admiten y necesitas usar el estándar IE de codificación utf-8, codificado en porcentaje:
Content-Disposition: attachment; filename=Na%C3%AFve%20file.txt
En ASP.Net utilizo el siguiente código:
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);
Probé lo anterior utilizando IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.
Actualización noviembre 2013:
Aquí está el código que uso actualmente. Todavía tengo que soportar IE8, así que no puedo deshacerme de la primera parte. Resulta que los navegadores en Android utilizan el gestor de descargas de Android integrado y no pueden analizar de forma fiable los nombres de los archivos de forma estándar.
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);
Lo anterior, ahora probado en IE7-11, Chrome 32, Opera 12, FF25, Safari 6, usa este nombre de archivo para descargar: 你好 abcABCæøåÆØÅäöüïëêîâéíáóúýñ½§! # ¤% & () = `@ $ $ € {[]} + ´¨ ^ ~ ''-_,;. txt
En IE7 funciona para algunos personajes pero no para todos. ¿Pero a quién le importa IE7 hoy en día?
Esta es la función que uso para generar nombres de archivo seguros para Android. Tenga en cuenta que no sé qué caracteres son compatibles con Android, pero que he probado que estos funcionan con seguridad:
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: Lo probé en IE7 e IE8 y resultó que no necesitaba escapar de apostrophe (''). ¿Tienes un ejemplo donde falla?
@Dave Van den Eynde: la combinación de los dos nombres de archivo en una línea de acuerdo con los trabajos RFC6266, excepto para Android e IE7 + 8, y he actualizado el código para reflejar esto. Gracias por la sugerencia.
@Thilo: No tengo idea sobre GoodReader o cualquier otro que no sea navegador. Es posible que tenga suerte con el enfoque de Android.
@Alex Zhukovskiy: No sé por qué, pero como se comentó en Connect , no parece funcionar muy bien.
Si está utilizando un backend de nodejs, puede usar el siguiente código que encontré here
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);
}
Terminé con el siguiente código en mi script "download.php" (basado en este blogpost y estos casos de prueba ).
$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) ));
Esto usa la forma estándar de filename = "..." siempre que solo se utilicen caracteres iso-latin1 y "safe"; si no, agrega el nombre de archivo * = UTF-8 '''' de manera codificada en url. De acuerdo con este caso de prueba específico , debería funcionar desde MSIE9 hasta, y en FF reciente, Chrome, Safari; en la versión más baja de MSIE, debe ofrecer un nombre de archivo que contenga la versión ISO8859-1 del nombre de archivo, con guiones bajos en caracteres que no estén en esta codificación.
Nota final: el máximo. El tamaño de cada campo de encabezado es de 8190 bytes en Apache. UTF-8 puede tener hasta cuatro bytes por carácter; después de rawurlencode, es x3 = 12 bytes por un carácter. Bastante ineficiente, pero aún debería ser teóricamente posible tener más de 600 "sonrisas"% F0% 9F% 98% 81 en el nombre del archivo.
Tuvimos un problema similar en una aplicación web, y terminamos leyendo el nombre del archivo desde el HTML <input type="file">
, y configurándolo en la forma codificada con url en un nuevo HTML <input type="hidden">
. Por supuesto, tuvimos que eliminar la ruta como "C: / fakepath /" que es devuelta por algunos navegadores.
Por supuesto, esto no responde directamente a la pregunta de OP, pero puede ser una solución para otros.
Utilizo los siguientes fragmentos de código para la codificación (suponiendo que fileName contiene el nombre de archivo y la extensión del archivo, es decir: 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 + "/"");
en asp.net mvc2 uso algo como esto:
return File(
tempFile
, "application/octet-stream"
, HttpUtility.UrlPathEncode(fileName)
);
Supongo que si no usa mvc (2) simplemente podría codificar el nombre de archivo usando
HttpUtility.UrlPathEncode(fileName)
RFC 6266 describe el " Uso del campo de encabezado de disposición de contenido en el protocolo de transferencia de hipertexto (HTTP) ". Citando de eso:
6. Consideraciones de internacionalización
El parámetro “
filename*
” ( Sección 4.3 ), que utiliza la codificación definida en [ RFC5987 ], permite que el servidor transmita caracteres fuera del conjunto de caracteres ISO-8859-1, y también que especifique opcionalmente el idioma en uso.
Y en su sección de ejemplos :
Este ejemplo es el mismo que el anterior, pero agrega el parámetro "nombre de archivo" para que sea compatible con los agentes de usuario que no implementan RFC 5987 :
Content-Disposition: attachment; filename="EURO rates"; filename*=utf-8''''%e2%82%ac%20rates
Nota: los agentes de usuario que no admiten la codificación RFC 5987 ignoran "
filename*
" cuando ocurre después de "filename
".
En el Apéndice D también hay una larga lista de sugerencias para aumentar la interoperabilidad. También apunta a un sitio que compara implementaciones . Las pruebas actuales de todo paso adecuadas para los nombres de archivos comunes incluyen:
- attwithisofnplain : simple nombre de archivo ISO-8859-1 con comillas dobles y sin codificación. Esto requiere un nombre de archivo que sea todo ISO-8859-1 y no contenga signos de porcentaje, al menos no delante de dígitos hexadecimales.
- attfnboth : dos parámetros en el orden descrito anteriormente. Debería funcionar para la mayoría de los nombres de archivos en la mayoría de los navegadores, aunque IE8 utilizará el parámetro "
filename
".
Ese RFC 5987 a su vez hace referencia al RFC 2231 , que describe el formato real. 2231 es principalmente para correo, y 5987 nos dice qué partes pueden usarse también para los encabezados HTTP. No confunda esto con los encabezados MIME utilizados dentro de un cuerpo HTTP multipart/form-data
, que se rige por RFC 2388 ( sección 4.4 en particular) y el borrador de HTML 5 .
No hay una forma interoperable de codificar nombres que no sean ASCII en
Content-Disposition
. La compatibilidad del navegador es un desastre .La greenbytes.de/tech/webdav/rfc5987.html para el uso de UTF-8 en
Content-Disposition
es muy rara:filename*=UTF-8''''foo%c3%a4
(sí, eso es un asterisco, y no hay comillas, excepto una comilla simple vacía en el centro)Este encabezado es bastante estándar (la especificación HTTP / 1.1 reconoce su existencia , pero no requiere que los clientes lo admitan).
Existe una alternativa simple y muy robusta: use una URL que contenga el nombre de archivo que desea .
Cuando el nombre después de la última barra es el que desea, ¡no necesita encabezados adicionales!
Este truco funciona:
/real_script.php/fake_filename.doc
Y si su servidor admite la reescritura de URL (por ejemplo, mod_rewrite
en Apache), puede ocultar completamente la parte del script.
Los caracteres en las URL deben estar en UTF-8, byte por byte:
/mot%C3%B6rhead # motörhead