Язык программирования Python.

Пакет email.

Модули пакета email помогут разобрать, изменить и сгенерировать сообщение в формате RFC 2822. Наиболее часто RFC 2822 применяется в сообщениях электронной почты в Интернете.

В пакете есть несколько модулей, назначение которых (кратко) указано ниже:

Message.

Модуль определяет класс Message — основной класс для представления сообщения в пакете email.

Parser.

Модуль для разбора представленного в виде текста сообщения с получением объектной структуры сообщения.

Header.

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

Generator.

Порождает текст сообщения RFC 2822 на основании объектной модели.

Utils.

Различные утилиты, которые решают разнообразные небольшие задачи, связанные с сообщениями.

В пакете есть и другие модули, которые здесь рассматриваться не будут.

Разбор сообщения. Класс Message.

Класс Message — центральный во всем пакете email. Он определяет методы для работы с сообщением, которое состоит из заголовка (header) и тела (payload). Поле заголовка имеет название и значение, разделенное двоеточием (двоеточие не входит ни в название, ни в значение). Названия полей нечувствительны к регистру букв при поиске значения, хотя хранятся с учетом регистра. В классе также определены методы для доступа к некоторым часто используемым сведениям (кодировке сообщения, типу содержимого и т.п.).

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

Пример наиболее употребительных методов экземпляров класса Message с пояснениями:

>>> import email.

>>> input_file = open("pr1.eml").

>>> msg = email.message_from_file(input_file).

Здесь используется функция email.message_from_file() для чтения сообщения из файла pr1.eml. Сообщение можно получить и из строки с помощью функции email.message_from_string(). А теперь следует произвести некоторые операции над этим сообщением (не стоит обращать внимания на странные имена — сообщение было взято из папки СПАМ). Доступ к полям по имени осуществляется так:

>>> print msg['from'].

"felton olive" <zinakinch@thecanadianteacher.com>

>>> msg.get_all('received').

['from mail.onego.ru\n\tby localhost with POP3 (fetchmail–6.2.5.

Polling mail.onego.ru account spam)\n\tfor spam@localhost.

(single–drop); Wed, 01 Sep 2004 15:46:33 +0400 (MSD)',

'from thecanadianteacher.com ([222.65.104.100])\n\tby mail.onego.ru.

(8.12.11/8.12.11) with SMTP id i817UtUN026093;\n\tWed, 1 Sep 2004.

11:30:58 +0400'].

Стоит заметить, что в электронном письме может быть несколько полей с именем received (в этом примере их два).

Некоторые важные данные можно получить в готовом виде, например, тип содержимого, кодировку:

>>> msg.get_content_type().

'text/plain'.

>>> print msg.get_main_type(), msg.get_subtype().

Text plain.

>>> print msg.get_charset().

None.

>>> print msg.get_params().

[('text/plain', ''), ('charset', 'us–ascii')].

>>> msg.is_multipart().

False.

Или список полей:

>>> print msg.keys().

['Received', 'Received', 'Message–ID', 'Date', 'From', 'User–Agent',

'MIME–Version', 'To', 'Subject', 'Content–Type',

'Content–Transfer–Encoding', 'Spam', 'X–Spam'].

Так как сообщение состоит из одной части, можно получить его тело в виде строки:

>>> print msg.get_payload().

Sorgeloosheid hullw ifesh nozama decompresssequenceframes.

Believe it or not, I have tried several sites to b»_«uy presription.

Medication. I should say that currently you are still be the best amony.

...

Теперь будет рассмотрен другой пример, в котором сообщение состоит из нескольких частей. Это сообщение порождено вирусом. Оно состоит из двух частей: HTML–текста и вложенного файла с расширением cpl. Для доступа к частям сообщения используется метод walk(), который обходит все его части. Попутно следует собрать типы содержимого (в списке parts), поля Content–Type (в ct_fields) и имена файлов (в filenames):

Import email.

Parts = [].

Ct_fields = [].

Filenames = [].

F = open("virus.eml").

Msg = email.message_from_file(f).

