angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

IMAPClient и уплощение BODYSTRUCTURE

Уплощение BODYSTRUCTURE требует, чтобы вы начали читать RFC3501.

27 сентября 2021 Обновленный 27 сентября 2021
В Email
post main image
https://unsplash.com/@2hmedia

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

Вся идея IMAP заключается в том, чтобы получить только то, что вы запрашиваете. Предположим, у вас есть письмо с большим количеством вложений, но вы хотите просмотреть или скачать только одно из них. Для этого вам нужен 'body_number' этого вложения, а затем FETCH этой части.

В интернете можно увидеть людей, которые скачивают все сообщение целиком, но это не тот способ, которым можно это сделать! Здесь я представляю решение для получения body_numbers всех частей сообщения электронной почты.

Сглаживание BODYSTRUCTURE

Я сам боролся с этим, и для начала я использовал некоторый код, который нашел в интернете, но пользы от него было мало. Пора начать читать RFC3501, смотрите ссылки ниже. Сообщение электронной почты IMAP состоит не только из вложений, таких как файлы images и PDF , но и сами вложения могут быть сообщениями, что означает, что наш код должен использовать рекурсию.

Мне нужен список body_numbers , который можно использовать для FETCH нужной нам части (частей). Мы можем назвать эту операцию "сглаживание BODYSTRUCTURE", потому что мы переходим от рекурсии к списку. В ходе этого процесса мы создаем body_numbers.

MULTIPART/ALTERNATIVE, MULTIPART/MIXED, MULTIPART/RELATED, ...

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

  • ALTERNATIVE: альтернативные части имеют одинаковое содержание, так что почтовый клиент может выбрать, какую из них показать.
  • RELATED: части должны быть представлены вместе, а не альтернативно. В результате они объединяются. например, встроенное изображение
  • MIXED: части содержат разную информацию и не должны быть показаны вместе.

Документ 'IMAP BODYSTRUCTURE: formatted examples' дает хорошее введение, см. ссылки ниже.

Вот пример отношения ALTERNATIVE . BODYSTRUCTURE , возвращаемый IMAPClient , является:

(
    [
        (b'text', b'plain', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 426, 15, None, None, None, None), 
        (b'text', b'html', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 1085, 36, None, None, None, None)
    ], 
    b'alternative', 
    (b'boundary', b'_000_CWXP265MB4244C3FA1F3563A988AAE2CABBDF9CWXP265MB4244GBRP_'), 
    None, 
    (b'en-US',), 
    None
)

IMAPClient дает нам список элементов ALTERNATIVE . Мы можем выбрать показать либо обычный текст, либо часть HTML . Сплющенный body_parts - это, первое число - body_number:

1        - ALTERNATIVE : TEXT, text/plain, iso-8859-1
2        - ALTERNATIVE : TEXT, text/html, iso-8859-1

Вот тот же пример с добавлением вложения PDF :

(
    [
        (
            [
                (b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'8bit', 1268, 25, None, (b'inline', None), None, None), 
                (b'text', b'html', (b'charset', b'UTF-8'), None, None, b'8bit', 10887, 115, None, (b'inline', None), None, None)
            ], 
            b'alternative', 
            (b'boundary', b'alt-60e413d9174229.65052373'), 
            None,
            None,
            None
        ), 
        (b'application', b'pdf', (b'name', b'summary.pdf'), None, None, b'base64', 187354, None, 
            (b'attachment', (b'filename', b'summary.pdf')), None, None)
    ], 
    b'mixed', 
    (b'boundary', b'multipart-60e413d9174190.84932644'), 
    None, 
    None, 
    None
)

Сплющенные body_parts , первый номер - body_number:

1.1      - ALTERNATIVE : TEXT, text/plain, utf-8
1.2      - ALTERNATIVE : TEXT, text/html, utf-8
2        - MIXED       : NON_MULTIPART-ATTACHMENT, application/pdf, summary.pdf

