¿Cómo codificar el parámetro de nombre de archivo del encabezado Content-Disposition en HTTP?

StackOverflow https://stackoverflow.com/questions/93551

Pregunta

Aplicaciones web que quieren forzar que un recurso sea descargado en lugar de directamente prestado en un navegador web emite un Content-Disposition encabezado en la respuesta HTTP del formulario:

Content-Disposition: attachment; filename=NOMBRE DEL ARCHIVO

El filename El parámetro se puede utilizar para sugerir un nombre para el archivo en el que el navegador descarga el recurso. RFC 2183 (Contenido-Disposición), sin embargo, establece en sección 2.3 (El parámetro de nombre de archivo) que el nombre del 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 archivo 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á más allá del alcance de este documento definir los mecanismos necesarios.

Sin embargo, existe evidencia empírica 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) no están de acuerdo con el esquema de codificación y la especificación del juego 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 del archivo “naïvefile” (sin comillas y donde la tercera letra es U+00EF) necesita codificarse en el encabezado Content-Disposition?

A los efectos de esta pregunta, navegadores populares ser:

  • Firefox
  • explorador de Internet
  • Safari
  • Google Chrome
  • Ópera
¿Fue útil?

Solución

Hay una discusión sobre esto, incluidos enlaces a pruebas de navegadores y compatibilidad con versiones anteriores, en la propuesta RFC 5987, "Conjunto de caracteres y codificación de idioma para los parámetros del campo de encabezado del Protocolo de transferencia de hipertexto (HTTP)."

RFC 2183 indica que dichos encabezados deben codificarse de acuerdo con RFC 2184, que quedó obsoleto por RFC 2231, cubierto por el borrador RFC anterior.

Otros consejos

Sé que esta es una publicación antigua pero sigue siendo muy relevante.Descubrí que los navegadores modernos admiten rfc5987, que permite la codificación utf-8, con codificación porcentual (codificada en URL).Entonces Naïve file.txt se convierte en:

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

Safari (5) no admite esto.En su lugar, deberías utilizar el estándar de Safari para escribir el nombre del archivo directamente en tu encabezado codificado en utf-8:

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

IE8 y versiones anteriores tampoco lo admiten y es necesario utilizar 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 usando IE7, IE8, IE9, Chrome 13, Opera 11, FF5, Safari 5.

Actualizar Noviembre de 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 de Android utilizan el administrador de descargas integrado de Android y no pueden analizar de manera confiable los nombres de archivos de la manera 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, usando 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 archivos seguros para Android.Tenga en cuenta que no sé qué caracteres son compatibles con Android, pero he probado que 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:Probé en IE7 e IE8 y resultó que no necesitaba escapar del apóstrofo (').¿Tiene un ejemplo en el que falla?

@Dave Van den Eynde:Combinar los dos nombres de archivos en una línea según RFC6266 funciona 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 un navegador.Es posible que tengas algo de suerte utilizando el enfoque de Android.

@Alex Zhukovskiy:No sé por qué, pero como se discutió en Conectar no parece funcionar muy bien.

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 ningún encabezado adicional!

Este truco funciona:

/real_script.php/fake_filename.doc

Y si su servidor admite la reescritura de URL (p. ej. mod_rewrite en Apache) entonces puedes ocultar completamente la parte del script.

Los caracteres de las URL deben estar en UTF-8, codificados en URL byte a byte:

/mot%C3%B6rhead   # motörhead

RFC 6266 describe el "Uso del campo de encabezado de disposición de contenido en el protocolo de transferencia de hipertexto (HTTP)”.Citando eso:

6.Consideraciones de internacionalización

El "filename*”parámetro (Sección 4.3), usando la codificación definida en [RFC5987], permite que el servidor transmita caracteres fuera del conjunto de caracteres ISO-8859-1, y también para especificar opcionalmente el idioma en uso.

y en su sección de ejemplos:

Este ejemplo es el mismo que el anterior, pero agregando el parámetro "nombre de archivo" para la compatibilidad con agentes de usuario que no implementan RFC 5987:

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

Nota:Aquellos agentes de usuario que no soportan la RFC 5987 codificar ignorar "filename*"cuando ocurre después de"filename”.

En 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 aprobación total adecuadas para nombres de archivos comunes incluyen:

  • attwithisofnplain:Nombre de archivo simple ISO-8859-1 con comillas dobles y sin codificación.Esto requiere un nombre de archivo que sea todo ISO-8859-1 y que no contenga signos de porcentaje, al menos no delante de dígitos hexadecimales.
  • attfnambos: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 usará el "filename”parámetro.

Eso RFC 5987 a su vez referencias RFC 2231, que describe el formato real.2231 es principalmente para correo y 5987 nos dice qué partes también se pueden usar para encabezados HTTP.No confunda esto con los encabezados MIME utilizados dentro de un multipart/form-data HTTP cuerpo, que se rige por RFC 2388 (sección 4.4 en particular) y el borrador HTML 5.

El siguiente documento enlazado desde el borrador del RFC mencionado por Jim en su respuesta aborda con más detalle 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 asp.net mvc2 uso algo como esto:

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

Supongo que si no usas mvc(2), podrías codificar el nombre del archivo usando

HttpUtility.UrlPathEncode(fileName)

Coloque el nombre del archivo entre comillas dobles.Resolvió el problema para mí.Como esto:

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

He probado varias opciones.Los navegadores no admiten las especificaciones y actúan de manera diferente; creo que las comillas dobles son la mejor opción.

Utilizo los siguientes fragmentos de código para codificar (asumiendo Nombre del archivo contiene el nombre del archivo y la extensión del archivo, es decir:prueba.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 Web API, codifico la URL del 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;
    }
}

