示例#1
0
    def _to_stream_when_changed(self, out):

        ctype = self.content_type

        if ctype.is_singlepart():

            if self._container.body_changed():
                charset, encoding, body = encode_body(self)
                if charset:
                    self.charset = charset
                self.content_encoding = WithParams(encoding)
            else:
                body = self._container.read_body()

            # RFC allows subparts without headers
            if self.headers:
                self.headers.to_stream(out)
            elif self.is_root():
                raise EncodingError("Root message should have headers")

            out.write(CRLF)
            out.write(body)
        else:
            self.headers.to_stream(out)
            out.write(CRLF)

            if ctype.is_multipart():
                boundary = ctype.get_boundary_line()
                for index, part in enumerate(self.parts):
                    out.write((CRLF if index != 0 else "") + boundary + CRLF)
                    part.to_stream(out)
                out.write(CRLF + ctype.get_boundary_line(final=True) + CRLF)

            elif ctype.is_message_container():
                self.enclosed.to_stream(out)
示例#2
0
def attachment(content_type, body, filename=None,
               disposition=None, charset=None):
    """Smarter method to build attachments that detects the proper content type
    and form of the message based on content type string, body and filename
    of the attachment
    """

    # fix and sanitize content type string and get main and sub parts:
    main, sub = fix_content_type(
        content_type, default=('application', 'octet-stream'))

    # adjust content type based on body or filename if it's not too accurate
    content_type = adjust_content_type(
        ContentType(main, sub), body, filename)

    if content_type.main == 'message':
        try:
            message = message_container(from_string(body))
            message.headers['Content-Disposition'] = WithParams(disposition)
            return message
        except DecodingError:
            content_type = ContentType('application', 'octet-stream')
    return binary(
        content_type.main,
        content_type.sub,
        body, filename,
        disposition,
        charset)
示例#3
0
    def __init__(
        self, content_type, body, charset=None, disposition=None, filename=None):
        self.headers = headers.MimeHeaders()
        self.body = body
        self.disposition = disposition or ('attachment' if filename else None)
        self.filename = filename
        self.size = len(body)

        if self.filename:
            self.filename = path.basename(self.filename)

        content_type = adjust_content_type(content_type, body, filename)

        if content_type.main == 'text':
            # the text should have a charset
            if not charset:
                charset = "utf-8"

            # it should be stored as unicode. period
            self.body = charsets.convert_to_unicode(charset, body)

            # let's be simple when possible
            if charset != 'ascii' and is_pure_ascii(body):
                charset = 'ascii'

        self.headers['MIME-Version'] = '1.0'
        self.headers['Content-Type'] = content_type
        if charset:
            content_type.params['charset'] = charset

        if self.disposition:
            self.headers['Content-Disposition'] = WithParams(disposition)
            if self.filename:
                self.headers['Content-Disposition'].params['filename'] = self.filename
                self.headers['Content-Type'].params['name'] = self.filename