Откуда берутся body_numbers ?

Мы не получаем body_numbers с сервера IMAP . Вместо этого мы получаем BODYSTRUCTURE с сервера IMAP и должны сгенерировать из него body_numbers . В приведенных выше примерах это несложно. Но с вложенными сообщениями все становится сложнее.

Структура BODYSTRUCTURE

Чтобы понять структуру BODYSTRUCTURE , я скопировал часть текста из RFC3501:

Основные поля многостороннего body part расположены в следующем порядке:

  • body type
    Строка с именем типа носителя содержимого, как определено в [MIME-IMB].
  • body subtype
    Строка с именем подтипа содержимого, как определено в [MIME-IMB].

  • body parameter parenthesized list
    Список пар атрибутов/значений в круглых скобках [например, ("foo" "bar" "baz" "rag"), где "bar" - значение "foo", а "rag" - значение "baz"], как определено в [MIME-IMB].

  • body id
    Строка, дающая идентификатор содержимого, как определено в [MIME-IMB].

  • body description
    Строка, указывающая содержимое , как определено в [MIME-IMB].

  • body encoding
    Строка с кодировкой передачи содержимого, как определено в [MIME-IMB].

  • body size
    Число, указывающее размер тела в октетах. Обратите внимание, что этот размер - размер в кодировке передачи, а не результирующий размер после декодирования.

body type типа MESSAGE и подтипа RFC822 содержит, сразу после основных полей, структуру конверта, структуру тела и размер в текстовых строках инкапсулированного сообщения.

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

Данные расширения следуют за основными полями и полями, специфичными для типа, перечисленными выше. Данные расширения никогда не возвращаются при выборке BODY, но могут быть возвращены при выборке BODYSTRUCTURE . Данные расширения, если они присутствуют, ДОЛЖНЫ быть в определенном порядке.

Обратите внимание, что body type типа MESSAGE и подтипа RFC822 вводит рекурсию. Когда мы сталкиваемся с этим, мы обрабатываем это сообщение как новое сообщение, извлекаем body_number и т.д. И это сообщение снова может содержать одно или несколько других сообщений.

Согласно RFC3501, каждое сообщение содержит структуру конверта и структуру тела. Структура тела сообщения - это новая структура тела, которую мы должны обработать.

Пример вложенных сообщений

Вот пример сообщения, которое содержит другое сообщение, которое содержит другое сообщение с вложением. BODYSTRUCTURE , возвращаемый IMAPClient , является:

(
    [
        (b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 35, 7, None, None, None, None), 
        (b'message', b'rfc822', (b'name', b'Re: My message.eml'), None, None, b'7bit', 10034116, 
            (
                b'Sun, 19 Sep 2021 10:04:43 +0200', 
                b'Re: My message', 
                ((b'Bob Smith', None, b'bobsmith', b'example.com'),), 
                ((b'Bob Smith', None, b'bobsmith', b'example.com'),), 
                ((b'Bob Smith', None, b'bobsmith', b'example.com'),), 
                ((b'richardroe@example.org', None, b'richardroe', b'example.org'),), 
                None, 
                None, 
                None, 
                b'<8b678e28-d03a-2bdd-2930-12470235ef9a@example.com>'
            ), 
            (
                (b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 46, 7, None, None, None, None), 
                (b'message', b'rfc822', (b'name', b'Fw: Some email two.eml'), None, None, b'7bit', 10029135, 
                    (
                        b'Sat, 18 Sep 2021 18:35:47 +0200', 
                        b'Fw: Some email two', 
                        ((b'John Doe', None, b'johndoe', b'example.org'),), 
                        ((b'John Doe', None, b'johndoe', b'example.org'),), 
                        ((b'John Doe', None, b'johndoe', b'example.org'),), 
                        ((None, None, b'richardroe', b'example.org'),), 
                        None,
                        None,
                        None,
                        b'<a7dfbf41-1a26-4316-b8b5-1753fc17cd54-1631982946791@3c-example.org>'), 
                    (
                        (b'text', b'html', (b'charset', b'UTF-8'), None, None, b'7bit', 5053, 89, None, None, None, None),
                        (b'application', b'pdf', (b'name', b'YZ345.pdf'), None, None, b'base64', 187354, None, 
                            (b'attachment', (b'filename', b'YZ345.pdf')), None, None),
                        b'mixed', 
                        (b'boundary', b'abmob-49888c7b-3a09-4d10-b119-df9366de9f4c'), 
                        None, 
                        None, 
                        None
                    ), 
                    128656,
                    None,
                    (b'attachment', (b'filename', b'Fw: Some email two.eml')), 
                    None, 
                    None
                ), 
                b'mixed', 
                (b'boundary', b'------------3F95A42110AABF9E24EC86EB'), 
                None, 
                (b'en-US',), 
                None
            ), 
            128759, 
            None, 
            (b'attachment', (b'filename', b'Re: My message.eml')), None, None
        )
    ], 
    b'mixed', 
    (b'boundary', b'------------7323DBBF0E22BDA4B95E42D1'), 
    None, 
    (b'en-US',), 
    None
)

