web-dev-qa-db-fra.com

Comment coder le nom de fichier UTF8 pour les en-têtes HTTP? (Python, Django)

J'ai un problème avec les en-têtes HTTP, ils sont encodés en ASCII et je veux fournir une vue pour télécharger des fichiers dont les noms peuvent être non ASCII.

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

Je ne veux pas utiliser de fichiers statiques servant au même problème avec des noms de fichiers non ASCII mais dans ce cas, il y aurait un problème avec le système de fichiers et son codage de nom de fichier. connaître le système d'exploitation cible.)

J'ai déjà essayé urllib.quote (), mais cela déclenche l'exception KeyError.

Je fais peut-être quelque chose de mal, mais c'est peut-être impossible.

45
Chris Ciesielski

Ceci est une FAQ.

Il n'y a aucun moyen interopérable de le faire. Certains navigateurs implémentent des extensions propriétaires (IE, Chrome), d'autres implémentent RFC 2231 (Firefox, Opera).

Voir les cas de test sur http://greenbytes.de/tech/tc2231/ .

Mise à jour: depuis novembre 2012, tous les navigateurs de bureau actuels prennent en charge l'encodage défini dans RFC 6266 et RFC 5987 (Safari> = 6, IE> = 9, Chrome, Firefox, Opera, Konqueror).

36
Julian Reschke

N'envoyez pas de nom de fichier dans Content-Disposition. Il n'y a aucun moyen de faire fonctionner les paramètres d'en-tête non ASCII entre plusieurs navigateurs (*).

Au lieu de cela, envoyez simplement "Content-Disposition: attachment" et laissez le nom de fichier sous la forme d'une chaîne UTF-8 codée URL dans la partie de fin (PATH_INFO) de votre URL, pour que le navigateur le récupère et l'utilise par défaut. Les URL UTF-8 sont gérées de manière beaucoup plus fiable par les navigateurs que tout ce qui a trait à Content-Disposition.

(*: en fait, il n'y a même pas de norme actuelle qui explique comment cela devrait être fait car les relations entre les RFC 2616, 2231 et 2047 sont assez dysfonctionnelles, ce que Julian essaie de clarifier à un niveau de spécification. La prise en charge cohérente du navigateur est dans un avenir lointain.)

31
bobince

Notez qu'en 2011, RFC 6266 (en particulier l'annexe D) a pesé sur cette question et a des recommandations spécifiques à suivre.

A savoir, vous pouvez émettre un filename avec seulement ASCII caractères, suivi de filename* avec un nom de fichier au format RFC 5987 pour les agents qui le comprennent.

Cela ressemblera généralement à filename="my-resume.pdf"; filename*=UTF-8''My%20R%C3%A9sum%C3%A9.pdf, où le nom de fichier Unicode ("Mon résumé.pdf") est codé en UTF-8 puis codé en pourcentage (notez, n'utilisez PAS + pour les espaces).

Veuillez lire les RFC 6266 et RFC 5987 (ou utilisez une bibliothèque robuste et testée qui résume cela pour vous), car mon résumé ici manque de détails importants.

29
Alan H.

Depuis 2018, une solution est désormais disponible dans Django 2.1 (après avoir langui pendant sept ans sous la forme d'un ticket ouvert ). Vous pouvez utiliser le as_attachment paramètre intégré à FileResponse . Par exemple, pour renvoyer un fichier output_file avec le type mime output_mime_type comme réponse HTTP:

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

Ou, si vous ne pouvez pas utiliser FileResponse, vous pouvez utiliser la partie appropriée de sa source pour modifier le Content-Disposition plus directement. Voici à quoi ressemble actuellement cette source:

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)
6
Mark Chackerian

Je peux dire que j'ai réussi à utiliser le nouveau format ( RFC 5987 ) de spécification d'un en-tête codé avec le formulaire de courrier électronique ( RFC 2231 ). J'ai trouvé la solution suivante qui est basée sur le code du projet 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

# e.g.
  # request['Content-Disposition'] = rfc5987_content_disposition(file_name)

Je n'ai testé mon code que sur Python 3.4 avec Django 1.8 . Ainsi, la même solution dans Django-sendfile peut mieux vous convenir.

Il y a un ticket de longue date dans le tracker de Django qui reconnaît cela, mais aucun correctif n'a encore été proposé. Donc, malheureusement, cela est aussi proche que possible de l'utilisation d'une bibliothèque testée robuste, faites-moi savoir s'il existe une meilleure solution.

4
Will S