def encode_header_param(param_text):
    """Returns an appropriate RFC2047 encoded representation of the given
       header parameter value, suitable for direct assignation as the
       param value (e.g. via Message.set_param() or Message.add_header())
       RFC2822 assumes that headers contain only 7-bit characters,
       so we ensure it is the case, using RFC2047 encoding when needed.

       :param param_text: unicode or utf-8 encoded string with header value
       :rtype: string
       :return: if ``param_text`` represents a plain ASCII string,
                return the same 7-bit string, otherwise returns an
                ASCII string containing the RFC2047 encoded text.
    """
    # For details see the encode_header() method that uses the same logic
    if not param_text: return ""
    param_text_utf8 = tools.ustr(param_text).encode('utf-8')
    param_text_ascii = try_coerce_ascii(param_text_utf8)
    return param_text_ascii if param_text_ascii\
         else Charset('utf8').header_encode(param_text_utf8)
Exemple #2
0
def write_patch_file(filename, commit_info, diff):
    """Write patch file"""
    if not diff:
        gbp.log.debug("I won't generate empty diff %s" % filename)
        return None
    try:
        with open(filename, 'w') as patch:
            msg = Message()
            charset = Charset('utf-8')
            charset.body_encoding = None
            charset.header_encoding = QP

            # Write headers
            name = commit_info['author']['name']
            email = commit_info['author']['email']
            # Git compat: put name in quotes if special characters found
            if re.search("[,.@()\[\]\\\:;]", name):
                name = '"%s"' % name
            from_header = Header(unicode(name, 'utf-8'), charset, 77, 'from')
            from_header.append(unicode('<%s>' % email))
            msg['From'] = from_header
            date = commit_info['author'].datetime
            datestr = date.strftime('%a, %-d %b %Y %H:%M:%S %z')
            msg['Date'] = Header(unicode(datestr, 'utf-8'), charset, 77,
                                 'date')
            msg['Subject'] = Header(unicode(commit_info['subject'], 'utf-8'),
                                    charset, 77, 'subject')
            # Write message body
            if commit_info['body']:
                # Strip extra linefeeds
                body = commit_info['body'].rstrip() + '\n'
                try:
                    msg.set_payload(body.encode('ascii'))
                except UnicodeDecodeError:
                    msg.set_payload(body, charset)
            patch.write(msg.as_string(unixfrom=False))

            # Write diff
            patch.write('---\n')
            patch.write(diff)
    except IOError as err:
        raise GbpError('Unable to create patch file: %s' % err)
    return filename
    def append(self, s, charset=None, errors='strict'):
        """Append a string to the MIME header.
        
        Optional charset, if given, should be a Charset instance or the name
        of a character set (which will be converted to a Charset instance).  A
        value of None (the default) means that the charset given in the
        constructor is used.
        
        s may be a byte string or a Unicode string.  If it is a byte string
        (i.e. isinstance(s, str) is true), then charset is the encoding of
        that byte string, and a UnicodeError will be raised if the string
        cannot be decoded with that charset.  If s is a Unicode string, then
        charset is a hint specifying the character set of the characters in
        the string.  In this case, when producing an RFC 2822 compliant header
        using RFC 2047 rules, the Unicode string will be encoded using the
        following charsets in order: us-ascii, the charset hint, utf-8.  The
        first character set not to provoke a UnicodeError is used.
        
        Optional `errors' is passed as the third argument to any unicode() or
        ustr.encode() call.
        """
        if charset is None:
            charset = self._charset
        elif not isinstance(charset, Charset):
            charset = Charset(charset)
        if charset != '8bit':
            if isinstance(s, str):
                incodec = charset.input_codec or 'us-ascii'
                ustr = unicode(s, incodec, errors)
                outcodec = charset.output_codec or 'us-ascii'
                ustr.encode(outcodec, errors)
            elif isinstance(s, unicode):
                for charset in (USASCII, charset, UTF8):
                    try:
                        outcodec = charset.output_codec or 'us-ascii'
                        s = s.encode(outcodec, errors)
                        break
                    except UnicodeError:
                        pass

        self._chunks.append((s, charset))
        return
 def __init__(self, recip, sender, subject=None, text=None, lang=None):
     Message.__init__(self)
     charset = None
     if lang is not None:
         charset = Charset(Utils.GetCharSet(lang))
     if text is not None:
         self.set_payload(text, charset)
     if subject is None:
         subject = '(no subject)'
     self['Subject'] = Header(subject,
                              charset,
                              header_name='Subject',
                              errors='replace')
     self['From'] = sender
     if isinstance(recip, list):
         self['To'] = COMMASPACE.join(recip)
         self.recips = recip
     else:
         self['To'] = recip
         self.recips = [recip]
Exemple #5
0
def encode_header_param(param_text):
    """Returns an appropriate RFC2047 encoded representation of the given
       header parameter value, suitable for direct assignation as the
       param value (e.g. via Message.set_param() or Message.add_header())
       RFC2822 assumes that headers contain only 7-bit characters,
       so we ensure it is the case, using RFC2047 encoding when needed.

       :param param_text: unicode or utf-8 encoded string with header value
       :rtype: string
       :return: if ``param_text`` represents a plain ASCII string,
                return the same 7-bit string, otherwise returns an
                ASCII string containing the RFC2047 encoded text.
    """
    # For details see the encode_header() method that uses the same logic
    if not param_text:
        return ""
    param_text = ustr(param_text) # FIXME: require unicode higher up?
    if is_ascii(param_text):
        return pycompat.to_native(param_text) # TODO: is that actually necessary?
    return Charset("utf-8").header_encode(param_text)
Exemple #6
0
def make_header(decoded_seq, maxlinelen=None, header_name=None,
                continuation_ws=' '):
    """Create a Header from a sequence of pairs as returned by decode_header()

    decode_header() takes a header value string and returns a sequence of
    pairs of the format (decoded_string, charset) where charset is the string
    name of the character set.

    This function takes one of those sequence of pairs and returns a Header
    instance.  Optional maxlinelen, header_name, and continuation_ws are as in
    the Header constructor.
    """
    h = Header(maxlinelen=maxlinelen, header_name=header_name,
               continuation_ws=continuation_ws)
    for s, charset in decoded_seq:
        # None means us-ascii but we can simply pass it on to h.append()
        if charset is not None and not isinstance(charset, Charset):
            charset = Charset(charset)
        h.append(s, charset)
    return h
    def test_send_utf8(self):
        subject = u'sübjèçt'
        body = u'bödÿ-àéïöñß'
        mailsender = MailSender(debug=True)
        mailsender.send(to=['*****@*****.**'],
                        subject=subject,
                        body=body,
                        charset='utf-8',
                        _callback=self._catch_mail_sent)

        assert self.catched_msg
        self.assertEqual(self.catched_msg['subject'], subject)
        self.assertEqual(self.catched_msg['body'], body)

        msg = self.catched_msg['msg']
        self.assertEqual(msg['subject'], subject)
        self.assertEqual(msg.get_payload(), body)
        self.assertEqual(msg.get_charset(), Charset('utf-8'))
        self.assertEqual(msg.get('Content-Type'),
                         'text/plain; charset="utf-8"')
