Как кодировать имя файла UTF8 для заголовков HTTP? (Python, Django)
У меня проблема с HTTP-заголовками, они закодированы в ASCII, и я хочу предоставить представление для загрузки файлов, имена которых могут быть не ASCII.
response['Content-Disposition'] = 'attachment; filename="%s"' % (vo.filename.encode("ASCII","replace"), )
Я не хочу использовать статические файлы для одной и той же проблемы с именами файлов без ASCII, но в этом случае возникнет проблема с файловой системой и кодировкой имени файла. (Я не знаю цели.)
Я уже пробовал urllib.quote(), но вызывает исключение KeyError.
Возможно, я делаю что-то неправильно, но, возможно, это невозможно.
Ответы
Ответ 1
Это 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).
Ответ 2
Не отправляйте имя файла в Content-Disposition. Невозможно настроить кросс-браузер (*) параметры не-ASCII-заголовка.
Вместо этого отправьте только "Content-Disposition: attachment" и оставьте имя файла в виде строки UTF-8, закодированной в URL-адресе в концевой (PATH_INFO) части вашего URL-адреса, чтобы браузер мог выбирать и использовать по умолчанию. URL-адреса UTF-8 обрабатываются гораздо более надежно браузерами, чем что-либо, что связано с Content-Disposition.
(*: на самом деле, нет даже текущего стандарта, который говорит, как это должно быть сделано, поскольку отношения между RFC 2616, 2231 и 2047 довольно дисфункциональны, то, что Джулиан пытается прояснить на уровне спецификации. поддержка браузера в отдаленном будущем.)
Ответ 3
Обратите внимание, что в 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 (или используйте надежную и протестированную библиотеку, которая абстрагирует это для вас), так как мое резюме здесь не содержит важных деталей.
Ответ 4
Я могу сказать, что у меня был успех с использованием нового (RFC 5987) формата указания заголовка, закодированного с помощью электронной почты form (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 может вам лучше поменять.
Там есть длинный билет в джэкго-трекере, который подтверждает это, но патчи еще не были предложены afaict. К сожалению, это так близко к использованию надежной проверенной библиотеки, как я мог найти, пожалуйста, дайте мне знать, если есть лучшее решение.
Ответ 5
Начиная с 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)
Ответ 6
Взлом:
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");
}