Beispiel #1
0
def msg_test():
    from email import policy
    from email.parser import BytesParser

    header_registry = HeaderRegistry()
    header_registry.map_to_type("To", AddressHeader)
    policy = policy.EmailPolicy(header_factory=header_registry)

    return BytesParser(policy=policy).parsebytes(MAIL_TEST)
Beispiel #2
0
def make_msg(subject,
             content,
             from_addr,
             tos=None,
             ccs=None,
             attachments=None):

    from_addr = parse_email(from_addr)

    # To have a correct address header
    header_registry = HeaderRegistry()
    header_registry.map_to_type('To', AddressHeader)
    msg = EmailMessage(policy.EmailPolicy(header_factory=header_registry))

    msg.set_content(content)
    msg['From'] = from_addr
    msg['Subject'] = subject
    msg['Message-Id'] = make_msgid()

    if tos:
        msg['To'] = tos
    if ccs:
        msg['Cc'] = ccs

    if attachments:
        for att in attachments:
            filename = att['filename']
            content = base64.b64decode(att['b64'])
            # Guess type here
            maintype, subtype = magic.from_buffer(content,
                                                  mime=True).split('/')
            msg.add_attachment(content,
                               maintype=maintype,
                               subtype=subtype,
                               filename=filename)

    msg['Date'] = localtime()

    # TODO add html alternative
    # See https://docs.python.org/3.6/library/email.examples.html
    """msg.add_alternative("\
    <html>
    <head></head>
    <body>
        <p>{body}<p>
    </body>
    </html>
    "".format(content, subtype='html')"""

    return msg
Beispiel #3
0
class EmailPolicy(Policy):
    __qualname__ = 'EmailPolicy'
    refold_source = 'long'
    header_factory = HeaderRegistry()

    def __init__(self, **kw):
        if 'header_factory' not in kw:
            object.__setattr__(self, 'header_factory', HeaderRegistry())
        super().__init__(**kw)

    def header_max_count(self, name):
        return self.header_factory[name].max_count

    def header_source_parse(self, sourcelines):
        (name, value) = sourcelines[0].split(':', 1)
        value = value.lstrip(' \t') + ''.join(sourcelines[1:])
        return (name, value.rstrip('\r\n'))

    def header_store_parse(self, name, value):
        if hasattr(value, 'name') and value.name.lower() == name.lower():
            return (name, value)
        if isinstance(value, str) and len(value.splitlines()) > 1:
            raise ValueError(
                'Header values may not contain linefeed or carriage return characters'
            )
        return (name, self.header_factory(name, value))

    def header_fetch_parse(self, name, value):
        if hasattr(value, 'name'):
            return value
        return self.header_factory(name, ''.join(value.splitlines()))

    def fold(self, name, value):
        return self._fold(name, value, refold_binary=True)

    def fold_binary(self, name, value):
        folded = self._fold(name, value, refold_binary=self.cte_type == '7bit')
        return folded.encode('ascii', 'surrogateescape')

    def _fold(self, name, value, refold_binary=False):
        if hasattr(value, 'name'):
            return value.fold(policy=self)
        maxlen = self.max_line_length if self.max_line_length else float('inf')
        lines = value.splitlines()
        refold = self.refold_source == 'all' or self.refold_source == 'long' and (
            lines and len(lines[0]) + len(name) + 2 > maxlen
            or any(len(x) > maxlen for x in lines[1:]))
        if refold or refold_binary and _has_surrogates(value):
            return self.header_factory(name, ''.join(lines)).fold(policy=self)
        return name + ': ' + self.linesep.join(lines) + self.linesep
class TestPickleCopyHeader(TestEmailBase):
    header_factory = HeaderRegistry()
    unstructured = header_factory('subject', 'this is a test')
    header_params = {
        'subject': ('subject', 'this is a test'),
        'from': ('from', '*****@*****.**'),
        'to': ('to', 'a: [email protected], [email protected];, [email protected]'),
        'date': ('date', 'Tue, 29 May 2012 09:24:26 +1000')
    }

    def header_as_deepcopy(self, name, value):
        header = self.header_factory(name, value)
        h = copy.deepcopy(header)
        self.assertEqual(str(h), str(header))

    def header_as_pickle(self, name, value):
        header = self.header_factory(name, value)
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            p = pickle.dumps(header, proto)
            h = pickle.loads(p)
            self.assertEqual(str(h), str(header))
Beispiel #5
0
 def __init__(self, **kw):
     if 'header_factory' not in kw:
         object.__setattr__(self, 'header_factory', HeaderRegistry())
     super().__init__(**kw)