Exemple #8
0
    def append(self, s, charset=None, errors='strict'):
        if charset is None:
            charset = self._charset
        elif not isinstance(charset, Charset):
            charset = Charset(charset)
        if charset != '8bit':
            if isinstance(s, str):
                incodec = charset.input_codec or 'us-ascii'
                ustr = unicode(s, incodec, errors)
                outcodec = charset.output_codec or 'us-ascii'
                ustr.encode(outcodec, errors)
            elif isinstance(s, unicode):
                for charset in (USASCII, charset, UTF8):
                    try:
                        outcodec = charset.output_codec or 'us-ascii'
                        s = s.encode(outcodec, errors)
                        break
                    except UnicodeError:
                        pass

        self._chunks.append((s, charset))
Exemple #9
0
    def init_payload(self):
        super().init_payload()
        self.all_recipients = []
        self.mime_message = self.message.message()

        # Work around an Amazon SES bug where, if all of:
        #   - the message body (text or html) contains non-ASCII characters
        #   - the body is sent with `Content-Transfer-Encoding: 8bit`
        #     (which is Django email's default for most non-ASCII bodies)
        #   - you are using an SES ConfigurationSet with open or click tracking enabled
        # then SES replaces the non-ASCII characters with question marks as it rewrites
        # the message to add tracking. Forcing `CTE: quoted-printable` avoids the problem.
        # (https://forums.aws.amazon.com/thread.jspa?threadID=287048)
        for part in self.mime_message.walk():
            if part.get_content_maintype() == "text" and part["Content-Transfer-Encoding"] == "8bit":
                content = part.get_payload()
                del part["Content-Transfer-Encoding"]
                qp_charset = Charset(part.get_content_charset("us-ascii"))
                qp_charset.body_encoding = QP
                # (can't use part.set_payload, because SafeMIMEText can undo this workaround)
                MIMEText.set_payload(part, content, charset=qp_charset)
Exemple #10
0
    def test_send_utf8(self):
        subject = "sübjèçt"
        body = "bödÿ-àéïöñß"
        mailsender = MailSender(debug=True)
        mailsender.send(
            to=["*****@*****.**"],
            subject=subject,
            body=body,
            charset="utf-8",
            _callback=self._catch_mail_sent,
        )

        assert self.catched_msg
        self.assertEqual(self.catched_msg["subject"], subject)
        self.assertEqual(self.catched_msg["body"], body)

        msg = self.catched_msg["msg"]
        self.assertEqual(msg["subject"], subject)
        self.assertEqual(msg.get_payload(), body)
        self.assertEqual(msg.get_charset(), Charset("utf-8"))
        self.assertEqual(msg.get("Content-Type"),
                         'text/plain; charset="utf-8"')
Exemple #11
0
    def send_mail(self, mail_template, in_reply_to = None, **kwargs):
        msgid = make_msgid()

        subject = mail_template['subject'].format(**kwargs)
        message = mail_template['message'].format(**kwargs)

        msg = MIMEMultipart('alternative')
        msg['Subject'] = str(Header(subject, 'utf-8'))
        msg['From'] = str(Header(SMTP_FROM, 'utf-8'))
        msg['To'] = str(Header(self.email, 'utf-8'))
        msg['Message-ID'] = msgid
        msg['Reply-To'] = SMTP_REPLY_TO_EMAIL
        msg['Date'] = datetime.datetime.now(pytz.utc).strftime("%a, %e %b %Y %T %z")

        if in_reply_to:
            msg['In-Reply-To'] = in_reply_to
            msg['References'] = in_reply_to

        # add message
        charset = Charset('utf-8')
        # QP = quoted printable; this is better readable instead of base64, when
        # the mail is read in plaintext!
        charset.body_encoding = QP
        message_part = MIMEText(message.encode('utf-8'), 'plain', charset)
        msg.attach(message_part)

        if DEBUG:
            with open("/tmp/keepitup_mails.log", "a") as f:
                f.write(msg.as_string() + "\n")
        else:
            with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
                server.ehlo()
                if SMTP_USE_STARTTLS:
                    context = ssl.create_default_context()
                    server.starttls(context=context)

                server.sendmail(SMTP_FROM, self.email, msg.as_string())

        return msgid
Exemple #12
0
    def append(self, s, charset=None, errors='strict'):
        """Append a string to the MIME header.

        Optional charset, if given, should be a Charset instance or the name
        of a character set (which will be converted to a Charset instance).  A
        value of None (the default) means that the charset given in the
        constructor is used.

        s may be a byte string or a Unicode string.  If it is a byte string
        (i.e. isinstance(s, str) is true), then charset is the encoding of
        that byte string, and a UnicodeError will be raised if the string
        cannot be decoded with that charset.  If s is a Unicode string, then
        charset is a hint specifying the character set of the characters in
        the string.  In this case, when producing an RFC 2822 compliant header
        using RFC 2047 rules, the Unicode string will be encoded using the
        following charsets in order: us-ascii, the charset hint, utf-8.  The
        first character set not to provoke a UnicodeError is used.

        Optional `errors' is passed as the third argument to any unicode() or
        ustr.encode() call.
        """
        if charset is None:
            charset = self._charset
        elif not isinstance(charset, Charset):
            charset = Charset(charset)
        if isinstance(s, str):
            # Convert the string from the input character set to the output
            # character set and store the resulting bytes and the charset for
            # composition later.
            input_charset = charset.input_codec or 'us-ascii'
            input_bytes = s.encode(input_charset, errors)
        else:
            # We already have the bytes we will store internally.
            input_bytes = s
        # Ensure that the bytes we're storing can be decoded to the output
        # character set, otherwise an early error is thrown.
        output_charset = charset.output_codec or 'us-ascii'
        output_string = input_bytes.decode(output_charset, errors)
        self._chunks.append((output_string, charset))
Exemple #13
0
def build_message(
    subject,
    from_email,
    html,
    to_recipients=[],
    bcc_recipients=[],
    attachments=[],
    headers=[],
):
    """Build raw email for sending."""
    message = MIMEMultipart("alternative")
    cs = Charset("utf-8")
    cs.body_encoding = QP

    message["Subject"] = subject
    message["From"] = from_email

    if to_recipients:
        message["To"] = ",".join(to_recipients)
    if bcc_recipients:
        message["Bcc"] = ",".join(bcc_recipients)

    text = get_text_from_html(html)
    plain_text = MIMEText(text, "plain", cs)
    message.attach(plain_text)

    html_text = MIMEText(html, "html", cs)
    message.attach(html_text)

    for header in headers:
        message[header["key"]] = header["value"]

    for filename in attachments:
        with open(filename, "rb") as attachment:
            part = MIMEApplication(attachment.read())
            part.add_header("Content-Disposition", "attachment", filename="report.pdf")
        message.attach(part)

    return message.as_string()
