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