Декодирование заголовков RFC 2231

Попытка адресовать эту проблему, я пытаюсь обернуть голову вокруг различных функций стандартной библиотеки Python, направленных на поддержку RFC 2231. Основная цель этого RFC представляется в три раза: позволяет кодирование без ASCII в параметрах заголовка, отмечая язык заданного значения и позволяя параметрам заголовка охватывать несколько строк. Библиотека email.util предоставляет несколько функций для решения различных аспектов этого. Насколько я могу судить, они работают следующим образом:

decode_rfc2231 только разделяет значение такого параметра на его части, например:

>>> email.utils.decode_rfc2231("utf-8''T%C3%A4st.txt")
['utf-8', '', 'T%C3%A4st.txt']

decode_params заботится об обнаружении параметров, закодированных RFC2231. Он собирает части, которые принадлежат друг другу, а также декодирует строку с кодировкой url в байтовую последовательность. Эта последовательность байтов, однако, затем кодируется как latin1. И все значения заключены в кавычки. Кроме того, существует некоторая специальная обработка для первого аргумента, которая по-прежнему должна быть кортежем из двух элементов, но эти два передаются без результата.

>>> email.utils.decode_params([
...   (1,2),
...   ("foo","bar"),
...   ("name*","utf-8''T%C3%A4st.txt"),
...   ("baz*0","two"),("baz*1","-part")])
[(1, 2), ('foo', '"bar"'), ('baz', '"two-part"'), ('name', ('utf-8', '', '"Täst.txt"'))]

collapse_rfc2231_value можно использовать для преобразования этой тройки кодировки, языка и последовательности байтов в правильную строку юникода. Однако меня смущает тот факт, что если вход был такой тройной, то кавычки будут перенесены на выход. Если, с другой стороны, вход был одиночной кавычкой, то эти кавычки будут удалены.

>>> [(k, email.utils.collapse_rfc2231_value(v)) for k, v in
...  email.utils.decode_params([
...   (1,2),
...   ("foo","bar"),
...   ("name*","utf-8''T%C3%A4st.txt"),
...   ("baz*0","two"),("baz*1","-part")])[1:]]
[('foo', 'bar'), ('baz', 'two-part'), ('name', '"Täst.txt"')]

Итак, кажется, что для использования всего этого механизма я должен добавить еще один шаг, чтобы исключить третий элемент любого кортежа, с которым я столкнулся. Это правда, или я пропустил какой-то момент здесь? Мне пришлось выяснить много из вышеперечисленного с помощью исходного кода, так как документы немного расплывчаты в деталях. Я не могу представить, что могло бы занять этот избирательный случай. Есть ли смысл?

Как лучше всего использовать эти функции?

Лучшее, что я нашел до сих пор, это email.message.Message реализация. Там процесс выглядит примерно так, как описано выше, но каждое поле получает без кавычек через _unquotevalue после decode_params и только get_filename и get_boundary сворачивают свои значения, все остальные возвращают кортеж. Надеюсь, что есть что-то более полезное.

Ответ 1

В настоящее время функции из email.utils используются редко в email.message. Большинство пользователей предпочитают использовать email.message.Message напрямую. Там даже несколько старый отчет о выпуске о добавлении модульных тестов (которые, безусловно, можно использовать в качестве примеров) для Python, даже если я не уверен в как это относится к email.util.

Короткий пример, который я нашел, это этот blogpost, который, однако, не содержит более одного раза предложение и несколько SLOC информации о RFC2231 разбор. Однако автор отмечает, что многие MTA используют RFC2047. В зависимости от вашего usecase это также может быть проблемой.

Судя по нескольким примерам, которые можно найти, я предполагаю, что ваш способ разбора с использованием email.util - единственный способ пойти, даже если понимание длинного списка несколько уродливое.

Из-за отсутствия примеров в некотором отношении может быть разумным написать новый парсер RFC2231 (если вам действительно нужна лучшая, может быть, более быстрая или более красивая база кода). Новая версия может быть основана на существующих реализациях, таких как Dovecot RFC2231 parser по соображениям совместимости (вы даже можете использовать Dovecot unit test. Поскольку код C кажется мне довольно сложным, и поскольку я не могу найти реализацию python, кроме email.util и Python2 backports email.util, задача портирования на Python будет непросто (обратите внимание, что Dovecot LGPL-лицензированный, что может быть проблемой в вашем проекте)

Я думаю, что API email.util RFC2231 не был разработан для простого автономного использования, а скорее как куча полезных методов для использования в email.message.Message.

Ответ 2

Старый вопрос, но я не смог найти полный ответ, который работает над этим. Так вот что я сделал (на Python 2.7):

def decode_rfc2231_header(header):
    """Decode a RFC 2231 header"""
    # Remove any quotes
    header = email.utils.unquote(header)
    encoding, language, value = email.utils.decode_rfc2231(header)
    value = urllib.unquote(value)
    return email.utils.collapse_rfc2231_value((encoding, language, value))

Например:

>>> name = u'èéêëēėęûüùúūàáâäæãåāāîïíīįì test ôöòóœøōõssśšłžźżçćčñń'
>>> encoded_header = email.utils.encode_rfc2231(name.encode("utf8"), 'utf8', 'en')
>>> print encoded_header 
utf8'en'%C3%A8%C3%A9%C3%AA%C3%AB%C4%93%C4%97%C4%99%C3%BB%C3%BC%C3%B9%C3%BA%C5%AB%C3%A0%C3%A1%C3%A2%C3%A4%C3%A6%C3%A3%C3%A5%C4%81%C4%81%C3%AE%C3%AF%C3%AD%C4%AB%C4%AF%C3%AC%20test%20%C3%B4%C3%B6%C3%B2%C3%B3%C5%93%C3%B8%C5%8D%C3%B5ss%C5%9B%C5%A1%C5%82%C5%BE%C5%BA%C5%BC%C3%A7%C4%87%C4%8D%C3%B1%C5%84
>>> print decode_rfc2231_header(encoded_header)
èéêëēėęûüùúūàáâäæãåāāîïíīįì test ôöòóœøōõssśšłžźżçćčñń