Exemple #14
0
    def __init__(self,
                 encoding="utf-8",
                 templates=None,
                 from_addr="*****@*****.**",
                 from_name="System",
                 server_factory=smtplib.SMTP):
        """initialize the mail API.

        :param server: an smtplib.SMTP server or a component implementing ``connect()``, ``sendmail()`` and ``quit()``
        :param encoding: the encoding to use for emails
        :param templates: The jinja2 template environment to use
        :param from_addr: the full name of the sender
        """
        self.templates = templates
        self.from_addr = from_addr
        self.from_name = from_name

        self.charset = Charset("utf-8")
        self.charset.header_encoding = QP
        self.charset.body_encoding = QP

        self.server_factory = server_factory
 def __setstate__(self, d):
     # The base class attributes have changed over time.  Which could
     # affect Mailman if messages are sitting in the queue at the time of
     # upgrading the email package.  We shouldn't burden email with this,
     # so we handle schema updates here.
     self.__dict__ = d
     # We know that email 2.4.3 is up-to-date
     version = d.get('__version__', (0, 0, 0))
     d['__version__'] = VERSION
     if version >= VERSION:
         return
     # Messages grew a _charset attribute between email version 0.97 and 1.1
     if '_charset' not in d:
         self._charset = None
     # Messages grew a _default_type attribute between v2.1 and v2.2
     if '_default_type' not in d:
         # We really have no idea whether this message object is contained
         # inside a multipart/digest or not, so I think this is the best we
         # can do.
         self._default_type = 'text/plain'
     # Header instances used to allow both strings and Charsets in their
     # _chunks, but by email 2.4.3 now it's just Charsets.
     headers = []
     hchanged = 0
     for k, v in self._headers:
         if isinstance(v, Header):
             chunks = []
             cchanged = 0
             for s, charset in v._chunks:
                 if isinstance(charset, str):
                     charset = Charset(charset)
                     cchanged = 1
                 chunks.append((s, charset))
             if cchanged:
                 v._chunks = chunks
                 hchanged = 1
         headers.append((k, v))
     if hchanged:
         self._headers = headers
Exemple #16
0
    def set_charset(self, charset):
        """Set the charset of the payload to a given character set.

        charset can be a Charset instance, a string naming a character set, or
        None.  If it is a string it will be converted to a Charset instance.
        If charset is None, the charset parameter will be removed from the
        Content-Type field.  Anything else will generate a TypeError.

        The message will be assumed to be of type text/* encoded with
        charset.input_charset.  It will be converted to charset.output_charset
        and encoded properly, if needed, when generating the plain text
        representation of the message.  MIME headers (MIME-Version,
        Content-Type, Content-Transfer-Encoding) will be added as needed.
        """
        if charset is None:
            self.del_param('charset')
            self._charset = None
            return
        if not isinstance(charset, Charset):
            charset = Charset(charset)
        self._charset = charset
        if 'MIME-Version' not in self:
            self.add_header('MIME-Version', '1.0')
        if 'Content-Type' not in self:
            self.add_header('Content-Type',
                            'text/plain',
                            charset=charset.get_output_charset())
        else:
            self.set_param('charset', charset.get_output_charset())
        if charset != charset.get_output_charset():
            self._payload = charset.body_encode(self._payload)
        if 'Content-Transfer-Encoding' not in self:
            cte = charset.get_body_encoding()
            try:
                cte(self)
            except TypeError:
                self._payload = charset.body_encode(self._payload)
                self.add_header('Content-Transfer-Encoding', cte)
Exemple #17
0
 def __init__(self,
              s=None,
              charset=None,
              maxlinelen=None,
              header_name=None,
              continuation_ws=' ',
              errors='strict'):
     if charset is None:
         charset = USASCII
     if not isinstance(charset, Charset):
         charset = Charset(charset)
     self._charset = charset
     self._continuation_ws = continuation_ws
     cws_expanded_len = len(continuation_ws.replace('\t', SPACE8))
     self._chunks = []
     if s is not None:
         self.append(s, charset, errors)
     if maxlinelen is None:
         maxlinelen = MAXLINELEN
     if header_name is None:
         self._firstlinelen = maxlinelen
     else:
         self._firstlinelen = maxlinelen - len(header_name) - 2
     self._maxlinelen = maxlinelen - cws_expanded_len
Exemple #18
0
 def _init_pref_encoding(self):
     self._charset = Charset()
     self._charset.input_charset = 'utf-8'
     pref = self.mime_encoding.lower()
     if pref == 'base64':
         self._charset.header_encoding = BASE64
         self._charset.body_encoding = BASE64
         self._charset.output_charset = 'utf-8'
         self._charset.input_codec = 'utf-8'
         self._charset.output_codec = 'utf-8'
     elif pref in ['qp', 'quoted-printable']:
         self._charset.header_encoding = QP
         self._charset.body_encoding = QP
         self._charset.output_charset = 'utf-8'
         self._charset.input_codec = 'utf-8'
         self._charset.output_codec = 'utf-8'
     elif pref == 'none':
         self._charset.header_encoding = None
         self._charset.body_encoding = None
         self._charset.input_codec = None
         self._charset.output_charset = 'ascii'
     else:
         raise TracError(_("Invalid email encoding setting: %(pref)s",
                           pref=pref))
Exemple #19
0
def sendmail(subject,
             text,
             to=None,
             cc=None,
             bcc=None,
             mail_from=None,
             html=None):
    """ Create and send a text/plain message

    Return a tuple of success or error indicator and message.

    :param subject: subject of email
    :type subject: unicode
    :param text: email body text
    :type text: unicode
    :param to: recipients
    :type to: list
    :param cc: recipients (CC)
    :type cc: list
    :param bcc: recipients (BCC)
    :type bcc: list
    :param mail_from: override default mail_from
    :type mail_from: unicode
    :param html: html email body text
    :type html: unicode

    :rtype: tuple
    :returns: (is_ok, Description of error or OK message)
    """
    import smtplib
    import socket
    from email.message import Message
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.charset import Charset, QP
    from email.utils import formatdate, make_msgid

    cfg = app.cfg
    if not cfg.mail_enabled:
        return (
            0,
            _("Contact administrator: cannot send password recovery e-mail "
              "because mail configuration is incomplete."))
    mail_from = mail_from or cfg.mail_from

    logging.debug("send mail, from: {0!r}, subj: {1!r}".format(
        mail_from, subject))
    logging.debug("send mail, to: {0!r}".format(to))

    if not to and not cc and not bcc:
        return 1, _("No recipients, nothing to do")

    subject = subject.encode(CHARSET)

    # Create a text/plain body using CRLF (see RFC2822)
    text = text.replace(u'\n', u'\r\n')
    text = text.encode(CHARSET)

    # Create a message using CHARSET and quoted printable
    # encoding, which should be supported better by mail clients.
    # TODO: check if its really works better for major mail clients
    text_msg = Message()
    charset = Charset(CHARSET)
    charset.header_encoding = QP
    charset.body_encoding = QP
    text_msg.set_charset(charset)

    # work around a bug in python 2.4.3 and above:
    text_msg.set_payload('=')
    if text_msg.as_string().endswith('='):
        text = charset.body_encode(text)

    text_msg.set_payload(text)

    if html:
        msg = MIMEMultipart('alternative')
        msg.attach(text_msg)
        html = html.encode(CHARSET)
        html_msg = MIMEText(html, 'html')
        html_msg.set_charset(charset)
        msg.attach(html_msg)
    else:
        msg = text_msg

    address = encodeAddress(mail_from, charset)
    msg['From'] = address
    if to:
        msg['To'] = ','.join(to)
    if cc:
        msg['CC'] = ','.join(cc)
    msg['Date'] = formatdate()
    msg['Message-ID'] = make_msgid()
    msg['Subject'] = Header(subject, charset)
    # See RFC 3834 section 5:
    msg['Auto-Submitted'] = 'auto-generated'

    if cfg.mail_sendmail:
        if bcc:
            # Set the BCC.  This will be stripped later by sendmail.
            msg['BCC'] = ','.join(bcc)
        # Set Return-Path so that it isn't set (generally incorrectly) for us.
        msg['Return-Path'] = address

    # Send the message
    if not cfg.mail_sendmail:
        try:
            logging.debug(
                "trying to send mail (smtp) via smtp server '{0}'".format(
                    cfg.mail_smarthost))
            host, port = (cfg.mail_smarthost + ':25').split(':')[:2]
            server = smtplib.SMTP(host, int(port))
            try:
                # server.set_debuglevel(1)
                if cfg.mail_username is not None and cfg.mail_password is not None:
                    try:  # try to do TLS
                        server.ehlo()
                        if server.has_extn('starttls'):
                            server.starttls()
                            server.ehlo()
                            logging.debug(
                                "tls connection to smtp server established")
                    except Exception:
                        logging.debug(
                            "could not establish a tls connection to smtp server, continuing without tls"
                        )
                    logging.debug(
                        "trying to log in to smtp server using account '{0}'".
                        format(cfg.mail_username))
                    server.login(cfg.mail_username, cfg.mail_password)
                server.sendmail(mail_from,
                                (to or []) + (cc or []) + (bcc or []),
                                msg.as_string())
            finally:
                try:
                    server.quit()
                except AttributeError:
                    # in case the connection failed, SMTP has no "sock" attribute
                    pass
        except smtplib.SMTPException as e:
            logging.exception("smtp mail failed with an exception.")
            return 0, str(e)
        except (os.error, socket.error) as e:
            logging.exception("smtp mail failed with an exception.")
            return (
                0,
                _("Connection to mailserver '%(server)s' failed: %(reason)s",
                  server=cfg.mail_smarthost,
                  reason=str(e)))
    else:
        try:
            logging.debug("trying to send mail (sendmail)")
            sendmailp = os.popen(cfg.mail_sendmail, "w")
            # msg contains everything we need, so this is a simple write
            sendmailp.write(msg.as_string())
            sendmail_status = sendmailp.close()
            if sendmail_status:
                logging.error("sendmail failed with status: {0!s}".format(
                    sendmail_status))
                return 0, str(sendmail_status)
        except Exception:
            logging.exception("sendmail failed with an exception.")
            return 0, _("Mail not sent")

    logging.debug("Mail sent successfully")
    return 1, _("Mail sent successfully")
