¿Cómo codificar el nombre de archivo UTF8 para encabezados HTTP? (Python, Django)

Tengo problemas con los encabezados HTTP, están codificados en ASCII y quiero proporcionar una vista para descargar archivos que los nombres no pueden ser ASCII.

response['Content-Disposition'] = 'attachment; filename="%s"' % (vo.filename.encode("ASCII","replace"), ) 

No quiero usar archivos estáticos que sirvan para el mismo problema con nombres de archivos que no son ASCII, pero en este caso habría un problema con el sistema de archivos y es la encoding del nombre del archivo. (No sé el objective OS).

Ya probé urllib.quote (), pero genera una excepción KeyError.

Posiblemente estoy haciendo algo mal pero tal vez sea imposible.

Esta es una pregunta frecuente.

No hay una manera interoperable de hacer esto. Algunos navegadores implementan extensiones propietarias (IE, Chrome), otros implementan RFC 2231 (Firefox, Opera).

Ver casos de prueba en http://greenbytes.de/tech/tc2231/ .

Actualización: a partir de noviembre de 2012, todos los navegadores de escritorio actuales admiten la encoding definida en RFC 6266 y RFC 5987 (Safari> = 6, IE> = 9, Chrome, Firefox, Opera, Konqueror).

No envíe un nombre de archivo en Content-Disposition. No hay forma de hacer que los parámetros de encabezado no ASCII funcionen en el navegador cruzado (*).

En su lugar, envíe solo “Contenido-Disposición: archivo adjunto”, y deje el nombre del archivo como una cadena UTF-8 codificada en la URL en la parte final (PATH_INFO) de su URL, para que el navegador la seleccione y la use de forma predeterminada. Las URL de UTF-8 se manejan de forma mucho más confiable por los navegadores que cualquier otra cosa que tenga que ver con la Disposición de contenido.

(*: en realidad, ni siquiera hay un estándar actual que diga cómo se debe hacer, ya que las relaciones entre los RFC 2616, 2231 y 2047 son bastante disfuncionales, algo que Julian está tratando de aclarar a un nivel de especificaciones. El soporte consistente del navegador es en el futuro distante.)

Tenga en cuenta que en 2011, el documento RFC 6266 (especialmente el Apéndice D) hizo un balance sobre este tema y tiene recomendaciones específicas a seguir.

Es decir, puede emitir un filename con solo caracteres ASCII, seguido de un filename* de filename* con un filename* de filename* con formato RFC 5987 para aquellos agentes que lo entienden.

Normalmente esto se verá como filename="my-resume.pdf"; filename*=UTF-8''My%20R%C3%A9sum%C3%A9.pdf filename="my-resume.pdf"; filename*=UTF-8''My%20R%C3%A9sum%C3%A9.pdf , donde el nombre de archivo Unicode (“My Résumé.pdf”) está codificado en UTF-8 y luego codificado en porcentaje (nota, NO uso + para espacios).

Lea realmente RFC 6266 y RFC 5987 (o use una biblioteca sólida y probada que resum esto para usted), ya que mi resumen aquí carece de detalles importantes.

Puedo decir que he tenido éxito utilizando el formato más nuevo ( RFC 5987 ) de especificar un encabezado codificado con el formulario de correo electrónico ( RFC 2231 ). Se me ocurrió la siguiente solución que se basa en el código del proyecto django-sendfile.

 import unicodedata from django.utils.http import urlquote def rfc5987_content_disposition(file_name): ascii_name = unicodedata.normalize('NFKD', file_name).encode('ascii','ignore').decode() header = 'attachment; filename="{}"'.format(ascii_name) if ascii_name != file_name: quoted_name = urlquote(file_name) header += '; filename*=UTF-8\'\'{}'.format(quoted_name) return header # eg # request['Content-Disposition'] = rfc5987_content_disposition(file_name) 

Solo he probado mi código en Python 3.4 con Django 1.8 . Así que la solución similar en django-sendfile puede ser mejor para usted.

Hay un boleto de larga data en el rastreador de Django que reconoce esto, pero aún no se han propuesto parches. Desafortunadamente, esto es lo más parecido a usar una biblioteca robusta y probada que pude encontrar, por favor, hágame saber si hay una solución mejor.

A partir de 2018, una solución ya está disponible en Django 2.1 (después de languidecer durante siete años como un boleto abierto ). Puede usar el parámetro as_attachment integrado en FileResponse . Por ejemplo, para devolver un archivo output_file con tipo mime output_mime_type como respuesta HTTP:

 response = FileResponse(open(output_file, 'rb'), as_attachment=True, content_type=output_mime_type) return response 

O, si no puede usar FileResponse , puede usar la parte relevante de su fuente para cambiar el Content-Disposition más directamente. Así es como se ve esa fuente actualmente:

 from urllib.parse import quote try: document.file_name.encode('ascii') file_expr = 'filename="{}"'.format(filename) except UnicodeEncodeError: # Handle a non-ASCII filename file_expr = "filename*=utf-8''{}".format(quote(filename)) response['Content-Disposition'] = 'attachment; {}'.format(file_expr) 

Un truco

 if (Request.UserAgent.Contains("IE")) { // IE will accept URL encoding, but spaces don't need to be, and since they're so common.. filename = filename.Replace("%", "%25").Replace(";", "%3B").Replace("#", "%23").Replace("&", "%26"); }