Как кодировать имя файла UTF8 для заголовков HTTP? (Питон, Джанго) - PullRequest
45 голосов
/ 01 сентября 2009

У меня проблема с заголовками HTTP, они закодированы в ASCII, и я хочу предоставить представление для загрузки файлов, имена которых могут быть не ASCII.

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

Я не хочу использовать статические файлы, служащие для той же проблемы с именами файлов, не относящихся к ASCII, но в этом случае возникла бы проблема с файловой системой и ее кодировкой имени файла. (Я не знаю, целевой объект.)

Я уже пробовал urllib.quote (), но возникает исключение KeyError.

Возможно, я делаю что-то не так, но, возможно, это невозможно.

Ответы [ 6 ]

36 голосов
/ 01 сентября 2009

Это FAQ.

Нет совместимого способа сделать это. Некоторые браузеры реализуют собственные расширения (IE, Chrome), другие - RFC 2231 (Firefox, Opera).

См. Контрольные примеры на http://greenbytes.de/tech/tc2231/.

Обновление: по состоянию на ноябрь 2012 года все существующие браузеры настольных компьютеров поддерживают кодировку, определенную в RFC 6266 и RFC 5987 (Safari> = 6, IE> = 9, Chrome, Firefox, Opera, Konqueror).

31 голосов
/ 02 сентября 2009

Не отправлять имя файла в Content-Disposition. Невозможно заставить параметры заголовка, отличные от ASCII, работать в кросс-браузерном режиме (*).

Вместо этого отправьте только «Content-Disposition: attachment» и оставьте имя файла в виде строки UTF-8 в кодировке URL-адреса в конечной части (PATH_INFO) вашего URL, чтобы браузер мог выбрать и использовать по умолчанию. URL-адреса UTF-8 обрабатываются браузерами гораздо надежнее, чем что-либо связанное с Content-Disposition.

(*: на самом деле, даже нет действующего стандарта, который говорит, как это должно быть сделано, поскольку отношения между RFC 2616, 2231 и 2047 довольно дисфункциональны, что Джулиан пытается выяснить на уровне спецификации. Последовательная поддержка браузеров в далеком будущем.)

29 голосов
/ 25 января 2012

Обратите внимание, что в 2011 году RFC 6266 (особенно Приложение D) взвесили этот вопрос и дали конкретные рекомендации.

А именно, вы можете выдать filename только с символами ASCII, за которыми следует filename* с именем файла в формате RFC 5987 для тех агентов, которые его понимают.

Обычно это будет выглядеть как filename="my-resume.pdf"; filename*=UTF-8''My%20R%C3%A9sum%C3%A9.pdf, где имя файла Unicode («My Résumé.pdf») кодируется в UTF-8, а затем кодируется в процентах (обратите внимание, НЕ используйте + для пробелов).

Пожалуйста, прочитайте RFC 6266 и RFC 5987 (или используйте надежную и протестированную библиотеку, которая абстрагирует вас от этого), так как мое резюме здесь не хватает важных деталей.

5 голосов
/ 05 декабря 2018

С 2018 года решение теперь доступно в Django 2.1 (после томления в течение семи лет как открытый билет ). Вы можете использовать параметр as_attachment, встроенный в FileResponse . Например, чтобы вернуть файл output_file с типом mime output_mime_type в качестве ответа HTTP:

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

Или, если вы не можете использовать FileResponse, вы можете использовать соответствующую деталь из ее источника, чтобы напрямую изменить Content-Disposition. Вот как выглядит этот источник:

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)
4 голосов
/ 13 ноября 2017

Могу сказать, что мне удалось использовать более новый ( RFC 5987 ) формат указания заголовка, закодированного в форме электронной почты ( RFC 2231 ). Я придумал следующее решение, основанное на коде из проекта 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)

Я тестировал мой код только на Python 3.4 с Django 1.8 . Так что подобное решение в django-sendfile может подойти вам лучше.

В трекере Django есть долгосрочный билет , который это подтверждает, но патчи еще не были предложены. Так что, к сожалению, это настолько близко к использованию надежной проверенной библиотеки, насколько я мог найти, пожалуйста, дайте мне знать, если есть лучшее решение.

0 голосов
/ 30 июня 2010

Взломать:

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");
}
...