def process(mlist, msg, msgdata=None):
    sanitize = mm_cfg.ARCHIVE_HTML_SANITIZER
    outer = True
    if msgdata is None:
        msgdata = {}
    if msgdata:
        # msgdata is available if it is in GLOBAL_PIPELINE
        # ie. not in digest or archiver
        # check if the list owner want to scrub regular delivery
        if not mlist.scrub_nondigest:
            return
    dir = calculate_attachments_dir(mlist, msg, msgdata)
    charset = None
    lcset = Utils.GetCharSet(mlist.preferred_language)
    lcset_out = Charset(lcset).output_charset or lcset
    # Now walk over all subparts of this message and scrub out various types
    format = delsp = None
    for part in msg.walk():
        ctype = part.get_content_type()
        # If the part is text/plain, we leave it alone
        if ctype == 'text/plain':
            # We need to choose a charset for the scrubbed message, so we'll
            # arbitrarily pick the charset of the first text/plain part in the
            # message.
            # MAS: Also get the RFC 3676 stuff from this part. This seems to
            # work OK for scrub_nondigest.  It will also work as far as
            # scrubbing messages for the archive is concerned, but pipermail
            # doesn't pay any attention to the RFC 3676 parameters.  The plain
            # format digest is going to be a disaster in any case as some of
            # messages will be format="flowed" and some not.  ToDigest creates
            # its own Content-Type: header for the plain digest which won't
            # have RFC 3676 parameters. If the message Content-Type: headers
            # are retained for display in the digest, the parameters will be
            # there for information, but not for the MUA. This is the best we
            # can do without having get_payload() process the parameters.
            if charset is None:
                charset = part.get_content_charset(lcset)
                format = part.get_param('format')
                delsp = part.get_param('delsp')
            # TK: if part is attached then check charset and scrub if none
            if part.get('content-disposition') and \
               not part.get_content_charset():
                omask = os.umask(0o02)
                try:
                    url = save_attachment(mlist, part, dir)
                finally:
                    os.umask(omask)
                filename = part.get_filename(_('not available'))
                filename = Utils.oneline(filename, lcset)
                replace_payload_by_text(
                    part,
                    _("""\
An embedded and charset-unspecified text was scrubbed...
Name: %(filename)s
URL: %(url)s
"""), lcset)
        elif ctype == 'text/html' and isinstance(sanitize, int):
            if sanitize == 0:
                if outer:
                    raise DiscardMessage
                replace_payload_by_text(
                    part,
                    _('HTML attachment scrubbed and removed'),
                    # Adding charset arg and removing content-type
                    # sets content-type to text/plain
                    lcset)
            elif sanitize == 2:
                # By leaving it alone, Pipermail will automatically escape it
                pass
            elif sanitize == 3:
                # Pull it out as an attachment but leave it unescaped.  This
                # is dangerous, but perhaps useful for heavily moderated
                # lists.
                omask = os.umask(0o02)
                try:
                    url = save_attachment(mlist, part, dir, filter_html=False)
                finally:
                    os.umask(omask)
                replace_payload_by_text(
                    part,
                    _("""\
An HTML attachment was scrubbed...
URL: %(url)s
"""), lcset)
            else:
                # HTML-escape it and store it as an attachment, but make it
                # look a /little/ bit prettier. :(
                payload = Utils.websafe(part.get_payload(decode=True))

                # For whitespace in the margin, change spaces into
                # non-breaking spaces, and tabs into 8 of those.  Then use a
                # mono-space font.  Still looks hideous to me, but then I'd
                # just as soon discard them.
                def doreplace(s):
                    return s.expandtabs(8).replace(' ', '&nbsp;')

                lines = [doreplace(s) for s in payload.split('\n')]
                payload = '<tt>\n' + BR.join(lines) + '\n</tt>\n'
                part.set_payload(payload)
                # We're replacing the payload with the decoded payload so this
                # will just get in the way.
                del part['content-transfer-encoding']
                omask = os.umask(0o02)
                try:
                    url = save_attachment(mlist, part, dir, filter_html=False)
                finally:
                    os.umask(omask)
                replace_payload_by_text(
                    part,
                    _("""\
An HTML attachment was scrubbed...
URL: %(url)s
"""), lcset)
        elif ctype == 'message/rfc822':
            # This part contains a submessage, so it too needs scrubbing
            submsg = part.get_payload(0)
            omask = os.umask(0o02)
            try:
                url = save_attachment(mlist, part, dir)
            finally:
                os.umask(omask)
            subject = submsg.get('subject', _('no subject'))
            subject = Utils.oneline(subject, lcset)
            date = submsg.get('date', _('no date'))
            who = submsg.get('from', _('unknown sender'))
            size = len(str(submsg))
            replace_payload_by_text(
                part,
                _("""\
An embedded message was scrubbed...
From: %(who)s
Subject: %(subject)s
Date: %(date)s
Size: %(size)s
URL: %(url)s
"""), lcset)
        # If the message isn't a multipart, then we'll strip it out as an
        # attachment that would have to be separately downloaded.  Pipermail
        # will transform the url into a hyperlink.
        elif part.get_payload() and not part.is_multipart():
            payload = part.get_payload(decode=True)
            ctype = part.get_content_type()
            # XXX Under email 2.5, it is possible that payload will be None.
            # This can happen when you have a Content-Type: multipart/* with
            # only one part and that part has two blank lines between the
            # first boundary and the end boundary.  In email 3.0 you end up
            # with a string in the payload.  I think in this case it's safe to
            # ignore the part.
            if payload is None:
                continue
            size = len(payload)
            omask = os.umask(0o02)
            try:
                url = save_attachment(mlist, part, dir)
            finally:
                os.umask(omask)
            desc = part.get('content-description', _('not available'))
            desc = Utils.oneline(desc, lcset)
            filename = part.get_filename(_('not available'))
            filename = Utils.oneline(filename, lcset)
            replace_payload_by_text(
                part,
                _("""\
A non-text attachment was scrubbed...
Name: %(filename)s
Type: %(ctype)s
Size: %(size)d bytes
Desc: %(desc)s
URL: %(url)s
"""), lcset)
        outer = False
    # We still have to sanitize multipart messages to flat text because
    # Pipermail can't handle messages with list payloads.  This is a kludge;
    # def (n) clever hack ;).
    if msg.is_multipart():
        # By default we take the charset of the first text/plain part in the
        # message, but if there was none, we'll use the list's preferred
        # language's charset.
        if not charset or charset == 'us-ascii':
            charset = lcset_out
        else:
            # normalize to the output charset if input/output are different
            charset = Charset(charset).output_charset or charset
        # We now want to concatenate all the parts which have been scrubbed to
        # text/plain, into a single text/plain payload.  We need to make sure
        # all the characters in the concatenated string are in the same
        # encoding, so we'll use the 'replace' key in the coercion call.
        # BAW: Martin's original patch suggested we might want to try
        # generalizing to utf-8, and that's probably a good idea (eventually).
        text = []
        for part in msg.walk():
            # TK: bug-id 1099138 and multipart
            # MAS test payload - if part may fail if there are no headers.
            if not part.get_payload() or part.is_multipart():
                continue
            # All parts should be scrubbed to text/plain by now, except
            # if sanitize == 2, there could be text/html parts so keep them
            # but skip any other parts.
            partctype = part.get_content_type()
            if partctype != 'text/plain' and (partctype != 'text/html'
                                              or sanitize != 2):
                text.append(_('Skipped content of type %(partctype)s\n'))
                continue
            try:
                t = part.get_payload(decode=True) or ''
            # MAS: TypeError exception can occur if payload is None. This
            # was observed with a message that contained an attached
            # message/delivery-status part. Because of the special parsing
            # of this type, this resulted in a text/plain sub-part with a
            # null body. See bug 1430236.
            except (binascii.Error, TypeError):
                t = part.get_payload() or ''
            # TK: get_content_charset() returns 'iso-2022-jp' for internally
            # crafted (scrubbed) 'euc-jp' text part. So, first try
            # get_charset(), then get_content_charset() for the parts
            # which are already embeded in the incoming message.
            partcharset = part.get_charset()
            if partcharset:
                partcharset = str(partcharset)
            else:
                partcharset = part.get_content_charset()
            if partcharset and partcharset != charset:
                try:
                    t = str(t, partcharset, 'replace')
                except (UnicodeError, LookupError, ValueError, AssertionError):
                    # We can get here if partcharset is bogus in come way.
                    # Replace funny characters.  We use errors='replace'
                    t = str(t, 'ascii', 'replace')
                try:
                    # Should use HTML-Escape, or try generalizing to UTF-8
                    t = t.encode(charset, 'replace')
                except (UnicodeError, LookupError, ValueError, AssertionError):
                    # if the message charset is bogus, use the list's.
                    t = t.encode(lcset, 'replace')
            # Separation is useful
            if isinstance(t, str):
                if not t.endswith('\n'):
                    t += '\n'
                text.append(t)
        # Now join the text and set the payload
        sep = _('-------------- next part --------------\n')
        # The i18n separator is in the list's charset. Coerce it to the
        # message charset.
        try:
            s = str(sep, lcset, 'replace')
            sep = s.encode(charset, 'replace')
        except (UnicodeError, LookupError, ValueError, AssertionError):
            pass
        replace_payload_by_text(msg, sep.join(text), charset)
        if format:
            msg.set_param('Format', format)
        if delsp:
            msg.set_param('DelSp', delsp)
    return msg
