""" mailthon.headers ~~~~~~~~~~~~~~~~ Implements RFC compliant headers, and is the recommended way to put headers into enclosures or envelopes. :copyright: (c) 2015 by Eeo Jun :license: MIT, see LICENSE for details. """ import sys from cgi import parse_header from email.utils import quote, formatdate, make_msgid, getaddresses from .helpers import format_addresses, UnicodeDict IS_PY3 = int(sys.version[0]) == 3 class Headers(UnicodeDict): """ :rfc:`2822` compliant subclass of the :class:`~mailthon.helpers.UnicodeDict`. The semantics of the dictionary is different from that of the standard library MIME object- only the latest header is preserved instead of preserving all headers. This makes header lookup deterministic and sane. """ @property def resent(self): """ Whether the email was resent, i.e. whether the ``Resent-Date`` header was set. """ return 'Resent-Date' in self @property def sender(self): """ Returns the sender, respecting the Resent-* headers. In any case, prefer Sender over From, meaning that if Sender is present then From is ignored, as per the RFC. """ to_fetch = ( ['Resent-Sender', 'Resent-From'] if self.resent else ['Sender', 'From'] ) for item in to_fetch: if item in self: _, addr = getaddresses([self[item]])[0] return addr @property def receivers(self): """ Returns a list of receivers, obtained from the To, Cc, and Bcc headers, respecting the Resent-* headers if the email was resent. """ attrs = ( ['Resent-To', 'Resent-Cc', 'Resent-Bcc'] if self.resent else ['To', 'Cc', 'Bcc'] ) addrs = (v for v in (self.get(k) for k in attrs) if v) return [addr for _, addr in getaddresses(addrs)] def prepare(self, mime): """ Prepares a MIME object by applying the headers to the *mime* object. Ignores any Bcc or Resent-Bcc headers. """ for key in self: if key == 'Bcc' or key == 'Resent-Bcc': continue del mime[key] # Python 3.* email's compatibility layer will handle # unicode field values in proper way but Python 2 # won't (it will encode not only additional field # values but also all header values) parsed_header, additional_fields = parse_header( self[key] if IS_PY3 else self[key].encode("utf-8") ) mime.add_header(key, parsed_header, **additional_fields) def subject(text): """ Generates a Subject header with a given *text*. """ yield 'Subject' yield text def sender(address): """ Generates a Sender header with a given *text*. *text* can be both a tuple or a string. """ yield 'Sender' yield format_addresses([address]) def to(*addrs): """ Generates a To header with the given *addrs*, where addrs can be made of ``Name <address>`` or ``address`` strings, or a mix of both. """ yield 'To' yield format_addresses(addrs) def cc(*addrs): """ Similar to ``to`` function. Generates a Cc header. """ yield 'Cc' yield format_addresses(addrs) def bcc(*addrs): """ Generates a Bcc header. This is safe when using the mailthon Headers implementation because the Bcc headers will not be included in the MIME object. """ yield 'Bcc' yield format_addresses(addrs) def content_disposition(disposition, filename): """ Generates a content disposition hedaer given a *disposition* and a *filename*. The filename needs to be the base name of the path, i.e. instead of ``~/file.txt`` you need to pass in ``file.txt``. The filename is automatically quoted. """ yield 'Content-Disposition' yield '%s; filename="%s"' % (disposition, quote(filename)) def date(time=None): """ Generates a Date header. Yields the *time* as the key if specified, else returns an RFC compliant date generated by formatdate. """ yield 'Date' yield time or formatdate(localtime=True) def message_id(string=None, idstring=None): """ Generates a Message-ID header, by yielding a given *string* if specified, else an RFC compliant message-id generated by make_msgid and strengthened by an optional *idstring*. """ yield 'Message-ID' yield string or make_msgid(idstring) def content_id(name): yield 'Content-ID' yield '<%s>' % (name,)