Сплющенные body_parts являются, первый номер - body_number:

1        - MIXED       : TEXT, text/plain, utf-8
2        - MIXED       : MESSAGE_RFC822, message/rfc822, Re: My message.eml
2.1      - MIXED       : TEXT, text/plain, utf-8
2.2      - MIXED       : MESSAGE_RFC822, message/rfc822, Fw: Some email two.eml
2.2.1    - MIXED       : TEXT, text/html, utf-8
2.2.2    - MIXED       : NON_MULTIPART-ATTACHMENT, application/pdf, YZ345.pdf

Исправление BODYSTRUCTURE вложенных сообщений

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

Как бы то ни было, IMAPClient содержит класс, который мы можем использовать для преобразования вложенного сообщения BODYSTRUCTURE в сообщение верхнего уровня BODYSTRUCTURE.

    body_data = BodyData()
    body_structure = body_data.create(nested_message_body_structure_part)

Теперь вложенное сообщение BODYSTRUCTURE содержит списки и может обрабатываться так же, как и сообщение верхнего уровня BODYSTRUCTURE.

Класс BodyStructurePart и типы частей

Сплющивание означает, что мы создаем список объектов BodyStructurePart. Класс BodyStructurePart содержит всю информацию для дальнейшей обработки. Он должен содержать, по крайней мере, следующее:

  • body_number: body_number
  • тип_тела: ALTERNATIVE, MIXED и т.д.
  • body_part: фактическая часть BODYSTRUCTURE.

Кроме того, я добавил атрибуты:

  • part_type
  • part_subtype
  • content_type

part_type может быть, см. также RFC3501 выше:

  • MESSAGE_RFC822
  • TEXT
  • NON_MULTIPART

part_subtype, используемый с part_type = NON_MULTIPART, может быть:

  • ATTACHMENT
  • INLINE
  • OTHER

Код парсера BODYSTRUCTURE

И, наконец, вот код парсера. Он содержит три класса:

  • IMAPBodyStructurePartUtils
  • IMAPBodyStructurePart
  • IMAPBodyStructureParser

Класс IMAPBodyStructureParser имеет метод parse, который вызывается с помощью BODYSTRUCTURE , возвращаемого IMAPClient. Этот метод возвращает список объектов IMAPBodyStructurePart , которые можно использовать в нашем коде.

Я поместил код и примеры в один файл на случай, если вы захотите попробовать:

import sys

from imapclient import IMAPClient
from imapclient.response_types import BodyData