Exemple #21
0
    def sendMailMessage(self, xMailMessage):
        COMMASPACE = ', '

        if dbg:
            print("PyMailSMTPService sendMailMessage", file=dbgout)
        recipients = xMailMessage.getRecipients()
        sendermail = xMailMessage.SenderAddress
        sendername = xMailMessage.SenderName
        subject = xMailMessage.Subject
        ccrecipients = xMailMessage.getCcRecipients()
        bccrecipients = xMailMessage.getBccRecipients()
        if dbg:
            print("PyMailSMTPService subject: " + subject, file=dbgout)
            print("PyMailSMTPService from:  " + sendername, file=dbgout)
            print("PyMailSMTPService from:  " + sendermail, file=dbgout)
            print("PyMailSMTPService send to: %s" % (recipients, ),
                  file=dbgout)

        attachments = xMailMessage.getAttachments()

        textmsg = Message()

        content = xMailMessage.Body
        flavors = content.getTransferDataFlavors()
        if dbg:
            print("PyMailSMTPService flavors len: %d" % (len(flavors), ),
                  file=dbgout)

        #Use first flavor that's sane for an email body
        for flavor in flavors:
            if flavor.MimeType.find('text/html') != -1 or flavor.MimeType.find(
                    'text/plain') != -1:
                if dbg:
                    print("PyMailSMTPService mimetype is: " + flavor.MimeType,
                          file=dbgout)
                textbody = content.getTransferData(flavor)

                if len(textbody):
                    mimeEncoding = re.sub("charset=.*", "charset=UTF-8",
                                          flavor.MimeType)
                    if mimeEncoding.find('charset=UTF-8') == -1:
                        mimeEncoding = mimeEncoding + "; charset=UTF-8"
                    textmsg['Content-Type'] = mimeEncoding
                    textmsg['MIME-Version'] = '1.0'

                    try:
                        #it's a string, get it as utf-8 bytes
                        textbody = textbody.encode('utf-8')
                    except:
                        #it's a bytesequence, get raw bytes
                        textbody = textbody.value
                    textbody = textbody.decode('utf-8')
                    c = Charset('utf-8')
                    c.body_encoding = QP
                    textmsg.set_payload(textbody, c)

                break

        if (len(attachments)):
            msg = MIMEMultipart()
            msg.epilogue = ''
            msg.attach(textmsg)
        else:
            msg = textmsg

        hdr = Header(sendername, 'utf-8')
        hdr.append('<' + sendermail + '>', 'us-ascii')
        msg['Subject'] = subject
        msg['From'] = hdr
        msg['To'] = COMMASPACE.join(recipients)
        if len(ccrecipients):
            msg['Cc'] = COMMASPACE.join(ccrecipients)
        if xMailMessage.ReplyToAddress != '':
            msg['Reply-To'] = xMailMessage.ReplyToAddress

        mailerstring = "LibreOffice via Caolan's mailmerge component"
        try:
            ctx = uno.getComponentContext()
            aConfigProvider = ctx.ServiceManager.createInstance(
                "com.sun.star.configuration.ConfigurationProvider")
            prop = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
            prop.Name = "nodepath"
            prop.Value = "/org.openoffice.Setup/Product"
            aSettings = aConfigProvider.createInstanceWithArguments(
                "com.sun.star.configuration.ConfigurationAccess", (prop, ))
            mailerstring = aSettings.getByName("ooName") + " " + \
             aSettings.getByName("ooSetupVersion") + " via Caolan's mailmerge component"
        except:
            pass

        msg['X-Mailer'] = mailerstring
        msg['Date'] = formatdate(localtime=True)

        for attachment in attachments:
            content = attachment.Data
            flavors = content.getTransferDataFlavors()
            flavor = flavors[0]
            ctype = flavor.MimeType
            maintype, subtype = ctype.split('/', 1)
            msgattachment = MIMEBase(maintype, subtype)
            data = content.getTransferData(flavor)
            msgattachment.set_payload(data.value)
            encode_base64(msgattachment)
            fname = attachment.ReadableName
            try:
                msgattachment.add_header('Content-Disposition', 'attachment', \
                 filename=fname)
            except:
                msgattachment.add_header('Content-Disposition', 'attachment', \
                 filename=('utf-8','',fname))
            if dbg:
                print(("PyMailSMTPService attachmentheader: ",
                       str(msgattachment)),
                      file=dbgout)

            msg.attach(msgattachment)

        uniquer = {}
        for key in recipients:
            uniquer[key] = True
        if len(ccrecipients):
            for key in ccrecipients:
                uniquer[key] = True
        if len(bccrecipients):
            for key in bccrecipients:
                uniquer[key] = True
        truerecipients = uniquer.keys()

        if dbg:
            print(("PyMailSMTPService recipients are: ", truerecipients),
                  file=dbgout)

        self.server.sendmail(sendermail, truerecipients, msg.as_string())
