1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
import dataclasses
import datetime
from email.utils import formataddr
from typing import Any, List, Optional, Tuple, TYPE_CHECKING, Union
from .typing_imapclient import _Atom
from .util import to_unicode
@dataclasses.dataclass
class Envelope:
r"""Represents envelope structures of messages. Returned when parsing
ENVELOPE responses.
:ivar date: A datetime instance that represents the "Date" header.
:ivar subject: A string that contains the "Subject" header.
:ivar from\_: A tuple of Address objects that represent one or more
addresses from the "From" header, or None if header does not exist.
:ivar sender: As for from\_ but represents the "Sender" header.
:ivar reply_to: As for from\_ but represents the "Reply-To" header.
:ivar to: As for from\_ but represents the "To" header.
:ivar cc: As for from\_ but represents the "Cc" header.
:ivar bcc: As for from\_ but represents the "Bcc" recipients.
:ivar in_reply_to: A string that contains the "In-Reply-To" header.
:ivar message_id: A string that contains the "Message-Id" header.
A particular issue to watch out for is IMAP's handling of "group
syntax" in address fields. This is often encountered as a
recipient header of the form::
undisclosed-recipients:;
but can also be expressed per this more general example::
A group: a@example.com, B <b@example.org>;
This example would yield the following Address tuples::
Address(name=None, route=None, mailbox=u'A group', host=None)
Address(name=None, route=None, mailbox=u'a', host=u'example.com')
Address(name=u'B', route=None, mailbox=u'b', host=u'example.org')
Address(name=None, route=None, mailbox=None, host=None)
The first Address, where ``host`` is ``None``, indicates the start
of the group. The ``mailbox`` field contains the group name. The
final Address, where both ``mailbox`` and ``host`` are ``None``,
indicates the end of the group.
See :rfc:`3501#section-7.4.2` and :rfc:`2822` for further details.
"""
date: Optional[datetime.datetime]
subject: bytes
from_: Optional[Tuple["Address", ...]]
sender: Optional[Tuple["Address", ...]]
reply_to: Optional[Tuple["Address", ...]]
to: Optional[Tuple["Address", ...]]
cc: Optional[Tuple["Address", ...]]
bcc: Optional[Tuple["Address", ...]]
in_reply_to: bytes
message_id: bytes
@dataclasses.dataclass
class Address:
"""Represents electronic mail addresses. Used to store addresses in
:py:class:`Envelope`.
:ivar name: The address "personal name".
:ivar route: SMTP source route (rarely used).
:ivar mailbox: Mailbox name (what comes just before the @ sign).
:ivar host: The host/domain name.
As an example, an address header that looks like::
Mary Smith <mary@foo.com>
would be represented as::
Address(name=u'Mary Smith', route=None, mailbox=u'mary', host=u'foo.com')
See :rfc:`2822` for more detail.
See also :py:class:`Envelope` for information about handling of
"group syntax".
"""
name: bytes
route: bytes
mailbox: bytes
host: bytes
def __str__(self) -> str:
if self.mailbox and self.host:
address = to_unicode(self.mailbox) + "@" + to_unicode(self.host)
else:
address = to_unicode(self.mailbox or self.host)
return formataddr((to_unicode(self.name), address))
class SearchIds(List[int]):
"""
Contains a list of message ids as returned by IMAPClient.search().
The *modseq* attribute will contain the MODSEQ value returned by
the server (only if the SEARCH command sent involved the MODSEQ
criteria). See :rfc:`4551` for more details.
"""
def __init__(self, *args: Any):
super().__init__(*args)
self.modseq: Optional[int] = None
_BodyDataType = Tuple[Union[bytes, int, "BodyData"], "_BodyDataType"]
class BodyData(_BodyDataType):
"""
Returned when parsing BODY and BODYSTRUCTURE responses.
"""
@classmethod
def create(cls, response: Tuple[_Atom, ...]) -> "BodyData":
# In case of multipart messages we will see at least 2 tuples
# at the start. Nest these in to a list so that the returned
# response tuple always has a consistent number of elements
# regardless of whether the message is multipart or not.
if isinstance(response[0], tuple):
# Multipart, find where the message part tuples stop
parts = []
for i, part in enumerate(response):
if isinstance(part, bytes):
break
if TYPE_CHECKING:
assert isinstance(part, tuple)
parts.append(part)
return cls(([cls.create(part) for part in parts],) + response[i:])
return cls(response)
@property
def is_multipart(self) -> bool:
return isinstance(self[0], list)
|