Как закодировать имя файла UTF8 для заголовков HTTP? (Питон, Джанго)

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

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

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

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

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


person Chris Ciesielski    schedule 01.09.2009    source источник
comment
Я понимаю, что опоздал на несколько лет, но... исключение KeyError действительно беспокоит меня. Я не просто имею в виду, что время от времени я сталкиваюсь с этой проблемой, я имею в виду, что несколько лет назад я отправил патч в Python, чтобы исправить это, некоторое время спорил, а затем решил, что они не хотят менять Python 2. Я исправил эта проблема в Python 3, но они никогда не принимали мой патч в Python 2. Обходной путь заключается в том, чтобы сначала .encode('utf-8') , а затем использовать urllib.quote. Но это для URL-кодирования, которое не является стандартным способом поместить их в заголовки.   -  person mgiuca    schedule 11.04.2011


Ответы (6)


Это часто задаваемые вопросы.

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

См. тестовые примеры по адресу http://greenbytes.de/tech/tc2231/.

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

person Julian Reschke    schedule 01.09.2009
comment
Спасибо! Самые простые вещи труднее всего найти ;) - person Chris Ciesielski; 01.09.2009
comment
Совсем недавно Джулиан составил для этой цели профиль RFC2231: http://datatracker.ietf.org/doc/draft-reschke-rfc2231-in-http/ - person Mark Nottingham; 09.05.2010
comment
Применяется ли это для поддержки multipart/form-data, потому что сейчас я вижу необработанные байты UTF-8, отправленные в параметре «имя файла» при загрузке файла из формы в Chrome - person Jaime Hablutzel; 08.03.2013
comment
Джейме: Нет. См. ‹greenbytes.de/tech/ webdav/rfc6266.html#rfc.section.1.p.4›. - person Julian Reschke; 08.03.2013
comment
RFC 5987 устарел из-за RFC 8187. - person Jules Sam. Randolph; 20.08.2018
comment
См. мой ответ для Django 2.1+ с использованием FileResponse, который теперь является частью Django. - person Mark Chackerian; 20.12.2018

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

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

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

person bobince    schedule 01.09.2009
comment
Верхний ответ содержит полезную информацию, но на самом деле вы решили проблему. Спасибо! - person Brandon Bloom; 15.02.2010
comment
Поскольку этот ответ вышел, был выпущен RFC по этой теме. Следует отметить конструкцию filename*=, которую поддерживают только более новые браузеры и которая гарантированно позволяет использовать UTF-8, закодированную как в RFC 5987. tools.ietf.org/html/rfc6266#appendix-D - person Alan H.; 25.01.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 (или используйте надежную и проверенную библиотеку, которая абстрагирует это для вас), так как в моем резюме здесь отсутствуют важные детали.

person Alan H.    schedule 25.01.2012
comment
Это то, что мне нужно для конечной точки загрузки файла в моем проекте Django. Спасибо! - person macguru2000; 04.02.2016

Начиная с Django 2.1 (см. выпуск #16470), вы можете используйте FileResponse, который правильно установит Content-Disposition заголовок для вложений. Начиная с Django 3.0 (выпуск #30196), он также устанавливает это правильно для inline файлов.

Например, чтобы вернуть файл с именем my_img.jpg с типом MIME image/jpeg в качестве ответа HTTP:

response = FileResponse(open("my_img.jpg", 'rb'), as_attachment=True, content_type="image/jpeg")
return response

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

from urllib.parse import quote

disposition = 'attachment' if as_attachment else 'inline'
try:
    filename.encode('ascii')
    file_expr = 'filename="{}"'.format(filename)
except UnicodeEncodeError:
    file_expr = "filename*=utf-8''{}".format(quote(filename))
response.headers['Content-Disposition'] = '{}; {}'.format(disposition, file_expr)
person Mark Chackerian    schedule 05.12.2018
comment
ПРИМЕЧАНИЕ: если as_attachment=False (если Content-Disposition равно inline) он недоступен ни в версии Django 2.1, ни в версии Django 2.2, теперь (21.05.2019) он находится в Django dev, поэтому для inline я использую ручную версию. - person don_vanchos; 21.05.2019
comment
Дополнительную информацию о комментарии @don_vanchos см. в Django issue #30196. - person Boris; 23.11.2020

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

person Will S    schedule 13.11.2017
comment
Потрясающий! То что надо! - person Quazer; 29.06.2018

Взлом:

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");
}
person anon    schedule 29.06.2010
comment
Обнюхивание пользовательского агента вообще воняет, эти глючные серверы используют его и несут ответственность за множество тестов tc2231/rfc6266. - person Tobu; 28.01.2012