Exemple #22
0
def fix_text_required(encodingname):
    from email.charset import Charset, BASE64, QP

    charset = Charset(encodingname)
    bodyenc = charset.body_encoding
    return bodyenc in (None, QP)
Exemple #23
0
def send_email(smtp, recipients, cc, subject, content, csv_reports):
    """Sends an email with attachment.
    Refer to https://gist.github.com/BietteMaxime/f75ae41f7b4557274a9f

    Args:
        smtp: A dictionary containing smtp info:
            - smtp_url
            - smtp_auth_username # optional
            - smtp_auth_password # optional
            - smtp_from
        recipients: To whom to send the email.
        cc: To whom to cc the email.
        subject: Email subject.
        content: Email body content
        csv_reports: List of dictionaries containing "filename", "data" to
            construct CSV attachments.

    Returns:
        None
    """
    if not isinstance(smtp, dict):
        logger.warning("smtp is not a dictionary. Skip.")
        return

    sender = smtp.get("smtp_from", None)
    smtp_url = smtp.get("smtp_url", None)
    smtp_auth_username = smtp.get("smtp_auth_username", None)
    smtp_auth_password = smtp.get("smtp_auth_password", None)
    if sender is None or smtp_url is None:
        logger.warning("Some fields in smtp %s is None. Skip.", smtp)
        return

    # Create message container - the correct MIME type is multipart/mixed
    # to allow attachment.
    full_email = MIMEMultipart("mixed")
    full_email["Subject"] = subject
    full_email["From"] = sender
    full_email["To"] = ", ".join(recipients)
    full_email["CC"] = ", ".join(cc)

    # Create the body of the message (a plain-text version).
    content = content.encode(ENCODING)
    content = MIMEText(content, "plain", _charset=ENCODING)
    full_email.attach(content)

    # Create the attachment of the message in text/csv.
    for report in csv_reports:
        attachment = MIMENonMultipart("text", "csv", charset=ENCODING)
        attachment.add_header("Content-Disposition",
                              "attachment",
                              filename=report["filename"])
        cs = Charset(ENCODING)
        cs.body_encoding = BASE64
        attachment.set_payload(report["data"].encode(ENCODING), charset=cs)
        full_email.attach(attachment)

    try:
        with smtplib.SMTP(smtp_url) as server:
            if smtp_auth_username is not None and smtp_auth_password is not None:
                server.starttls()
                server.login(smtp_auth_username, smtp_auth_password)

            receivers = recipients + cc
            server.sendmail(sender, receivers, full_email.as_string())
            logger.info("Successfully sent email to %s and cc %s",
                        ", ".join(recipients), ", ".join(cc))
    except smtplib.SMTPAuthenticationError:
        logger.warning("The server didn\'t accept the user\\password "
                       "combination.")
    except smtplib.SMTPServerDisconnected:
        logger.warning("Server unexpectedly disconnected")
    except smtplib.SMTPException as e:
        logger.exception("SMTP error occurred: %s", e)
Exemple #24
0
    def sendMailMessage(self, xMailMessage):
        COMMASPACE = ', '

        if dbg:
            print("PyMailSMTPService sendMailMessage", file=dbgout)
        recipients = xMailMessage.getRecipients()
        sendermail = xMailMessage.SenderAddress
        sendername = xMailMessage.SenderName
        subject = xMailMessage.Subject
        ccrecipients = xMailMessage.getCcRecipients()
        bccrecipients = xMailMessage.getBccRecipients()
        if dbg:
            print("PyMailSMTPService subject: " + subject, file=dbgout)
            print("PyMailSMTPService from:  " + sendername, file=dbgout)
            print("PyMailSMTPService from:  " + sendermail, file=dbgout)
            print("PyMailSMTPService send to: %s" % (recipients, ),
                  file=dbgout)

        attachments = xMailMessage.getAttachments()

        textmsg = Message()

        content = xMailMessage.Body
        flavors = content.getTransferDataFlavors()
        if dbg:
            print("PyMailSMTPService flavors len: %d" % (len(flavors), ),
                  file=dbgout)

        #Use first flavor that's sane for an email body
        for flavor in flavors:
            if flavor.MimeType.find('text/html') != -1 or flavor.MimeType.find(
                    'text/plain') != -1:
                if dbg:
                    print("PyMailSMTPService mimetype is: " + flavor.MimeType,
                          file=dbgout)
                textbody = content.getTransferData(flavor)

                if len(textbody):
                    mimeEncoding = re.sub("charset=.*", "charset=UTF-8",
                                          flavor.MimeType)
                    if mimeEncoding.find('charset=UTF-8') == -1:
                        mimeEncoding = mimeEncoding + "; charset=UTF-8"
                    textmsg['Content-Type'] = mimeEncoding
                    textmsg['MIME-Version'] = '1.0'

                    try:
                        #it's a string, get it as utf-8 bytes
                        textbody = textbody.encode('utf-8')
                    except:
                        #it's a bytesequence, get raw bytes
                        textbody = textbody.value
                    if sys.version >= '3':
                        if sys.version_info.minor < 3 or (
                                sys.version_info.minor == 3
                                and sys.version_info.micro <= 1):
                            #http://stackoverflow.com/questions/9403265/how-do-i-use-python-3-2-email-module-to-send-unicode-messages-encoded-in-utf-8-w
                            #see http://bugs.python.org/16564, etc. basically it now *seems* to be all ok
                            #in python 3.3.2 onwards, but a little busted in 3.3.0

                            textbody = textbody.decode('iso8859-1')
                        else:
                            textbody = textbody.decode('utf-8')
                        c = Charset('utf-8')
                        c.body_encoding = QP
                        textmsg.set_payload(textbody, c)
                    else:
                        textmsg.set_payload(textbody)

                break

        if (len(attachments)):
            msg = MIMEMultipart()
            msg.epilogue = ''
            msg.attach(textmsg)
        else:
            msg = textmsg

        hdr = Header(sendername, 'utf-8')
        hdr.append('<' + sendermail + '>', 'us-ascii')
        msg['Subject'] = subject
        msg['From'] = hdr
        msg['To'] = COMMASPACE.join(recipients)
        if len(ccrecipients):
            msg['Cc'] = COMMASPACE.join(ccrecipients)
        if xMailMessage.ReplyToAddress != '':
            msg['Reply-To'] = xMailMessage.ReplyToAddress

        mailerstring = "LibreOffice via Caolan's mailmerge component"
        try:
            configuration = self._getConfiguration(
                "/org.openoffice.Setup/Product")
            mailerstring = "%s %s via Caolan's mailmerge component" % (
                configuration.getByName("ooName"),
                configuration.getByName("ooSetupVersion"))
        except:
            pass

        msg['X-Mailer'] = mailerstring
        msg['Date'] = formatdate(localtime=True)

        for attachment in attachments:
            content = attachment.Data
            flavors = content.getTransferDataFlavors()
            flavor = flavors[0]
            ctype = flavor.MimeType
            maintype, subtype = ctype.split('/', 1)
            msgattachment = MIMEBase(maintype, subtype)
            data = content.getTransferData(flavor)
            msgattachment.set_payload(data.value)
            encode_base64(msgattachment)
            fname = attachment.ReadableName
            try:
                msgattachment.add_header('Content-Disposition', 'attachment', \
                    filename=fname)
            except:
                msgattachment.add_header('Content-Disposition', 'attachment', \
                    filename=('utf-8','',fname))
            if dbg:
                print(("PyMailSMTPService attachmentheader: ",
                       str(msgattachment)),
                      file=dbgout)

            msg.attach(msgattachment)

        uniquer = {}
        for key in recipients:
            uniquer[key] = True
        if len(ccrecipients):
            for key in ccrecipients:
                uniquer[key] = True
        if len(bccrecipients):
            for key in bccrecipients:
                uniquer[key] = True
        truerecipients = uniquer.keys()

        if dbg:
            print(("PyMailSMTPService recipients are: ", truerecipients),
                  file=dbgout)

        self.server.sendmail(sendermail, truerecipients, msg.as_string())