Beispiel #6
0
class EmailPolicy(Policy):
    """+
    PROVISIONAL

    The API extensions enabled by this policy are currently provisional.
    Refer to the documentation for details.

    This policy adds new header parsing and folding algorithms.  Instead of
    simple strings, headers are custom objects with custom attributes
    depending on the type of the field.  The folding algorithm fully
    implements RFCs 2047 and 5322.

    In addition to the settable attributes listed above that apply to
    all Policies, this policy adds the following additional attributes:

    utf8                -- if False (the default) message headers will be
                           serialized as ASCII, using encoded words to encode
                           any non-ASCII characters in the source strings.  If
                           True, the message headers will be serialized using
                           utf8 and will not contain encoded words (see RFC
                           6532 for more on this serialization format).

    refold_source       -- if the value for a header in the Message object
                           came from the parsing of some source, this attribute
                           indicates whether or not a generator should refold
                           that value when transforming the message back into
                           stream form.  The possible values are:

                           none  -- all source values use original folding
                           long  -- source values that have any line that is
                                    longer than max_line_length will be
                                    refolded
                           all  -- all values are refolded.

                           The default is 'long'.

    header_factory      -- a callable that takes two arguments, 'name' and
                           'value', where 'name' is a header field name and
                           'value' is an unfolded header field value, and
                           returns a string-like object that represents that
                           header.  A default header_factory is provided that
                           understands some of the RFC5322 header field types.
                           (Currently address fields and date fields have
                           special treatment, while all other fields are
                           treated as unstructured.  This list will be
                           completed before the extension is marked stable.)

    content_manager     -- an object with at least two methods: get_content
                           and set_content.  When the get_content or
                           set_content method of a Message object is called,
                           it calls the corresponding method of this object,
                           passing it the message object as its first argument,
                           and any arguments or keywords that were passed to
                           it as additional arguments.  The default
                           content_manager is
                           :data:`~email.contentmanager.raw_data_manager`.

    """
    message_factory = EmailMessage
    utf8 = False
    refold_source = 'long'
    header_factory = HeaderRegistry()
    content_manager = raw_data_manager

    def __init__(self, **kw):
        if 'header_factory' not in kw:
            object.__setattr__(self, 'header_factory', HeaderRegistry())
        super().__init__(**kw)

    def header_max_count(self, name):
        """+
        The implementation for this class returns the max_count attribute from
        the specialized header class that would be used to construct a header
        of type 'name'.
        """
        return self.header_factory[name].max_count

    def header_source_parse(self, sourcelines):
        """+
        The name is parsed as everything up to the ':' and returned unmodified.
        The value is determined by stripping leading whitespace off the
        remainder of the first line, joining all subsequent lines together, and
        stripping any trailing carriage return or linefeed characters.  (This
        is the same as Compat32).

        """
        name, value = sourcelines[0].split(':', 1)
        value = value.lstrip(' \t') + ''.join(sourcelines[1:])
        return name, value.rstrip('\r\n')

    def header_store_parse(self, name, value):
        """+
        The name is returned unchanged.  If the input value has a 'name'
        attribute and it matches the name ignoring case, the value is returned
        unchanged.  Otherwise the name and value are passed to header_factory
        method, and the resulting custom header object is returned as the
        value.  In this case a ValueError is raised if the input value contains
        CR or LF characters.

        """
        if hasattr(value, 'name') and value.name.lower() == name.lower():
            return name, value
        if isinstance(value, str) and len(value.splitlines()) > 1:
            raise ValueError(
                'Header values may not contain linefeed or carriage return characters'
            )
        return name, self.header_factory(name, value)

    def header_fetch_parse(self, name, value):
        """+
        If the value has a 'name' attribute, it is returned to unmodified.
        Otherwise the name and the value with any linesep characters removed
        are passed to the header_factory method, and the resulting custom
        header object is returned.  Any surrogateescaped bytes get turned
        into the unicode unknown-character glyph.

        """
        if hasattr(value, 'name'):
            return value
        value = ''.join(linesep_splitter.split(value))
        return self.header_factory(name, value)

    def fold(self, name, value):
        """+
        Header folding is controlled by the refold_source policy setting.  A
        value is considered to be a 'source value' if and only if it does not
        have a 'name' attribute (having a 'name' attribute means it is a header
        object of some sort).  If a source value needs to be refolded according
        to the policy, it is converted into a custom header object by passing
        the name and the value with any linesep characters removed to the
        header_factory method.  Folding of a custom header object is done by
        calling its fold method with the current policy.

        Source values are split into lines using splitlines.  If the value is
        not to be refolded, the lines are rejoined using the linesep from the
        policy and returned.  The exception is lines containing non-ascii
        binary data.  In that case the value is refolded regardless of the
        refold_source setting, which causes the binary data to be CTE encoded
        using the unknown-8bit charset.

        """
        return self._fold(name, value, refold_binary=True)

    def fold_binary(self, name, value):
        """+
        The same as fold if cte_type is 7bit, except that the returned value is
        bytes.

        If cte_type is 8bit, non-ASCII binary data is converted back into
        bytes.  Headers with binary data are not refolded, regardless of the
        refold_header setting, since there is no way to know whether the binary
        data consists of single byte characters or multibyte characters.

        If utf8 is true, headers are encoded to utf8, otherwise to ascii with
        non-ASCII unicode rendered as encoded words.

        """
        folded = self._fold(name, value, refold_binary=self.cte_type == '7bit')
        charset = 'utf8' if self.utf8 else 'ascii'
        return folded.encode(charset, 'surrogateescape')

    def _fold(self, name, value, refold_binary=False):
        if hasattr(value, 'name'):
            return value.fold(policy=self)
        maxlen = self.max_line_length if self.max_line_length else float('inf')
        lines = value.splitlines()
        refold = (self.refold_source == 'all'
                  or self.refold_source == 'long' and
                  (lines and len(lines[0]) + len(name) + 2 > maxlen
                   or any(len(x) > maxlen for x in lines[1:])))
        if refold or refold_binary and _has_surrogates(value):
            return self.header_factory(name, ''.join(lines)).fold(policy=self)
        return name + ': ' + self.linesep.join(lines) + self.linesep