For submsg in msg.walk():

 parts.append(submsg.get_content_type()).

 ct_fields.append(submsg.get('Content–Type', '')).

 filenames.append(submsg.get_filename()).

 if submsg.get_filename():

  print "Длина файла:", len(submsg.get_payload()).

F.close().

Print parts.

Print ct_fields.

Print filenames.

В результате получилось:

Длина файла: 31173.

['multipart/mixed', 'text/html', 'application/octet–stream'].

['multipart/mixed;\n boundary="--------hidejpxkblmvuwfplzue"',

'text/html; charset="us–ascii"',

'application/octet–stream; name="price.cpl"'].

[None, None, 'price.cpl'].

Из списка parts можно увидеть, что само сообщение имеет тип multipart/mixed, тогда как две его части — text/html и application/octet–stream соответственно. Только с последней частью связано имя файла ( price.cpl). Файл читается методом get_payload() и вычисляется его длина.

Кстати, в случае, когда сообщение является контейнером для других частей, get_payload() выдает список объектов–сообщений (то есть экземпляров класса Message).

Формирование сообщения.

Часто возникает ситуация, когда нужно сформировать сообщение с вложенным файлом. В следующем примере строится сообщение с текстом и вложением. В качестве класса для порождения сообщения можно использовать не только Message из модуля email.Message, но и MIMEMultipart из email.MIMEMultipart (для сообщений из нескольких частей), MIMEImage (для сообщения с графическим изображением), MIMEAudio (для аудиофайлов), MIMEText (для текстовых частей):

# Загружаются необходимые модули и функции из модулей.

From email.Header import make_header as mkh.

From email.MIMEMultipart import MIMEMultipart.

From email.MIMEText import MIMEText.

From email.MIMEBase import MIMEBase.

From email.Encoders import encode_base64.

# Создается главное сообщение и задаются некоторые поля.

Msg = MIMEMultipart().

Msg["Subject"] = mkh([("Привет", "koi8–r")]).

Msg["From"] = mkh([("Друг", "koi8–r"), ("<friend@mail.ru>", "us–ascii")]).

Msg["To"] = mkh([("Друг2", "koi8–r"), ("<friend2@yandex.ru>", "us–ascii")]).

# То, чего будет не видно, если почтовая программа поддерживает MIME.

Msg.preamble = "Multipart message".

Msg.epilogue = "".

# Текстовая часть сообщения.

Text = u"""К письму приложен файл с архивом.""".encode("koi8–r").

To_attach = MIMEText(text, _charset="koi8–r").

Msg.attach(to_attach).

# Прикладывается файл.

Fp = open("archive_file.zip", "rb").

To_attach = MIMEBase("application", "octet–stream").

To_attach.set_payload(fp.read()).

Encode_base64(to_attach).

To_attach.add_header("Content–Disposition", "attachment",

 filename="archive_file.zip").

Fp.close().

Msg.attach(to_attach).

Print msg.as_string().

В этом примере видно сразу несколько модулей пакета email. Функция make_header() из email.Header позволяет закодировать содержимое для заголовка:

>>> from email.Header import make_header.

>>> print make_header([("Друг", "koi8–r"), ("<friend@mail.ru>", "us–ascii")]).

=?koi8–r?b?5NLVxw==?= <friend@mail.ru>

>>> print make_header([(u"Друг", ""), ("<friend@mail.ru>", "us–ascii")]).

=?utf–8?b?w6TDksOVw4c=?= <friend@mail.ru>

Функция email.Encoders.encode_base64() воздействует на переданное ей сообщение и кодирует тело с помощью base64. Другие варианты: encode_quopri() — кодировать quoted printable, encode_7or8bit() — оставить семь или восемь бит. Эти функции добавляют необходимые поля.

Аргументы конструкторов классов из MIME–модулей пакета email:

Class MIMEBase(_maintype, _subtype, **_params).

Базовый класс для всех использующих MIME сообщений (подклассов Message). Тип содержимого задается через _maintype и _subtype.

Class MIMENonMultipart().

Подкласс для MIMEBase, в котором запрещен метод attach(), отчего он гарантированно состоит из одной части.

Class MIMEMultipart([_subtype[, boundary[, _subparts[, _params]]]]).

