Хабрахабр

Использование UTF-8 в HTTP заголовках

1 — это текстовой протокол передачи данных. Как известно, HTTP 1. При этом в теле сообщений можно использовать другую кодировку, которая должна быть обозначена в заголовке «Content-Type». HTTP сообщения закодированы, используя ISO-8859-1 (которую условно можно считать расширенной версией ASCII, содержащей умляуты, диакритику и другие символы, используемые в западноевропейских языках). Наверное, самый распространенный кейс — это проставление имени файла в «Content-Disposition» заголовке. Но что делать, если нам необходимо задать non-ASCII символы не в теле сообщения, а в самих заголовках? Это, казалось бы, довольно распространенная задача, но ее реализация не так очевидна.

TL;DR: Используйте кодировку, описанную в RFC 6266, для «Content-Disposition» и преобразуйте текст в латиницу (транслит) в остальных случаях.

Небольшая вводная в кодировки

В статье упоминаются и используются кодировки US-ASCII (часто именуемую просто ASCII), ISO-8859-1 и UTF-8. Это небольшая вводная в эти кодировки. Раздел предназначен для разработчиков, которые редко или совсем не работают с кодировками и успели подзабыть их. Если вы к ним не относитесь, то смело пропускайте раздел.

ASCII — это простая кодировка, содержащая 128 символов и включающая весь английский алфавит, цифры, знаки препинания и служебные символы.

Слово «test» будет представлено в HEX представлении, как 0x74 0x65 0x73 0x74. 7 бит достаточно, чтобы представить любой ASCII символ. Первый бит у всех символов всегда 0, поскольку символов в кодировке 128, а байт предоставляет 2^8 = 256 вариантов.

Содержит французскую диакритику, немецкие умляуты и т.д. ISO-8859-1 — кодировка, предназначенная для западноевропейских языков.

Первая половина (128 символов) полностью совпадает с ASCII. Кодировка содержит 256 символов и, таким образом, может быть представлена одним байтом. Если 1, то это символ, специфичный для ISO-8859-1. Таким образом, если первый бит = 0, то это обычный ASCII символ.

Способна кодировать 1. UTF-8 — одна из самых известных кодировок наравне с ASCII. 064 символов. 112. Размер каждого символа варьируется от 1-го до 4-х байт (раньше допускались значения до 6 байт).

Если октет начинается с 0, то символ представлен одним байтом. Программа, работающая с этой кодировкой, определяет по первым битам, как много байтов входит в символ. 110 — два байта, 1110 — три байта, 11110 — 4 байта.

Поэтому тексты, использующие только ASCII символы, будут абсолютно идентичны в бинарном представлении, вне зависимости от того, использовалась ли для кодирования US-ASCII, ISO-8859-1 или UTF-8. Как и в случае с ISO-8859-1, первые 128 символов полностью соответствуют ASCII.

Использование UTF-8 в теле сообщения

Прежде чем перейти к заголовкам, давайте быстро взглянем, как использовать UTF-8 в теле сообщений. Для этого используется заголовок «Content-Type».

Браузер не должен пытаться отгадать кодировку и, тем более, игнорировать «Content-Type». Если «Content-Type» не задан, то браузер должен обрабатывать сообщения, как будто они написаны в ISO-8859-1. Например, Firefox сделает согласно спецификации и прочитает сообщение, будто оно было закодировано в ISO-8859-1. Но, что реально отобразится в ситуации, когда «Content-Type» не передан, зависит от реализации браузера. В любом случае, если сообщение было в UTF-8, то оно будет отображено некорректно. Google Chrome, напротив, будет использовать кодировку операционной системы, которая для многих российских пользователей равна Windows-1251.

Проставляем UTF-8 сообщение в значение заголовка

С телом сообщения все достаточно просто. Тело сообщения всегда следует после заголовков, поэтому здесь не возникает технических проблем. Но как быть с заголовками? В спецификации недвусмысленно заявляется, что порядок заголовков в сообщении не имеет значения. Т.е. задать кодировку в одном заголовке через другой заголовок не представляется возможным.

Мы видели, что такой трюк с телом сообщения приведет к тому, что значение будет просто прочитано в ISO-8859-1. Что будет, если просто взять и записать UTF-8 значение в значение заголовка? Но это не так. Логично было бы предположить, что то же самое произойдет с заголовком. Сюда включаются старые айфончики, IE11, Firefox, Google Chrome. Фактически, во многих, если не в большинстве, случаях такое решение будет работать. Единственным из находящихся у меня под рукой браузеров, когда я писал эту статью, который не захотел работать с таким заголовком, является Edge.

