Примечание к предыдущему разделу: в заголовках сообщений, содержащих адре са элек трон ной поч ты (такие как «From»), компонент «имя» в паре имя/адрес также может кодироваться подобным образом. Поскольку пакет email при анализе ожидает, что кодированные подстроки будут завершаться пробельным символом или простираться до конца строки, мы не можем выполнить декодирование всего заголовка с адресом — кавычки вокруг компонента с именем будут вызывать неудачу.
Для поддержки таких интернационализированных заголовков с адресами необходимо извлечь первую часть адреса электронной почты и затем декодировать ее. Прежде всего, необходимо с помощью инструментов в пакете email извлечь имя и адрес:
> >> from email.utils import parseaddr, formataddr
> >> p = parseaddr(‘"Smith, Bob" <bob@bob.com>’) # разбить пару имя/адрес
> >> p # адрес — некодированный
(‘Smith, Bob’, ‘bob@bob.com’)
> >> formataddr(p)
‘"Smith, Bob" <bob@bob.com>’
> >> parseaddr(‘Bob Smith <bob@bob.com>’) # имя без кавычек
(‘Bob Smith’, ‘bob@bob.com’)
> >> formataddr(parseaddr(‘Bob Smith <bob@bob.com>’))
‘Bob Smith <bob@bob.com>’
> >> parseaddr(‘bob@bob.com‘) # простой адрес, без имени
(», ‘bob@bob.com’)
> >> formataddr(parseaddr(‘bob@bob.com’))
‘bob@bob.com‘
В заголовках с несколькими адресами (например, «To») адреса отделяются друг от друга запятыми. Так как имена в адресах также могут содержать запятые, простое разбиение по запятым не всегда дает желаемый результат. Однако для анализа отдельных адресов можно использовать еще одну утилиту: функция getaddresses игнорирует запятые в именах и разбивает содержимое заголовка на отдельные адреса. Функция parseaddr делает то же самое, потому что она просто возвращает первую пару из результата, полученного вызовом функции getaddresses (в следующем примере кое-где были добавлены пустые строки для большей удобочитаемости):
> >> from email.utils import getaddresses
> >> multi = ‘"Smith, Bob" <bob@bob.com>, Bob Smith <bob@bob.com>, bob@bob.com, "Bob" <bob@bob.com>’
> >> getaddresses([multi])
[(‘Smith, Bob’,’bob@bob.com’),(‘Bob Smith’,’bob@bob.com’),(»,’bob@bob.com’), (‘Bob’, ‘bob@bob.com’)]
> >> [formataddr(pair) for pair in getaddresses([multi])]
[‘"Smith, Bob" <bob@bob.com>’, ‘Bob Smith <bob@bob.com>’, ‘bob@bob.com’, ‘Bob <bob@bob.com>’]
>>> ‘, ‘.join([formataddr(pair) for pair in getaddresses([multi])]) ‘"Smith, Bob" <bob@bob.com>, Bob Smith <bob@bob.com>, bob@bob.com, Bob <bob@bob.com>’
>>> getaddresses([‘bob@bob.com‘]) # также корректно обрабатывает простые (», ‘bob@bob.com‘)] # адреса без имени
Итак, декодирование адресов электронной почты включает всего лишь один шаг перед применением обычной логики декодирования заголовков, которую мы видели выше, и один шаг после:
>>> rawfromheader = ‘"=?UTF-8?Q?Walmart?=" <newsletters@walmart.com>’
>>> from email.utils import parseaddr, formataddr
>>> from email.header import decode_header
>>> name, addr = parseaddr(rawfromheader) # разбить на части имя/адрес
>>> name, addr
(‘=?UTF-8?Q?Walmart?=’, ‘newsletters@walmart.com’)
>>> abytes, aenc = decode_header(name)[0] # выполнить
# декодирование email+MIME
>>> abytes, aenc
(b’Walmart’, ‘utf-8’)
>>> name = abytes.decode(aenc) # декодировать в Юникод
>>> name
‘Walmart’
>>> formataddr((name, addr)) # объединить компоненты адреса
‘Walmart <newsletters@walmart.com>’
Заголовки «From» обычно содержат только один адрес, однако для большей надежности необходимо применять этот прием ко всем заголовкам с адресами, таким как «To», «Cc» и «Bcc». Напомню, что утилита getaddresses корректно распознает запятые внутри имен и не путает их с запятыми, отделяющими разные адреса, а так как она корректно обрабатывает случай единственного адреса, ее можно применять для обработки заголовков «From»:
>>> rawfromheader = ‘"=?UTF-8?Q?Walmart?=" <newsletters@walmart.com>’
>>> rawtoheader = rawfromheader + ‘, ‘ + rawfromheader
>>> rawtoheader
‘"=?UTF-8?Q?Walmart?=" <newsletters@walmart.com>, "=?UTF-8?Q?Walmart?=" <newsletters@walmart.com>’
>>> pairs = getaddresses([rawtoheader])
>>> pairs
[(‘=?UTF-8?Q?Walmart?=’, ‘newsletters@walmart.com’), (‘=?UTF-8?Q?Walmart?=’, ‘newsletters@walmart.com’)]
>>> addrs = []
>>> for name, addr in pairs:
… abytes, aenc = decode_header(name)[0] # декодирование email+MIME
… name = abytes.decode(aenc) # декодирование в Юникод
… addrs.append(formataddr((name, addr))) # один или более адресов
>>> ‘, ‘.join(addrs)
‘Walmart <newsletters@walmart.com>, Walmart <newsletters@walmart.com>’
Эти инструменты в целом могут принимать некодированное содержимое и возвращают его нетронутым. Однако для большей надежности в последней части примера выше необходимо также учесть, что decode_ header может возвращать несколько частей (для кодированных подстрок), значение None в качестве имен кодировок (для некодированных подстрок) и подстроки типа str вместо bytes (для полностью некодиро- ванных имен).
В данном алгоритме предусматриваются обе разновидности декодирования полученных почтовых сообщений — из формата MIME и в текст Юникода. Создание корректно закодированных заголовков для включения в новые почтовые сообщения выполняется так же просто:
>>> from email.header import make_header
>>> hdr = make_header([(b’A\xc4B\xe4C’, ‘latin-1’)])
>>> print(hdr)
A.A.B.a.C
>>> print(hdr.encode())
=?iso-8859-1?q?A=C4B=E4C?=
>>> decode_header(hdr.encode())
[(b’A\xc4B\xe4C’, ‘iso-8859-1’)]
Этот прием может применяться как к целым заголовкам, таким как «Subject», так и к отдельным их компонентам, например к именам в заголовках с адресами электронной почты, таких как «From» и «To» (при необходимости используйте getaddresses, чтобы разбить содержимое заголовка на отдельные адреса). Объект заголовка предоставляет альтернативный интерфейс — оба приема учитывают дополнительные детали, такие как длина строк, за которыми я отсылаю вас к руководствам по языку Python:
>>> from email.header import Header
>>> h = Header(b’A\xe4B\xc4X’, charset=’latin-1′)
>>> h.encode()
‘=?iso-8859-1?q?A=E4B=C4X?=’
>>>
>>> h = Header(‘spam’, charset=’ascii’) # то же, что и Header(‘spam’)
>>> h.encode()
‘spam‘
Пакет mailtools, описываемый далее, и его клиент PyMailGUI, который будет представлен в главе 14, используют эти интерфейсы для автоматического декодирования заголовков полученных почтовых сообщений в отображаемое содержимое и для кодирования заголовков отправляемых сообщений, которые содержат символы не из диапазона ASCII. Кроме того, в последнем случае кодирование применяется также к компоненту имени в адресах электронной почты, и предполагается, что серверы SMTP правильно воспринимают такие адреса. На некоторых серверах SMTP это может приводить к проблемам, решение которых мы не имеем возможности описать из-за нехватки места в книге. Дополнительные сведения по обработке заголовков на серверах SMTP ищите в Интернете. Кроме того, дополнительную информацию о декодировании заголовков можно найти в файле _test-i18n-headers.py в пакете с примерами. В нем с помощью методов mailtools реализовано декодирование дополнительных заголовков, имеющих отношение к теме сообщения и к адресам, и их отображение в виджете Text из библиотеки tkinter — демонстрируя, как они будут отображаться в PyMailGUI.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011