Подкласс для MIMEBase, который является базовым для MIME–сообщений из нескольких частей. Главный тип multipart, подтип указывается с помощью _subtype.

Class MIMEAudio(_audiodata[, _subtype[, _encoder[, **_params]]]).

Подкласс MIMENonMultipart. Используется для создания MIME–сообщений, содержащих аудио данные. Главный тип — audio, подтип указывается с помощью _subtype. Данные задаются параметром _audiodata.

Class MIMEImage(_imagedata[, _subtype[, _encoder[, **_params]]]).

Подкласс MIMENonMultipart. Используется для создания MIME–сообщений с графическим изображением. Главный тип — image, подтип указывается с помощью _subtype. Данные задаются параметром _imagedata.

Class MIMEMessage(_msg[, _subtype]).

Подкласс MIMENonMultipart для класса MIMENonMultipart используется для создания MIME–объектов с главным типом message. Параметр _msg применяется в качестве тела и должен являться экземпляром класса Message или его потомков. Подтип задается с помощью _subtype, по умолчанию 'rfc822'.

Class MIMEText(_text[, _subtype[, _charset]]).

Подкласс MIMENonMultipart. Используется для создания MIME–сообщений текстового типа. Главный тип — text, подтип указывается с помощью _subtype. Данные задаются параметром _text. Посредством _charset можно указать кодировку (по умолчанию 'us–ascii').

Разбор поля заголовка.

В примере выше поле Subject формировалось с помощью email.Header.make_header(). Разбор поля поможет провести другая функция: email.Header.decode_header(). Эта функция возвращает список кортежей, в каждом из них указан кусочек текста поля и кодировка, в которой этот текст был задан. Следующий пример поможет понять суть дела:

Subj = """=?koi8–r?Q?=FC=D4=CF_=D0=D2=C9=CD=C5=D2_=CF=DE=C5=CE=D8_=C4=CC=C9?=

 =?koi8–r?Q?=CE=CE=CF=C7=CF_=28164_bytes=29_=D0=CF=CC=D1_=D3_=D4?=

  =?koi8–r?Q?=C5=CD=CF=CA_=D3=CF=CF=C2=DD=C5=CE=C9=D1=2E_=EF=CE=CF_?=

 =?koi8–r?Q?=D2=C1=DA=C2=C9=CC=CF=D3=D8_=CE=C1_=CB=D5=D3=CB=C9_=D7?=

 =?koi8–r?Q?_=D3=CF=CF=C2=DD=C5=CE=C9=C9=2C_=CE=CF_=CC=C5=C7=CB=CF?=

 =?koi8–r?Q?_=D3=CF=C2=C9=D2=C1=C5=D4=D3=D1_=D7_=D4=C5=CB=D3=D4_?=

 =?koi8–r?Q?=D3_=D0=CF=CD=CF=DD=D8=C0_email=2EHeader=2Edecode=5Fheader?=

 =?koi8–r?Q?=28=29?=""".

Import email.Header.

For text, enc in email.Header.decode_header(subj):

 print enc, text.

В результате будет выведено:

Koi8–r Это пример очень длинного (164 bytes) поля с темой сообщения.

Оно разбилось на куски в сообщении, но легко собирается в текст.

С помощью email.Header.decode_header().

Следует заметить, что кодировку можно не указывать:

>>> email.Header.decode_header("simple text").

[('simple text', None)].

>>> email.Header.decode_header("пример").

[('\xd0\xd2\xc9\xcd\xc5\xd2', None)].

>>> email.Header.decode_header("=?KOI8–R?Q?=D0=D2=CF_?=Linux").

[('\xd0\xd2\xcf ', 'koi8–r'), ('Linux', None)].

Если в первом случае можно подразумевать us–ascii, то во втором случае о кодировке придется догадываться: вот почему в электронных письмах нельзя просто так использовать восьмибитные кодировки. В третьем примере русские буквы закодированы, а латинские — нет, поэтому в результате email.Header.decode_header() список из двух пар.

В общем случае представить поле сообщения можно только в Unicode. Создание функции для такого преобразования предлагается в качестве упражнения.