class IMAPBodyStructurePartUtils:

    @classmethod
    def __decode(cls, s):
        try:
            s = s.decode()
        except Exception as e:
            pass
        return s

    @classmethod
    def get_part_type_and_part_subtype(cls, body_part):
        part_type = None
        part_subtype = None
        try:
            if body_part[0] == b'message' and body_part[1] == b'rfc822':
                part_type = 'MESSAGE_RFC822'
            elif body_part[0] == b'text':
                part_type = 'TEXT'
            else:
                part_type = 'NON_MULTIPART'
        except:
            pass
        if part_type == 'NON_MULTIPART':
            try:
                if body_part[8][0] == b'attachment':
                    part_subtype = 'ATTACHMENT'
                elif body_part[8][0] == b'inline':
                    part_subtype = 'INLINE'
                else:
                    part_subtype = 'OTHER'
            except Exception as e:
                pass
        return part_type, part_subtype

    @classmethod
    def get_content_type(cls, body_part):
        try:
            ctype = cls.__decode(body_part[0])
            csubtype = cls.__decode(body_part[1])
            return ctype.lower() + '/' + csubtype.lower()
        except Exception as e:
            pass
        return None  

    @classmethod
    def __get_charset_or_name(cls, charset_or_name, body_part):
        try:
            for a in range(0, len(body_part[2]), 2):
                key = body_part[2][a].lower()
                val = body_part[2][a + 1]
                if key == charset_or_name:
                    if charset_or_name == b'charset':
                        return cls.__decode(val).lower()
                    return cls.__decode(val)
        except Exception as e:
            pass
        return None  

    @classmethod
    def get_charset(cls, body_part):
        return cls.__get_charset_or_name(b'charset', body_part)

    @classmethod
    def get_name(cls, body_part):
        return cls.__get_charset_or_name(b'name', body_part)

    @classmethod
    def get_filename(cls, body_part):
        try:
            sub_body_part = body_part[8][1]
            for i in range(0, len(sub_body_part), 2):
                key = sub_body_part[i]
                val = sub_body_part[i + 1]
                if key == b'filename':
                    return cls.__decode(val)
        except:
            pass
        return None


class IMAPBodyStructurePart:

    def __init__(
        self,
        body_number=None,
        body_type=None,
        body_part=None,
    ):
        self.body_number = body_number
        self.body_type = body_type
        self.body_part = body_part

        self.part_type, self.part_subtype = IMAPBodyStructurePartUtils.get_part_type_and_part_subtype(self.body_part)
        self.content_type = IMAPBodyStructurePartUtils.get_content_type(self.body_part)
        self.name = IMAPBodyStructurePartUtils.get_name(self.body_part)
        self.charset = IMAPBodyStructurePartUtils.get_charset(self.body_part)
        self.filename = IMAPBodyStructurePartUtils.get_filename(self.body_part)

    def __str__(self):
        if self.body_type is None:
            self.body_type = ''
        if self.part_type == 'MESSAGE_RFC822':
            return '{:8} - {:12}: {}, {}, {}'.format(self.body_number, self.body_type, self.part_type, self.content_type, self.name)
        elif self.part_type == 'TEXT':
            return '{:8} - {:12}: {}, {}, {}'.format(self.body_number, self.body_type, self.part_type, self.content_type, self.charset)
        return '{:8} - {:12}: {}-{}, {}, {}'.format(self.body_number, self.body_type, self.part_type, self.part_subtype, self.content_type, self.filename)