Возможно, разработчики браузеров решили облегчить жизнь разработчиков и автоматически определять, что в заголовках сообщение закодировано в UTF-8. Такое поведение не зафиксировано в спецификациях. Смотрим на первый бит: если 0, то ASCII, если 1 — то, возможно, UTF-8. В общем-то, это не является такой сложной задачей.

На самом деле, практически нет. Нет ли в этом случае пересечения с ISO-8859-1? Символ в бинарном представили будет иметь вид: 110xxxxx 10xxxxxx. Возьмем для примера UTF-8 символ из 2-х октетов (русские буквы представлены двумя октетами). В ISO-8859-1 этими символами едва ли можно закодировать что-то, несущее смысловую нагрузку. В HEX представлении: [0xC0-0x6F] [0x80-0xBF]. Поэтому риск того, что браузер неправильно расшифрует сообщение, очень мал.

Например, Apache Tomcat вместо всех UTF-8 символов проставляет 0x3F (вопросительный знак). Однако, при попытке использовать этот способ можно столкнуться с техническими проблемами: ваш веб-сервер или фреймворк может просто не разрешить записывать UTF-8 символы в значение заголовка. Разумеется, это ограничение можно обойти, но, если само приложение бьет по рукам и не дает что-то сделать, то, возможно, вам и не нужно это делать.

Это не задокументированное решение, которое в любой момент времени может перестать работать в браузерах. Но, независимо от того, разрешает ли вам ваш фреймворк или сервер записать UTF-8 сообщения в заголовок или нет, я не рекомендую этого делать.

Транслит

Я думаю, что использовать транслит — eto bolee horoshee reshenie. Многие крупные популярные русские ресурсы не брезгуют использовать транслит в названиях файлов. Это гарантированное решение, которое не сломается с выпуском новых браузеров и которое не надо тестировать отдельно на каждой платформе. Хотя, разумеется, надо подумать, как преобразовывать весь спектр возможных символов, что может быть не совсем тривиально. Например, если приложение рассчитано на российскую аудиторию, то в имя файла могут попасть татарские буквы ә и ң, которые надо как-то обработать, а не просто заменять на "?".

RFC 2047

Как я уже упомянул, томкат не позволил мне проставить UTF-8 в заголовке сообщения. Отражена ли эта особенность поведения в Java docs для сервлетов? Да, отражена:

Я пробовал кодировать сообщения, используя этот формат, — браузер меня не понял. Упоминается RFC 2047. Хотя работал раньше. Этот метод кодировки не работает в HTTP. Вот, например, тикет на удаление поддержки этой кодировки из Firefox.

RFC 6266

В тикете, ссылка на который содержится в предыдущем разделе, есть упоминания, что даже после прекращения поддержки RFC 2047, все еще есть способ передавать UTF-8 значения в названии скачиваемых файлов: RFC 6266. На мой взгляд, это самое правильно решение на сегодняшний день. Многие популярные интернет ресурсы используют его. Мы в CUBA Platform также используем именно этот RFC для генерации «Content-Disposition».

Сам способ кодировки подробно описан в другой спецификации — RFC 8187. RFC 6266 — это спецификация, описывающая использование “Content-Disposition” заголовка.

При наличии обоих атрибутов “filename” игнорируется во всех современных браузерах (включая IE11 и старые версии Safari). Параметр “filename” содержит название файла в ASCII, “filename*” — в любой необходимой кодировке. Совсем старые браузеры, напротив, игнорируют “filename*”.

Видимые символы из ASCII кодирования не требуют. При использовании данного способа кодирования в параметре сначала указывается кодировка, после '' идет закодированное значение. Остальные символы просто пишутся в hex представлении, со стоящим "%" перед каждым октетом.

Что делать с другими заголовками?

Кодирование, описанное в RFC 8187, не является универсальным. Да, можно поместить в заголовок параметр с * префиксом, и это, возможно, будет даже работать для некоторых браузеров, но спецификация предписывает не делать так.

Помимо «Content-Disposition» данная кодировка используется, например, в Web Linking и Digest Access Authentication. В каждом случае, где в заголовках поддерживается UTF-8, на настоящий момент есть явное упоминание об этом в релевантном RFC.

Использование описанной выше кодировки в HTTP было предложено лишь в 2010. Следует учесть, что стандарты в этой области постоянно меняются. Несмотря на то, что эти стандарты находятся лишь на стадии «Proposed Standard», они поддержаны повсеместно. Использование данной кодировки именно в «Content-Disposition» было зафиксировано в стандарте в 2011. Поэтому остается только следить за новостями в мире стандартов HTTP и уровня их поддержки на стороне браузеров. Вариант, что в будущем нас ожидают новые стандарты, которые позволят более унифицировано работать с различными кодировками в заголовках, не исключен.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»