Beispiel #7
0
 def __init__(self, **kw):
     # Ensure that each new instance gets a unique header factory
     # (as opposed to clones, which share the factory).
     if 'header_factory' not in kw:
         object.__setattr__(self, 'header_factory', HeaderRegistry())
     super().__init__(**kw)
Beispiel #8
0
class EmailPolicy(Policy):
    """+
    PROVISIONAL

    The API extensions enabled by this policy are currently provisional.
    Refer to the documentation for details.

    This policy adds new header parsing and folding algorithms.  Instead of
    simple strings, headers are custom objects with custom attributes
    depending on the type of the field.  The folding algorithm fully
    implements RFCs 2047 and 5322.

    In addition to the settable attributes listed above that apply to
    all Policies, this policy adds the following additional attributes:

    refold_source       -- if the value for a header in the Message object
                           came from the parsing of some source, this attribute
                           indicates whether or not a generator should refold
                           that value when transforming the message back into
                           stream form.  The possible values are:

                           none  -- all source values use original folding
                           long  -- source values that have any line that is
                                    longer than max_line_length will be
                                    refolded
                           all  -- all values are refolded.

                           The default is 'long'.

    header_factory      -- a callable that takes two arguments, 'name' and
                           'value', where 'name' is a header field name and
                           'value' is an unfolded header field value, and
                           returns a string-like object that represents that
                           header.  A default header_factory is provided that
                           understands some of the RFC5322 header field types.
                           (Currently address fields and date fields have
                           special treatment, while all other fields are
                           treated as unstructured.  This list will be
                           completed before the extension is marked stable.)
    """

    refold_source = 'long'
    header_factory = HeaderRegistry()

    def __init__(self, **kw):
        # Ensure that each new instance gets a unique header factory
        # (as opposed to clones, which share the factory).
        if 'header_factory' not in kw:
            object.__setattr__(self, 'header_factory', HeaderRegistry())
        super().__init__(**kw)

    def header_max_count(self, name):
        """+
        The implementation for this class returns the max_count attribute from
        the specialized header class that would be used to construct a header
        of type 'name'.
        """
        return self.header_factory[name].max_count

    # The logic of the next three methods is chosen such that it is possible to
    # switch a Message object between a Compat32 policy and a policy derived
    # from this class and have the results stay consistent.  This allows a
    # Message object constructed with this policy to be passed to a library
    # that only handles Compat32 objects, or to receive such an object and
    # convert it to use the newer style by just changing its policy.  It is
    # also chosen because it postpones the relatively expensive full rfc5322
    # parse until as late as possible when parsing from source, since in many
    # applications only a few headers will actually be inspected.

    def header_source_parse(self, sourcelines):
        """+
        The name is parsed as everything up to the ':' and returned unmodified.
        The value is determined by stripping leading whitespace off the
        remainder of the first line, joining all subsequent lines together, and
        stripping any trailing carriage return or linefeed characters.  (This
        is the same as Compat32).

        """
        name, value = sourcelines[0].split(':', 1)
        value = value.lstrip(' \t') + ''.join(sourcelines[1:])
        return (name, value.rstrip('\r\n'))

    def header_store_parse(self, name, value):
        """+
        The name is returned unchanged.  If the input value has a 'name'
        attribute and it matches the name ignoring case, the value is returned
        unchanged.  Otherwise the name and value are passed to header_factory
        method, and the resulting custom header object is returned as the
        value.  In this case a ValueError is raised if the input value contains
        CR or LF characters.

        """
        if hasattr(value, 'name') and value.name.lower() == name.lower():
            return (name, value)
        if isinstance(value, str) and len(value.splitlines()) > 1:
            raise ValueError("Header values may not contain linefeed "
                             "or carriage return characters")
        return (name, self.header_factory(name, value))

    def header_fetch_parse(self, name, value):
        """+
        If the value has a 'name' attribute, it is returned to unmodified.
        Otherwise the name and the value with any linesep characters removed
        are passed to the header_factory method, and the resulting custom
        header object is returned.  Any surrogateescaped bytes get turned
        into the unicode unknown-character glyph.

        """
        if hasattr(value, 'name'):
            return value
        return self.header_factory(name, ''.join(value.splitlines()))

    def fold(self, name, value):
        """+
        Header folding is controlled by the refold_source policy setting.  A
        value is considered to be a 'source value' if and only if it does not
        have a 'name' attribute (having a 'name' attribute means it is a header
        object of some sort).  If a source value needs to be refolded according
        to the policy, it is converted into a custom header object by passing
        the name and the value with any linesep characters removed to the
        header_factory method.  Folding of a custom header object is done by
        calling its fold method with the current policy.

        Source values are split into lines using splitlines.  If the value is
        not to be refolded, the lines are rejoined using the linesep from the
        policy and returned.  The exception is lines containing non-ascii
        binary data.  In that case the value is refolded regardless of the
        refold_source setting, which causes the binary data to be CTE encoded
        using the unknown-8bit charset.

        """
        return self._fold(name, value, refold_binary=True)

    def fold_binary(self, name, value):
        """+
        The same as fold if cte_type is 7bit, except that the returned value is
        bytes.

        If cte_type is 8bit, non-ASCII binary data is converted back into
        bytes.  Headers with binary data are not refolded, regardless of the
        refold_header setting, since there is no way to know whether the binary
        data consists of single byte characters or multibyte characters.

        """
        folded = self._fold(name, value, refold_binary=self.cte_type == '7bit')
        return folded.encode('ascii', 'surrogateescape')

    def _fold(self, name, value, refold_binary=False):
        if hasattr(value, 'name'):
            return value.fold(policy=self)
        maxlen = self.max_line_length if self.max_line_length else float('inf')
        lines = value.splitlines()
        refold = (self.refold_source == 'all'
                  or self.refold_source == 'long' and
                  (lines and len(lines[0]) + len(name) + 2 > maxlen
                   or any(len(x) > maxlen for x in lines[1:])))
        if refold or refold_binary and _has_surrogates(value):
            return self.header_factory(name, ''.join(lines)).fold(policy=self)
        return name + ': ' + self.linesep.join(lines) + self.linesep