Exemple #25
0
    def as_message(self, escape_addresses=True):
        # http://wordeology.com/computer/how-to-send-good-unicode-email-with-python.html
        # http://stackoverflow.com/questions/31714221/how-to-send-an-email-with-quoted
        # http://stackoverflow.com/questions/9403265/how-do-i-use-python/9509718#9509718
        charset = Charset('utf-8')
        charset.header_encoding = QP
        charset.body_encoding = QP
        msg = MIMEMultipart()

        # Headers
        unixfrom = "From %s %s" % (self.sender.address,
                                   self.archived_date.strftime("%c"))
        header_from = self.sender.address
        if self.sender_name and self.sender_name != self.sender.address:
            header_from = "%s <%s>" % (self.sender_name, header_from)
        header_to = self.mailinglist.name
        if escape_addresses:
            header_from = header_from.replace("@", " at ")
            header_to = header_to.replace("@", " at ")
            unixfrom = unixfrom.replace("@", " at ")
        msg.set_unixfrom(unixfrom)
        headers = (
            ("From", header_from),
            ("To", header_to),
            ("Subject", self.subject),
        )
        for header_name, header_value in headers:
            if not header_value:
                continue
            try:
                msg[header_name] = header_value.encode('ascii')
            except UnicodeEncodeError:
                msg[header_name] = Header(header_value.encode('utf-8'),
                                          charset).encode()
        tz = get_fixed_timezone(self.timezone)
        header_date = self.date.astimezone(tz).replace(microsecond=0)
        # Date format: http://tools.ietf.org/html/rfc5322#section-3.3
        msg["Date"] = header_date.strftime("%a, %d %b %Y %H:%M:%S %z")
        msg["Message-ID"] = "<%s>" % self.message_id
        if self.in_reply_to:
            msg["In-Reply-To"] = self.in_reply_to

        # Body
        content = self.ADDRESS_REPLACE_RE.sub(r"\1(a)\2", self.content)
        # Don't use MIMEText, it won't encode to quoted-printable
        textpart = MIMENonMultipart("text", "plain", charset='utf-8')
        textpart.set_payload(content, charset=charset)
        msg.attach(textpart)

        # Attachments
        for attachment in self.attachments.order_by("counter"):
            mimetype = attachment.content_type.split('/', 1)
            part = MIMEBase(mimetype[0], mimetype[1])
            part.set_payload(attachment.content)
            encode_base64(part)
            part.add_header('Content-Disposition',
                            'attachment',
                            filename=attachment.name)
            msg.attach(part)

        return msg
Exemple #26
0
# Functions for sending email
#
# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved.
# Email: [email protected]; WWW: http://www.mysociety.org/
#
# $Id: sendemail.py,v 1.5 2009/12/17 17:31:04 francis dead $
#

import re, smtplib
from minimock import mock, Mock
from email.message import Message
from email.header import Header
from email.utils import formataddr, make_msgid, formatdate
from email.charset import Charset, QP

charset = Charset('utf-8')
charset.body_encoding = QP