class IMAPBodyStructureParser:

    def __init__(
        self,
        dbg=False,
    ):
        pass

    @classmethod
    def __is_multipart(cls, part):
        return isinstance(part[0], list)

    @classmethod
    def __get_body_type(cls, part):
        # body_type (ALTERNATIVE, MIXED, ...) is first item after list
        body_type = None
        if len(part) > 1:
            body_type = part[1]
            if body_type is not None:
                try:
                    body_type = body_type.decode()
                except Exception as e:
                    pass
        if body_type is not None:
            body_type = body_type.upper()
        return body_type

    @classmethod
    def __add_body_part(cls, body_parts, body_number, body_type, part):
        body_parts.append(IMAPBodyStructurePart(
            body_number=body_number,
            body_type=body_type,
            body_part=part,
        ))

    @classmethod
    def parse(cls, part, body_number='', body_type=None):
        return cls.__recursive_parse(body_parts=[], part=part, body_number=body_number, body_type=body_type)

    @classmethod
    def __recursive_parse(cls, body_parts, part, body_number='', body_type=None):
        if part is None:
            return None

        part_type, part_sub_type = IMAPBodyStructurePartUtils.get_part_type_and_part_subtype(part)
        if part_type == 'MESSAGE_RFC822':
            cls.__add_body_part(body_parts, body_number, body_type, part)
            # convert message body_structure at part[8] using BodyData
            body_data = BodyData()
            part = body_data.create(part[8])
            if cls.__is_multipart(part):
                body_type = cls.__get_body_type(part)
                for i, p in enumerate(part[0], 1):
                    if body_number == '':
                        next_body_number = str(i)
                    else:
                        next_body_number = body_number + '.' + str(i)
                    cls.__recursive_parse(body_parts, p, body_number=next_body_number, body_type=body_type)
            else:
                cls.__add_body_part(body_parts, body_number, body_type, part)

        elif cls.__is_multipart(part):
            body_type = cls.__get_body_type(part)
            for i, p in enumerate(part[0], 1):
                if body_number == '':
                    next_body_number = str(i)
                else:
                    next_body_number = body_number + '.' + str(i)
                cls.__recursive_parse(body_parts, p, body_number=next_body_number, body_type=body_type)
        else:
            if body_number == '':
                body_number = '1'
            cls.__add_body_part(body_parts, body_number, body_type, part)

        return body_parts


