Content-disposition: nom de fichier UTF-8 crypté en base64

Le problème

Dans un récent projet Django, j’ai eu à coder une vue qui permettait de télécharger des fichiers via HTTP. Le problème: plusieurs fichiers avaient des lettres avec accents d’alphabets cyrilliques. Bien sûr, on pourrait simplement nettoyer le nom du fichier afin de s’assurer que tous les caractères sont ASCII, mais dans le cas présent, il était important de conserver le nom du fichier intact. Normalement, on se contenterait de donner le nom du fichier tel quel en prenant garde uniquement aux guillemets et guillemets doubles: 

content-disposition:attachment; filename="Nom de fichier standard.pdf"

Cependant, les noms de fichiers contenant des caractères UTF-8 sans équivalent ASCII provoquaient des erreurs du type: 

UnicodeEncodeError: 'ascii' codec can't encode character u'xe9' in position 24: ordinal not in range(128), HTTP response headers must be in US-ASCII format

Comment permettre des noms de fichier UTF-8 ?

C’est alors que je me suis demandé comment les grands de ce monde géraient ces cas. Rapidement, je me suis aperçu que Gmail retournait à tout coup le nom exact du fichier. Aucune substitution, le nom de fichier original est retourné à tout coup. 

J’ai donc fait quelques tests avec des pièces jointes dans Gmail avec Internet Explorer, Firefox et Chrome. Je me suis amusé à analyser le Response Header retourné par Gmail lors du téléchargement d’une pièce jointe. Plus précisément, j’ai porté attention au content-disposition qui déclenche le processus de téléchargement de fichiers dans un navigateur et qui contient le nom du fichier proposé à l’utilisateur.

Avec Firefox et Chrome

Dans le cas d’un nom de fichier sans accent ou caractère spécial, Gmail envoie un Content-Disposition des plus standards:

Content-Disposition: attachment; filename="nom-du-fichier.doc"

Là où l’expérience devient intéressante est lorsqu’on ajoute un (ou plusieurs) accent dans le nom du fichier. Par exemple, pour un fichier nommé Un fichier spécial avec accent.txt, on obtient ceci: 

content-disposition:attachment; filename="=?UTF-8?B?VW4gZmljaGllciBzcMOpY2lhbCBhdmVjIGFjY2VudC50eHQ=?="

La partie VW4gZmljaGllciBzcMOpY2lhbCBhdmVjIGFjY2VudC50eHQ= correspond au nom du fichier (Un fichier spécial avec accent.txt) crypté en base64.

Encore plus étrange, lorsque le nom de fichier est passablement long (environ plus de 35 caractères), par exemple: Un fichier spécial avec accent mais avec un très long nom de fichier.txt on obtient un résultat semblable, mais on remarque que le nom est segmenté:

content-disposition:attachment; filename="=?UTF-8?B?VW4gZmljaGllciBzcMOpY2lhbCBhdmVjIGFjY2VudCBtYWlzIA==?= =?UTF-8?B?YXZlYyB1biB0csOocyBsb25nIG5vbSBkZSBmaWNoaWVyLnR4dA==?="
  • VW4gZmljaGllciBzcMOpY2lhbCBhdmVjIGFjY2VudCBtYWlzIA==
    donne : 
    “Un fichier spécial avec accent mais “
     
  • YXZlYyB1biB0csOocyBsb25nIG5vbSBkZSBmaWNoaWVyLnR4dA==
    donne : 
    “avec un très long nom de fichier.txt” 

Avec Internet Explorer

Lorsqu’on utilise Gmail avec Internet Explorer, Gmail renvoie simplement la version encodée pour le web:

Content-Disposition attachment; filename="Un%20fichier%20sp%C3%A9cial%20avec%20accent%20mais%20avec%20un%20tr%C3%A8s%20long%20nom%20de%20fichier.txt"

Bien que cela soit simple à implémenter, c’est encore un cas “d’autre navigateur, autres moeurs”… Souhaitons que le vent de standardisation apporté par le HTML5 souffle assez fort pour réviser cet aspect.

Content-disposition en format base64 pour supporter les caractères UTF-8

Voici un extrait de code en Python pour Django qui permet d’implémenter un comportement semblable à celui que Gmail propose: 
https://gist.github.com/3085973