Beispiel #9
0
class ParsedHeaders(Mapping[bytes, Sequence[BaseHeader]]):
    """The mapping of message headers, parsed on-demand using a
    :class:`~email.headerregistry.HeaderRegistry`. Also provides typed
    properties for standard headers used in IMAP processing.

    """

    _registry: ClassVar[HeaderRegistry] = HeaderRegistry()

    __slots__ = ['_headers', '_parsed']

    def __init__(self, headers: _Headers) -> None:
        super().__init__()
        self._headers = headers
        self._parsed: Dict[bytes, Sequence[BaseHeader]] = {}

    def __getitem__(self, name: bytes) -> Sequence[BaseHeader]:
        name_lower = name.lower()
        parsed = self._parsed.get(name_lower)
        if parsed is not None:
            return parsed
        values = self._headers[name_lower]
        self._parsed[name_lower] = parsed = list(self._parse(values))
        return parsed

    def __len__(self) -> int:
        return len(self._headers)

    def __contains__(self, name: Any) -> bool:
        if not isinstance(name, bytes):
            raise TypeError(name)
        return name.lower() in self._headers

    def __iter__(self) -> Iterator[bytes]:
        return iter(self._headers.keys())

    @classmethod
    def _parse(cls, values: Sequence[Sequence[bytes]]) \
            -> Iterable[BaseHeader]:
        for value in values:
            lines = [line.decode('ascii', 'surrogateescape') for line in value]
            hdr_name, hdr_val = SMTP.header_source_parse(lines)
            yield cls._registry(hdr_name, hdr_val)

    def __repr__(self) -> str:
        return repr(dict(self))

    @property
    def content_type(self) -> Optional[ContentTypeHeader]:
        """The ``Content-Type`` header."""
        try:
            return cast(ContentTypeHeader, self[b'content-type'][0])
        except (KeyError, IndexError):
            return None

    @property
    def date(self) -> Optional[DateHeader]:
        """The ``Date`` header."""
        try:
            return cast(DateHeader, self[b'date'][0])
        except (KeyError, IndexError):
            return None

    @property
    def subject(self) -> Optional[UnstructuredHeader]:
        """The ``Subject`` header."""
        try:
            return cast(UnstructuredHeader, self[b'subject'][0])
        except (KeyError, IndexError):
            return None

    @property
    def from_(self) -> Optional[Sequence[AddressHeader]]:
        """The ``From`` header."""
        try:
            return cast(Sequence[AddressHeader], self[b'from'])
        except KeyError:
            return None

    @property
    def sender(self) -> Optional[Sequence[SingleAddressHeader]]:
        """The ``Sender`` header."""
        try:
            return cast(Sequence[SingleAddressHeader], self[b'sender'])
        except KeyError:
            return None

    @property
    def reply_to(self) -> Optional[Sequence[AddressHeader]]:
        """The ``Reply-To`` header."""
        try:
            return cast(Sequence[AddressHeader], self[b'reply-to'])
        except KeyError:
            return None

    @property
    def to(self) -> Optional[Sequence[AddressHeader]]:
        """The ``To`` header."""
        try:
            return cast(Sequence[AddressHeader], self[b'to'])
        except KeyError:
            return None

    @property
    def cc(self) -> Optional[Sequence[AddressHeader]]:
        """The ``Cc`` header."""
        try:
            return cast(Sequence[AddressHeader], self[b'cc'])
        except KeyError:
            return None

    @property
    def bcc(self) -> Optional[Sequence[AddressHeader]]:
        """The ``Bcc`` header."""
        try:
            return cast(Sequence[AddressHeader], self[b'bcc'])
        except KeyError:
            return None

    @property
    def in_reply_to(self) -> Optional[UnstructuredHeader]:
        """The ``In-Reply-To`` header."""
        try:
            return cast(UnstructuredHeader, self[b'in-reply-to'][0])
        except (KeyError, IndexError):
            return None

    @property
    def references(self) -> Optional[UnstructuredHeader]:
        """The ``References`` header."""
        try:
            return cast(UnstructuredHeader, self[b'references'][0])
        except (KeyError, IndexError):
            return None

    @property
    def message_id(self) -> Optional[UnstructuredHeader]:
        """The ``Message-Id`` header."""
        try:
            return cast(UnstructuredHeader, self[b'message-id'][0])
        except (KeyError, IndexError):
            return None

    @property
    def content_disposition(self) -> Optional[ContentDispositionHeader]:
        """The ``Content-Disposition`` header."""
        try:
            return cast(ContentDispositionHeader,
                        self[b'content-disposition'][0])
        except (KeyError, IndexError):
            return None

    @property
    def content_language(self) -> Optional[UnstructuredHeader]:
        """The ``Content-Language`` header."""
        try:
            return cast(UnstructuredHeader, self[b'content-language'][0])
        except (KeyError, IndexError):
            return None

    @property
    def content_location(self) -> Optional[UnstructuredHeader]:
        """The ``Content-Location`` header."""
        try:
            return cast(UnstructuredHeader, self[b'content-location'][0])
        except (KeyError, IndexError):
            return None

    @property
    def content_id(self) -> Optional[UnstructuredHeader]:
        """The ``Content-Id`` header."""
        try:
            return cast(UnstructuredHeader, self[b'content-id'][0])
        except (KeyError, IndexError):
            return None

    @property
    def content_description(self) -> Optional[UnstructuredHeader]:
        """The ``Content-Description`` header."""
        try:
            return cast(UnstructuredHeader, self[b'content-description'][0])
        except (KeyError, IndexError):
            return None

    @property
    def content_transfer_encoding(self) \
            -> Optional[ContentTransferEncodingHeader]:
        """The ``Content-Transfer-Encoding`` header."""
        try:
            return cast(ContentTransferEncodingHeader,
                        self[b'content-transfer-encoding'][0])
        except (KeyError, IndexError):
            return None