示例#4
0
def create_email(
    from_name,
    from_email,
    reply_to,
    nylas_uid,
    to_addr,
    cc_addr,
    bcc_addr,
    subject,
    html,
    in_reply_to,
    references,
    attachments,
):
    """
    Creates a MIME email message (both body and sets the needed headers).

    Parameters
    ----------
    from_name: string
        The name aka phrase of the sender.
    from_email: string
        The sender's email address.
    to_addr, cc_addr, bcc_addr: list of pairs (name, email_address), or None
        Message recipients.
    reply_to: tuple or None
        Indicates the mailbox in (name, email_address) format to which
        the author of the message suggests that replies be sent.
    subject : string
        a utf-8 encoded string
    html : string
        a utf-8 encoded string
    in_reply_to: string or None
        If this message is a reply, the Message-Id of the message being replied
        to.
    references: list or None
        If this message is a reply, the Message-Ids of prior messages in the
        thread.
    attachments: list of dicts, optional
        a list of dicts(filename, data, content_type, content_disposition)
    """
    html = html if html else ""
    plaintext = html2text(html)

    # Create a multipart/alternative message
    msg = mime.create.multipart("alternative")
    html_part = mime.create.text("html", html)
    html_part.content_encoding = WithParams("base64")
    msg.append(mime.create.text("plain", plaintext), html_part)

    # Create an outer multipart/mixed message
    if attachments:
        text_msg = msg
        msg = mime.create.multipart("mixed")

        # The first part is the multipart/alternative text part
        msg.append(text_msg)

        # The subsequent parts are the attachment parts
        for a in attachments:
            # Disposition should be inline if we add Content-ID
            attachment = mime.create.attachment(
                a["content_type"],
                a["data"],
                filename=a["filename"],
                disposition=a["content_disposition"],
            )
            if a["content_disposition"] == "inline":
                attachment.headers["Content-Id"] = "<{}>".format(a["block_id"])
            msg.append(attachment)

    msg.headers["Subject"] = subject if subject else ""

    # Gmail sets the From: header to the default sending account. We can
    # however set our own custom phrase i.e. the name that appears next to the
    # email address (useful if the user has multiple aliases and wants to
    # specify which to send as), see: http://lee-phillips.org/gmailRewriting/
    # For other providers, we simply use name = ''
    from_addr = address.EmailAddress(from_name, from_email)
    msg.headers["From"] = from_addr.full_spec()

    # Need to set these headers so recipients know we sent the email to them
    # TODO(emfree): should these really be unicode?
    if to_addr:
        full_to_specs = [
            _get_full_spec_without_validation(name, spec)
            for name, spec in to_addr
        ]
        msg.headers["To"] = u", ".join(full_to_specs)
    if cc_addr:
        full_cc_specs = [
            _get_full_spec_without_validation(name, spec)
            for name, spec in cc_addr
        ]
        msg.headers["Cc"] = u", ".join(full_cc_specs)
    if bcc_addr:
        full_bcc_specs = [
            _get_full_spec_without_validation(name, spec)
            for name, spec in bcc_addr
        ]
        msg.headers["Bcc"] = u", ".join(full_bcc_specs)
    if reply_to:
        # reply_to is only ever a list with one element
        msg.headers["Reply-To"] = _get_full_spec_without_validation(
            reply_to[0][0], reply_to[0][1])

    add_nylas_headers(msg, nylas_uid)

    if in_reply_to:
        msg.headers["In-Reply-To"] = in_reply_to
    if references:
        msg.headers["References"] = "\t".join(references)

    # Most ISPs set date automatically, but we need to set it here for those
    # which do not. The Date header is required and omitting it causes issues
    # with scoring in many spam systems like SpamAssassin
    # Set dates in UTC since we don't know the timezone of the sending user
    # +0000 means UTC, whereas -0000 means unsure of timezone
    utc_datetime = datetime.utcnow()
    day = utc_datetime.strftime("%a")
    date = utc_datetime.strftime("%d %b %Y %X")
    date_header = "{day}, {date} +0000\r\n".format(day=day, date=date)
    msg.headers["Date"] = date_header

    rfcmsg = _rfc_transform(msg)

    return rfcmsg
示例#5
0
 def content_encoding(self):
     return self.headers.get(
         'Content-Transfer-Encoding', WithParams('7bit'))
示例#6
0
 def content_disposition(self):
     """ returns tuple (value, params) """
     return self.headers.get('Content-Disposition', WithParams(None))
示例#7
0
from os import path
from email.mime import audio

from flanker import metrics
from flanker.mime import bounce
from flanker.mime.message import headers, charsets
from flanker.mime.message.headers import (WithParams, ContentType, MessageId,
                                          Subject)
from flanker.mime.message.headers.parametrized import fix_content_type
from flanker.mime.message.errors import EncodingError, DecodingError
from flanker.utils import is_pure_ascii


log = logging.getLogger(__name__)

CTE = WithParams('7bit', {})

class Stream(object):

    def __init__(self, content_type, start, end, string, stream):
        self.content_type = content_type
        self.start = start
        self.end = end
        self.string = string
        self.stream = stream

        self._headers = None
        self._body_start = None
        self._body = None
        self._body_changed = False
        self.size = len(self.string)