def send_email(sender, to, message, headers={}):
    """Sends MESSAGE from SENDER to TO, with HEADERS
    Returns True if successful, False if not
    
    >>> mock('smtplib.SMTP', returns=Mock('smtp_connection'))
    >>> send_email("[email protected]", "[email protected]", "Hello, this is a message!", {
    ...     'Subject': 'Mapumental message',
    ...     'From': ("[email protected]", "Ms. A"),
    ...     'To': "[email protected]"
    ... }) # doctest:+ELLIPSIS
    Called smtplib.SMTP('localhost')
    Called smtp_connection.sendmail(
Exemple #27
0
 def _contact(self, address):
     if isinstance(address, tuple):
         return formataddr((Charset('utf-8').header_encode(address[0]), address[1]))
     else:
         return address
Exemple #28
0
def verpdeliver(mlist, msg, msgdata, envsender, failures, conn):
    for recip in msgdata['recips']:
        # We now need to stitch together the message with its header and
        # footer.  If we're VERPIng, we have to calculate the envelope sender
        # for each recipient.  Note that the list of recipients must be of
        # length 1.
        #
        # BAW: ezmlm includes the message number in the envelope, used when
        # sending a notification to the user telling her how many messages
        # they missed due to bouncing.  Neat idea.
        msgdata['recips'] = [recip]
        # Make a copy of the message and decorate + delivery that
        msgcopy = copy.deepcopy(msg)
        Decorate.process(mlist, msgcopy, msgdata)
        # Calculate the envelope sender, which we may be VERPing
        if msgdata.get('verp'):
            bmailbox, bdomain = Utils.ParseEmail(envsender)
            rmailbox, rdomain = Utils.ParseEmail(recip)
            if rdomain is None:
                # The recipient address is not fully-qualified.  We can't
                # deliver it to this person, nor can we craft a valid verp
                # header.  I don't think there's much we can do except ignore
                # this recipient.
                syslog('smtp', 'Skipping VERP delivery to unqual recip: %s',
                       recip)
                continue
            d = {'bounces': bmailbox,
                 'mailbox': rmailbox,
                 'host'   : DOT.join(rdomain),
                 }
            envsender = '%s@%s' % ((mm_cfg.VERP_FORMAT % d), DOT.join(bdomain))
        if mlist.personalize == 2:
            # When fully personalizing, we want the To address to point to the
            # recipient, not to the mailing list
            del msgcopy['to']
            name = None
            if mlist.isMember(recip):
                name = mlist.getMemberName(recip)
            if name:
                # Convert the name to an email-safe representation.  If the
                # name is a byte string, convert it first to Unicode, given
                # the character set of the member's language, replacing bad
                # characters for which we can do nothing about.  Once we have
                # the name as Unicode, we can create a Header instance for it
                # so that it's properly encoded for email transport.
                charset = Utils.GetCharSet(mlist.getMemberLanguage(recip))
                if charset == 'us-ascii':
                    # Since Header already tries both us-ascii and utf-8,
                    # let's add something a bit more useful.
                    charset = 'iso-8859-1'
                charset = Charset(charset)
                codec = charset.input_codec or 'ascii'
                if not isinstance(name, str):
                    name = str(name, codec, 'replace')
                name = Header(name, charset).encode()
                msgcopy['To'] = formataddr((name, recip))
            else:
                msgcopy['To'] = recip
        # We can flag the mail as a duplicate for each member, if they've
        # already received this message, as calculated by Message-ID.  See
        # AvoidDuplicates.py for details.
        del msgcopy['x-mailman-copy']
        if recip in msgdata.get('add-dup-header', {}):
            msgcopy['X-Mailman-Copy'] = 'yes'
        # If desired, add the RCPT_BASE64_HEADER_NAME header
        if len(mm_cfg.RCPT_BASE64_HEADER_NAME) > 0:
            del msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME]
            msgcopy[mm_cfg.RCPT_BASE64_HEADER_NAME] = b64encode(recip)
        # For the final delivery stage, we can just bulk deliver to a party of
        # one. ;)
        bulkdeliver(mlist, msgcopy, msgdata, envsender, failures, conn)
#Embedded file name: email\header.py
"""Header encoding and decoding functionality."""
__all__ = ['Header', 'decode_header', 'make_header']
import re
import binascii
import email.quoprimime
import email.base64mime
from email.errors import HeaderParseError
from email.charset import Charset
NL = '\n'
SPACE = ' '
USPACE = u' '
SPACE8 = ' ' * 8
UEMPTYSTRING = u''
MAXLINELEN = 76
USASCII = Charset('us-ascii')
UTF8 = Charset('utf-8')
ecre = re.compile('\n  =\\?                   # literal =?\n  (?P<charset>[^?]*?)   # non-greedy up to the next ? is the charset\n  \\?                    # literal ?\n  (?P<encoding>[qb])    # either a "q" or a "b", case insensitive\n  \\?                    # literal ?\n  (?P<encoded>.*?)      # non-greedy up to the next ?= is the encoded string\n  \\?=                   # literal ?=\n  (?=[ \\t]|$)           # whitespace or the end of the string\n  ', re.VERBOSE | re.IGNORECASE | re.MULTILINE)
fcre = re.compile('[\\041-\\176]+:$')
_max_append = email.quoprimime._max_append

def decode_header(header):
    """Decode a message header value without converting charset.
    
    Returns a list of (decoded_string, charset) pairs containing each of the
    decoded parts of the header.  Charset is None for non-encoded parts of the
    header, otherwise a lower-case string containing the name of the character
    set specified in the encoded string.
    
    An email.errors.HeaderParseError may be raised when certain decoding error
    occurs (e.g. a base64 decoding exception).
Exemple #30
0
    def send(self, emails):
        if isinstance(emails, Email):
            emails = [emails]
        if len([e for e in emails if e.__class__ != Email]):
            raise TypeError('emails must be Email or list of Email instances')

        smtpclass = SMTP_SSL if self.ssl else SMTP
        if self.server == 'localhost':
            smtp = smtpclass(self.server)
        else:
            smtp = smtpclass(self.server, self.port)
        if self.login and self.password:
            smtp.login(self.login, self.password)
        for email in emails:
            c = Charset(email.charset)
            c.header_encoding = QP
            c.body_encoding = 0
            r = Charset(email.charset)
            r.header_encoding = 0
            r.body_encoding = 0

            mime1, mime2 = email.mimetype.split('/')
            mainpart = MIMEBase(mime1, mime2)
            if not email.force_7bit:
                mainpart.set_param('charset', email.charset)

            if len(email.attachments):
                message = MIMEMultipart('mixed')
                message.attach(mainpart)
                del mainpart['mime-version']
            else:
                message = mainpart

            message['Date'] = datetime.datetime.now().strftime(
                '%a, %d %b %Y %H:%M:%S') + (" +%04d" % (time.timezone / -36, ))

            h = Header()
            fromname = self.fromname.encode(email.charset, 'xmlcharrefreplace')
            h.append(fromname, r if is7bit(fromname) else c)
            h.append('<%s>' % self.email, r)
            message['From'] = h

            message['To'] = email.get_emails_header('rcpt')
            if len(email.cc):
                message['CC'] = email.get_emails_header('cc')
            if len(email.bcc):
                message['BCC'] = email.get_emails_header('bcc')

            subject = email.subject.encode(email.charset, 'xmlcharrefreplace')
            message['Subject'] = Header(subject, r if is7bit(subject) else c)

            if email.force_7bit:
                body = email.body.encode('ascii', 'xmlcharrefreplace')
            else:
                body = email.body.encode(email.charset, 'xmlcharrefreplace')
            mainpart.set_payload(body)

            for hn, hv in email.headers.items():
                if hn == 'X-Mailgun-Campaign-Tag':
                    hn = 'X-Mailgun-Tag'
                message[hn] = hv

            if is7bit(body):
                mainpart['Content-Transfer-Encoding'] = '7bit'
            else:
                encode_quopri(mainpart)

            for attachment in email.attachments:
                if attachment.__class__ != Attachment:
                    raise TypeError("invalid attachment")

                mimetype = attachment.mimetype
                if not mimetype:
                    mimetype, encoding = guess_type(attachment.filename)
                    if not mimetype:
                        mimetype = 'application/octet-stream'
                mime1, mime2 = mimetype.split('/')
                part = MIMEBase(mime1, mime2)

                # using newer rfc2231 (not supported by Outlook):
                # part.set_param('name', attachment.filename.encode('utf-8'), charset = 'utf-8')

                # hack: using deprecated rfc2047 - supported by Outlook:
                part.set_param('name', str(Header(attachment.filename)))
                del part['mime-version']

                if attachment.id:
                    part['Content-Disposition'] = 'inline'
                else:
                    part['Content-Disposition'] = 'attachment'

                # using newer rfc2231 (not supported by Outlook):
                # part.set_param('filename',
                #                attachment.filename.encode('utf-8'),
                #                'Content-Disposition',
                #                charset = 'utf-8')

                # hack: using deprecated rfc2047 - supported by Outlook:
                part.set_param('filename', str(Header(attachment.filename)),
                               'Content-Disposition')

                if attachment.id:
                    part['Content-ID'] = '<%s>' % attachment.id

                part.set_payload(attachment.content)
                encode_base64(part)

                message.attach(part)

            emails = email.rcpt + email.cc + email.bcc
            smtp.sendmail(self.email, [email for _, email in emails],
                          message.as_string())

        smtp.quit()