IE 9 Not fixed
IE 9 Fixed

Probé el siguiente código en todos los navegadores principales, incluidos los exploradores más antiguos (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.'"');

Si está utilizando un backend de nodejs, puede usar el siguiente código que encontré aquí

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 esta publicación de blog 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 utiliza la forma estándar de filename="..." siempre y cuando sólo se utilicen caracteres iso-latin1 y "seguros";de lo contrario, agrega la forma codificada en URL filename*=UTF-8''.De acuerdo a este caso de prueba específico, debería funcionar desde MSIE9 en adelante y en FF, Chrome y Safari recientes;en una versión inferior de MSIE, debería ofrecer un nombre de archivo que contenga la versión ISO8859-1 del nombre de archivo, con guiones bajos en los 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 carácter.Bastante ineficiente, pero en teoría debería ser posible tener más de 600 "sonrisas" %F0%9F%98%81 en el nombre del archivo.

En PHP esto lo hizo por mí (suponiendo 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 nombre de archivo*=utf-8 utilizará la versión UTF8 del nombre de archivo; de lo contrario, utilizará el nombre de archivo decodificado.Si su nombre de archivo contiene caracteres que no se pueden representar en ISO-8859-1, es posible que desee considerar usar iconv en cambio.

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 carga de archivos que uso y que se basó en FreeASPUpload.Net (el sitio ya no existe, el enlace apunta a archivo.org) no funcionaría ya que el análisis del binario se basaba en la lectura de cadenas codificadas en ASCII de un solo byte, lo que funcionó bien cuando pasó datos codificados en UTF-8 hasta llegar a caracteres que ASCII no admite.

Sin embargo, pude encontrar una solución para que el código leyera y analizara 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 Carga de archivos ASP puros implementando el BytesToString() función de include_aspuploader.asp en mi propio código pude obtener UTF-8 nombres de archivos funcionando.


Enlaces útiles

Solo una actualización ya que hoy estaba probando todo esto en respuesta a un problema de un cliente.

  • Con la excepción de Safari configurado para japonés, todos los navegadores que nuestro cliente probó funcionaron mejor con filename=text.pdf, donde el texto es un valor del cliente serializado por ASP.Net/IIS en utf-8 sin codificación de URL.Por alguna razón, Safari configurado para inglés aceptaría y guardaría correctamente un archivo con el nombre japonés utf-8, pero ese mismo navegador configurado para japonés guardaría el archivo con los caracteres utf-8 sin interpretar.Todos los demás navegadores probados parecieron funcionar mejor/bien (independientemente de la configuración del idioma) con el nombre de archivo utf-8 codificado sin codificación de URL.
  • No pude encontrar un solo navegador que implemente Rfc5987/8187 en absoluto.Probé con las últimas versiones de Chrome, Firefox, IE 11 y Edge.Intenté configurar el encabezado solo con filename*=utf-8''texturlencoded.pdf, configurándolo con filename=text.pdf;nombre de archivo*=utf-8''texturlencoded.pdf.Ninguna característica de Rfc5987/8187 parecía procesarse correctamente en ninguno de los anteriores.

Tuvimos un problema similar en una aplicación web y terminamos leyendo el nombre del archivo del HTML. <input type="file">, y configurarlo en el formato codificado en URL en un nuevo HTML <input type="hidden">.Por supuesto, tuvimos que eliminar la ruta como "C:\fakepath\" que devuelven algunos navegadores.

Por supuesto, esto no responde directamente a la pregunta de los OP, pero puede ser una solución para otros.

Normalmente codifico URL (con %xx) los nombres de archivos y parece funcionar en todos los navegadores.Quizás quieras hacer algunas pruebas de todos modos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top