# BODYSTRUCTURE examples
body_structures = [
    
    {
        'name': 'single text/plain part',
        'body_structure': 
        (b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 1148, 59, None, None, None, None),
    },
    {
        'name': 'text/plain and text/html',
        'body_structure': 
        (
            [
                (b'text', b'plain', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 426, 15, None, None, None, None), 
                (b'text', b'html', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 1085, 36, None, None, None, None)
            ], 
            b'alternative', 
            (b'boundary', b'_000_CWXP265MB4244C3FA1F3563A988AAE2CABBDF9CWXP265MB4244GBRP_'), 
            None, 
            (b'en-US',), 
            None
        ),
    },
    {
        'name': 'text/plain and pdf attachment',
        'body_structure': 
        (
            [
                (b'text', b'plain', (b'charset', b'ISO-8859-15'), None, None, b'quoted-printable', 394, 13, None, (b'inline', None), None, None), 
                (b'application', b'pdf', (b'name', b'manual.pdf'), None, None, b'base64', 175098, None, (b'attachment', (b'filename', b'manual.pdf')), None, None)
            ], 
            b'mixed', 
            (b'boundary', b'_----------=_1631938636414264302'), 
            None, 
            None, 
            None
        ),
    },
    {
        'name': 'text/plain and text/html and pdf attachment',
        'body_structure': 
        (
            [
                (
                    [
                        (b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'8bit', 1268, 25, None, (b'inline', None), None, None), 
                        (b'text', b'html', (b'charset', b'UTF-8'), None, None, b'8bit', 10887, 115, None, (b'inline', None), None, None)
                    ], 
                    b'alternative', 
                    (b'boundary', b'alt-60e413d9174229.65052373'), 
                    None, 
                    None, 
                    None
                ), 
                (b'application', b'pdf', (b'name', b'summary.pdf'), None, None, b'base64', 187354, None, 
                    (b'attachment', (b'filename', b'summary.pdf')), None, None)
            ], 
            b'mixed', 
            (b'boundary', b'multipart-60e413d9174190.84932644'), 
            None, 
            None, 
            None
        ),
    },
    {
        'name': 'text/plain, text/html and inline image',
        'body_structure': 
        (
            [
                (
                    [
                        (b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 3909, 138, None, None, None, None), 
                        (b'text', b'html', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 21375, 397, None, None, None, None)
                    ], 
                    b'alternative', 
                    (b'boundary', b'000000000000239fe505c86b594b'), 
                    None, 
                    None, 
                    None
                ), 
                (b'image', b'png', (b'name', b'image.png'), b'<17afcc25c9bcb971f161>', None, b'base64', 1491868, None, 
                    (b'inline', (b'filename', b'image.png')), None, None)
            ], 
            b'related', 
            (b'boundary', 
            b'000000000000239fe605c86b594c'), 
            None, 
            None, 
            None
        ),
    },
    {
        'name': 'text/plain, text/html and inline images and pdf attachment',
        'body_structure': 
        (
            [
                (
                    [
                        (
                            [
                                (b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 4393, 90, None, None, None, None), 
                                (b'text', b'html', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 12720, 264, None, None, None, None)
                            ], 
                            b'alternative', 
                            (b'boundary', b'0000000000007dda0f05c7b3e2d2'), 
                            None, 
                            None, 
                            None
                        ),
                        (b'image', b'png', (b'name', b'image.png'), b'<17acdbf96cccb971f161>', None, b'base64', 120514, None, 
                            (b'inline', (b'filename', b'image.png')), None, None), 
                        (b'image', b'png', (b'name', b'image.png'), b'<17acdbf96cccb971f162>', None, b'base64', 78208, None, 
                            (b'inline', (b'filename', b'image.png')), None, None)
                    ], 
                    b'related', 
                    (b'boundary', b'0000000000007dda1005c7b3e2d3'), 
                    None, 
                    None, 
                    None
                ), 
                (b'application', b'pdf', (b'name', b'Love letter.pdf'), b'<17acdbf96cc7f74e7e76>', None, b'base64', 591456, None, 
                    (b'attachment', (b'filename', b'Love letter.pdf')), None, None)
            ], 
            b'mixed', 
            (b'boundary', b'0000000000007dda1105c7b3e2d4'), 
            None, 
            None, 
            None
        ),
    },
    {
        'name': 'text/plain with message/rfc822 attachment',
        'body_structure': 
        (
            [
                (b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 35, 7, None, None, None, None), 
                (b'message', b'rfc822', (b'name', b'Please respond.eml'), None, None, b'7bit', 10034116, 
                    (
                        b'Sun, 19 Sep 2021 10:04:43 +0200', 
                        b'Please respond', 
                        ((b'Peter Mooring', None, b'petermooring', b'gmail.com'),), 
                        ((b'Peter Mooring', None, b'petermooring', b'gmail.com'),), 
                        ((b'Peter Mooring', None, b'petermooring', b'gmail.com'),), 
                        ((b'peterpm@xs4all.nl', None, b'peterpm', b'xs4all.nl'),), 
                        None, 
                        None, 
                        None, 
                        b'<8b678e28-d03a-2bdd-2930-12470235ef9a@gmail.com>'), 
                    (
                        (b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 46, 7, None, None, None, None), 
                        (b'text', b'html', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 21375, 397, None, None, None, None),
                        b'alternative', 
                        (b'boundary', b'------------3F95A42110AABF9E24EC86EB'), 
                        None, 
                        (b'en-US',), 
                        None
                    ), 
                    128759, 
                    None, 
                    (b'attachment', (b'filename', b'Please respond.eml')), None, None
                )
            ], 
            b'mixed', 
            (b'boundary', b'------------7323DBBF0E22BDA4B95E42D1'), 
            None, 
            (b'en-US',), 
            None
        ),
    },
    {
        'name': 'text/plain with message/rfc822 attachment including another message/rfc822',
        'body_structure': 
        (
            [
                (b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 35, 7, None, None, None, None), 
                (b'message', b'rfc822', (b'name', b'Re: My message.eml'), None, None, b'7bit', 10034116, 
                    (
                        b'Sun, 19 Sep 2021 10:04:43 +0200', 
                        b'Re: My message', 
                        ((b'Bob Smith', None, b'bobsmith', b'example.com'),), 
                        ((b'Bob Smith', None, b'bobsmith', b'example.com'),), 
                        ((b'Bob Smith', None, b'bobsmith', b'example.com'),), 
                        ((b'richardroe@example.org', None, b'richardroe', b'example.org'),), 
                        None, 
                        None, 
                        None, 
                        b'<8b678e28-d03a-2bdd-2930-12470235ef9a@example.com>'
                    ), 
                    (
                        (b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 46, 7, None, None, None, None), 
                        (b'message', b'rfc822', (b'name', b'Fw: Some email two.eml'), None, None, b'7bit', 10029135, 
                            (
                                b'Sat, 18 Sep 2021 18:35:47 +0200', 
                                b'Fw: Some email two', 
                                ((b'John Doe', None, b'johndoe', b'example.org'),), 
                                ((b'John Doe', None, b'johndoe', b'example.org'),), 
                                ((b'John Doe', None, b'johndoe', b'example.org'),), 
                                ((None, None, b'richardroe', b'example.org'),), 
                                None,
                                None,
                                None,
                                b'<a7dfbf41-1a26-4316-b8b5-1753fc17cd54-1631982946791@3c-example.org>'), 
                            (
                                (b'text', b'html', (b'charset', b'UTF-8'), None, None, b'7bit', 5053, 89, None, None, None, None),
                                (b'application', b'pdf', (b'name', b'YZ345.pdf'), None, None, b'base64', 187354, None, 
                                    (b'attachment', (b'filename', b'YZ345.pdf')), None, None),
                                b'mixed', 
                                (b'boundary', b'abmob-49888c7b-3a09-4d10-b119-df9366de9f4c'), 
                                None, 
                                None, 
                                None
                            ), 
                            128656,
                            None,
                            (b'attachment', (b'filename', b'Fw: Some email two.eml')), 
                            None, 
                            None
                        ), 
                        b'mixed', 
                        (b'boundary', b'------------3F95A42110AABF9E24EC86EB'), 
                        None, 
                        (b'en-US',), 
                        None
                    ), 
                    128759, 
                    None, 
                    (b'attachment', (b'filename', b'Fw: Some email two.eml')), None, None
                )
            ],
            b'mixed', 
            (b'boundary', b'------------7323DBBF0E22BDA4B95E42D1'), 
            None, 
            (b'en-US',), 
            None
        ),
    },
]


# show examples
for b in body_structures:
    print('\nBody structure: {}\n{}'.format(b['name'], '-'*60))
    for body_structure_part in IMAPBodyStructureParser.parse(b['body_structure']):
        print('{}'.format(body_structure_part))

Несколько слов о IMAP и конфиденциальности

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

Резюме

Расплющивание IMAP BODYSTRUCTURE заняло у меня много времени, потому что в интернете не было рецептов Python . После прочтения RFC3501 оказалось, что это не так уж и сложно... но... Поскольку мы сами расшифровываем BODYSTRUCTURE , легко допустить ошибки. А сможем ли мы справиться со всеми типами (malformed) BODYSTRUCTURE?
В интернете можно найти информацию о иногда отказывающих декодерах, например, из RoundCube. Они отступают, получая сообщение целиком.

Ссылки / кредиты

IMAP BODYSTRUCTURE: formatted examples
http://sgerwk.altervista.org/imapbodystructure.html

IMAPClient
https://imapclient.readthedocs.io/en/2.2.0/index.html

INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
https://datatracker.ietf.org/doc/html/rfc3501

Подробнее

Email IMAP

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.