Example #1
0
    def test_japanese_codecs(self):
        eq = self.ndiffAssertEqual
        j = Charset("euc-jp")
        g = Charset("iso-8859-1")
        h = Header("Hello World!")
        jhello = '\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa'
        ghello = 'Gr\xfc\xdf Gott!'
        h.append(jhello, j)
        h.append(ghello, g)
        # BAW: This used to -- and maybe should -- fold the two iso-8859-1
        # chunks into a single encoded word.  However it doesn't violate the
        # standard to have them as two encoded chunks and maybe it's
        # reasonable <wink> for each .append() call to result in a separate
        # encoded word.
        eq(
            h.encode(), """\
Hello World! =?iso-2022-jp?b?GyRCJU8lbSE8JW8hPCVrJUkhKhsoQg==?=
 =?iso-8859-1?q?Gr=FC=DF?= =?iso-8859-1?q?_Gott!?=""")
        eq(decode_header(h.encode()),
           [('Hello World!', None),
            ('\x1b$B%O%m!<%o!<%k%I!*\x1b(B', 'iso-2022-jp'),
            ('Gr\xfc\xdf Gott!', 'iso-8859-1')])
        int = 'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4\xa4\xa4\xde\xa4\xb9'
        h = Header(int, j, header_name="Subject")
        # test a very long header
        enc = h.encode()
        # TK: splitting point may differ by codec design and/or Header encoding
        eq(
            enc, """\
=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKGyhC?=
 =?iso-2022-jp?b?GyRCMnE8VCROPjVHJyRyQlQkQyRGJCQkXiQ5GyhC?=""")
        # TK: full decode comparison
        eq(h.__unicode__().encode('euc-jp'), int)
Example #2
0
def formataddr(pair, charset='utf-8'):
    """The inverse of parseaddr(), this takes a 2-tuple of the form
    (realname, email_address) and returns the string value suitable
    for an RFC 2822 From, To or Cc header.

    If the first element of pair is false, then the second element is
    returned unmodified.

    The optional charset is the character set that is used to encode
    realname in case realname is not ASCII safe.  Can be an instance of str or
    a Charset-like object which has a header_encode method.  Default is
    'utf-8'.
    """
    name, address = pair
    # The address MUST (per RFC) be ascii, so raise a UnicodeError if it isn't.
    address.encode('ascii')
    if name:
        try:
            name.encode('ascii')
        except UnicodeEncodeError:
            if isinstance(charset, str):
                charset = Charset(charset)
            encoded_name = charset.header_encode(name)
            return "%s <%s>" % (encoded_name, address)
        else:
            quotes = ''
            if specialsre.search(name):
                quotes = '"'
            name = escapesre.sub(r'\\\g<0>', name)
            return '%s%s%s <%s>' % (quotes, name, quotes, address)
    return address
Example #3
0
    def format(self, events, encoding="utf-8"):
        from email import message_from_string
        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

        parts = list()
        data = templates.Template.format(self, parts, events)
        parsed = message_from_string(data.encode(encoding))

        charset = Charset(encoding)
        charset.header_encoding = QP

        msg = MIMEMultipart()
        msg.set_charset(charset)
        for key, value in msg.items():
            del parsed[key]
        for key, value in parsed.items():
            msg[key] = value

        for encoded in ["Subject", "Comment"]:
            if encoded not in msg:
                continue
            value = charset.header_encode(msg[encoded])
            del msg[encoded]
            msg[encoded] = value

        del msg['Content-Transfer-Encoding']
        msg['Content-Transfer-Encoding'] = '7bit'

        msg.attach(MIMEText(parsed.get_payload(), "plain", encoding))
        for part in parts:
            msg.attach(part)
        return msg
Example #4
0
    def test_send_attach_utf8(self):
        subject = u'sübjèçt'
        body = u'bödÿ-àéïöñß'
        attach = BytesIO()
        attach.write(body.encode('utf-8'))
        attach.seek(0)
        attachs = [('attachment', 'text/plain', attach)]

        mailsender = MailSender(debug=True)
        mailsender.send(to=['*****@*****.**'], subject=subject, body=body,
                        attachs=attachs, 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_charset(), Charset('utf-8'))
        self.assertEqual(msg.get('Content-Type'), 'multipart/mixed; charset="utf-8"')

        payload = msg.get_payload()
        assert isinstance(payload, list)
        self.assertEqual(len(payload), 2)

        text, attach = payload
        self.assertEqual(text.get_payload(decode=True).decode('utf-8'), body)
        self.assertEqual(text.get_charset(), Charset('utf-8'))
        self.assertEqual(attach.get_payload(decode=True).decode('utf-8'), body)
Example #5
0
def formataddr(pair, charset='utf-8'):
    """The inverse of parseaddr(), this takes a 2-tuple of the form
    (realname, email_address) and returns the string value suitable
    for an RFC 2822 From, To or Cc header.

    If the first element of pair is false, then the second element is
    returned unmodified.

    Optional charset if given is the character set that is used to encode
    realname in case realname is not ASCII safe.  Can be an instance of str or
    a Charset-like object which has a header_encode method.  Default is
    'utf-8'.
    """
    name, address = pair
    # The address MUST (per RFC) be ascii, so throw a UnicodeError if it isn't.
    address.encode('ascii')
    if name:
        try:
            name.encode('ascii')
        except UnicodeEncodeError:
            if isinstance(charset, str):
                charset = Charset(charset)
            encoded_name = charset.header_encode(name)
            return "%s <%s>" % (encoded_name, address)
        else:
            quotes = ''
            if specialsre.search(name):
                quotes = '"'
            name = escapesre.sub(r'\\\g<0>', name)
            return '%s%s%s <%s>' % (quotes, name, quotes, address)
    return address
Example #6
0
    def test_japanese_codecs(self):
        eq = self.ndiffAssertEqual
        jcode = 'euc-jp'
        gcode = 'iso-8859-1'
        j = Charset(jcode)
        g = Charset(gcode)
        h = Header('Hello World!')
        jhello = str(
            b'\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa',
            jcode)
        ghello = str(b'Gr\xfc\xdf Gott!', gcode)
        h.append(jhello, j)
        h.append(ghello, g)
        eq(
            h.encode(),
            """Hello World! =?iso-2022-jp?b?GyRCJU8lbSE8JW8hPCVrJUkhKhsoQg==?=
 =?iso-8859-1?q?Gr=FC=DF_Gott!?=""")
        eq(decode_header(h.encode()),
           [(b'Hello World! ', None),
            (b'\x1b$B%O%m!<%o!<%k%I!*\x1b(B', 'iso-2022-jp'),
            (b'Gr\xfc\xdf Gott!', gcode)])
        subject_bytes = (
            b'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4\xa4\xa4\xde\xa4\xb9'
        )
        subject = str(subject_bytes, jcode)
        h = Header(subject, j, header_name='Subject')
        enc = h.encode()
        eq(
            enc,
            """=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKGyhC?=
 =?iso-2022-jp?b?GyRCMnE8VCROPjVHJyRyQlQkQyRGJCQkXiQ5GyhC?=""")
        eq(str(h).encode(jcode), subject_bytes)
Example #7
0
    def format(self, events, encoding="utf-8"):
        parts = list()
        data = templates.Template.format(self, parts, events)
        parsed = message_from_string(data.encode(encoding))

        charset = Charset(encoding)
        charset.header_encoding = QP

        msg = MIMEMultipart()
        msg.set_charset(charset)
        for key, value in msg.items():
            del parsed[key]
        for key, value in parsed.items():
            msg[key] = value

        for encoded in ["Subject", "Comment"]:
            if encoded not in msg:
                continue
            value = charset.header_encode(msg[encoded])
            del msg[encoded]
            msg[encoded] = value

        del msg["Content-Transfer-Encoding"]
        msg["Content-Transfer-Encoding"] = "7bit"

        msg.attach(MIMEText(parsed.get_payload(), "plain", encoding))
        for part in parts:
            msg.attach(part)
        return msg
Example #8
0
def encode_address(addr, charset):
    """Encode a pair of (name, address) or an email address string.

    When non-ascii characters are present in the name or local part, they're
    MIME-word encoded. The domain name is idna-encoded if it contains
    non-ascii characters.
    """
    if not isinstance(addr, tuple):
        addr = parseaddr(to_str(addr))
    name, addr = addr
    if isinstance(charset, str):
        charset = Charset(charset)
    if "@" in addr:
        localpart, domain = addr.rsplit("@", 1)
        # Try to get the simplest encoding - ascii if possible so that
        # [email protected] doesn't become [email protected]. This
        # makes unit testing a bit easier and more readable.
        try:
            localpart.encode("ascii")
        except UnicodeEncodeError:
            localpart = charset.header_encode(localpart)
        addr = localpart + "@" + domain.encode("idna").decode("ascii")
        del localpart, domain
    else:
        try:
            addr.encode("ascii")
        except UnicodeEncodeError:
            addr = charset.header_encode(addr)
    return formataddr((name, addr), charset=charset)
Example #9
0
    def format(self, events, encoding="utf-8"):
        parts = list()
        data = templates.Template.format(self, parts, events)
        parsed = message_from_string(data.encode(encoding))

        charset = Charset(encoding)
        charset.header_encoding = QP

        msg = MIMEMultipart()
        msg.set_charset(charset)
        for key, value in msg.items():
            del parsed[key]
        for key, value in parsed.items():
            msg[key] = value

        for encoded in ["Subject", "Comment"]:
            if encoded not in msg:
                continue
            value = charset.header_encode(msg[encoded])
            del msg[encoded]
            msg[encoded] = value

        del msg['Content-Transfer-Encoding']
        msg['Content-Transfer-Encoding'] = '7bit'

        msg.attach(MIMEText(parsed.get_payload(), "plain", encoding))
        for part in parts:
            msg.attach(part)
        return msg
 def test_japanese_codecs(self):
     eq = self.ndiffAssertEqual
     j = Charset('euc-jp')
     g = Charset('iso-8859-1')
     h = Header('Hello World!')
     jhello = '\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa'
     ghello = 'Gr\xfc\xdf Gott!'
     h.append(jhello, j)
     h.append(ghello, g)
     eq(
         h.encode(),
         'Hello World! =?iso-2022-jp?b?GyRCJU8lbSE8JW8hPCVrJUkhKhsoQg==?=\n =?iso-8859-1?q?Gr=FC=DF?= =?iso-8859-1?q?_Gott!?='
     )
     eq(decode_header(h.encode()),
        [('Hello World!', None),
         ('\x1b$B%O%m!<%o!<%k%I!*\x1b(B', 'iso-2022-jp'),
         ('Gr\xfc\xdf Gott!', 'iso-8859-1')])
     long = 'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4\xa4\xa4\xde\xa4\xb9'
     h = Header(long, j, header_name='Subject')
     enc = h.encode()
     eq(
         enc,
         '=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKGyhC?=\n =?iso-2022-jp?b?GyRCMnE8VCROPjVHJyRyQlQkQyRGJCQkXiQ5GyhC?='
     )
     eq(h.__unicode__().encode('euc-jp'), long)
     return None
Example #11
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
Example #12
0
 def get_text_message(self, _charset='utf-8', _subtype='plain'):
     message = MIMENonMultipart('text', _subtype)
     cs = Charset(_charset)
     if cs.body_encoding == charset.BASE64:
         cs.body_encoding = charset.QP
     message.set_charset(cs)
     del message['Content-Transfer-Encoding']
     return message
Example #13
0
        def send_report(self, short_description, additional_description, title,
                        report):
            # Create message container - the correct MIME type is multipart/mixed to allow attachment.
            full_email = MIMEMultipart('mixed')
            full_email[
                'Subject'] = '[' + self.conf['subject_keyword'] + '] ' + title
            full_email['From'] = self.conf['from']
            full_email['To'] = self.conf['to']

            # Create the body of the message (a plain-text and an HTML version).
            body = MIMEMultipart('alternative')
            body.attach(
                MIMEText((short_description + "\n\n" +
                          additional_description).encode('utf-8'),
                         'plain',
                         _charset='utf-8'))
            body.attach(
                MIMEText(("""\
                                    <html>
                                      <head></head>
                                      <body>
                                        <p>""" + short_description +
                          """</p><br>
                                        """ + additional_description + """
                                      </body>
                                    </html>
                                    """).encode('utf-8'),
                         'html',
                         _charset='utf-8'))
            full_email.attach(body)

            # Create the attachment of the message in text/csv.
            attachment = MIMENonMultipart('text', 'csv', charset='utf-8')
            attachment.add_header('Content-Disposition',
                                  'attachment',
                                  filename=report['filename'])
            cs = Charset('utf-8')
            cs.body_encoding = BASE64
            attachment.set_payload(report['content'].encode('utf-8'),
                                   charset=cs)
            full_email.attach(attachment)

            # Send the message via SMTP server.
            s = smtplib.SMTP(self.conf['server'], self.conf['port'])
            if self.conf['tls'] == 'yes':
                s.starttls()
            if not self.conf['login'] == '':
                s.login(self.conf['login'], self.conf['password'])
            # sendmail function takes 3 arguments: sender's address, recipient's address
            # and message to send - here it is sent as one string.
            s.sendmail(self.conf['from'], self.conf['to'],
                       full_email.as_string())
            # self.logger.info('email sent')
            s.quit()
Example #14
0
def sendmail(server, from_, to, message):
    if not isinstance(to, list):
        to = [to]

    charset = Charset('UTF-8')
    charset.header_encoding = QP
    charset.body_encoding = QP

    msg = email.message_from_string(message)
    msg.set_charset(charset)

    server.sendmail(from_, to, msg.as_string())
Example #15
0
    def convert_to_attachment(self, key):
        s3object = self.get_object(key)
        attachment = MIMEBase(
            s3object['ContentType'].split('/')[0],
            '%s; name="%s"' % (s3object['ContentType'].split('/')[1],
                               Charset('utf-8').header_encode(basename(key))))
        attachment.add_header(
            'Content-Disposition', 'attachment; filename="%s"' %
            Charset('utf-8').header_encode(basename(key)))
        attachment.set_payload(s3object['Body'].read())
        encoders.encode_base64(attachment)

        return attachment
Example #16
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, 'wb') 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(header_name='from')
            try:
                from_header.append(name, 'us-ascii')
            except UnicodeDecodeError:
                from_header.append(name, charset)
            from_header.append('<%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(datestr, 'us-ascii', 'date')
            subject_header = Header(header_name='subject')
            try:
                subject_header.append(commit_info['subject'], 'us-ascii')
            except UnicodeDecodeError:
                subject_header.append(commit_info['subject'], charset)
            msg['Subject'] = subject_header
            # Write message body
            if commit_info['body']:
                # Strip extra linefeeds
                body = commit_info['body'].rstrip() + '\n'
                try:
                    msg.set_payload(body.encode('us-ascii'))
                except (UnicodeEncodeError):
                    msg.set_payload(body, charset)
            patch.write(
                msg.as_string(unixfrom=False, maxheaderlen=77).encode('utf-8'))

            # Write diff
            patch.write(b'---\n')
            patch.write(diff)
    except IOError as err:
        raise GbpError('Unable to create patch file: %s' % err)
    return filename
Example #17
0
    def send(self, subject, recipients, sender=None, attach=None,
             html=None, text=None, template=None, **kwargs):
        """
        Отправка самосборного письма.
        Ссылки на картинке в статике превращаются в аттачменты. Текст правильно кодируется, чтобы
        избежать багов с переносом строки в Flask-Mail

        recipients - список
        attach - вложения, словарь имя-путь
        template - можно указать имя шаблона без расширения

        """

        sender = sender or self.default_sender

        if template:
            html, text = render_email(template, **kwargs)

        recipients_str = self._contact_list(recipients)

        charset = Charset(input_charset='utf-8')

        msgRoot = MIMEMultipart('related')
        msgRoot['Subject'] = charset.header_encode(subject)
        msgRoot['From'] = self._contact(sender)
        msgRoot['To'] = recipients_str
        msgRoot.preamble = 'This is a multi-part message in MIME format.'
        msgRoot.set_charset('utf-8')

        msgAlternative = MIMEMultipart(_subtype='alternative')
        msgAlternative.set_charset("utf-8")
        msgRoot.attach(msgAlternative)

        msgText = MIMEText(_text=text, _subtype='plain', _charset='utf-8')
        msgAlternative.attach(msgText)

        html, images = self._extract_statics(html)
        self._attach_images(msgRoot, images)
        if attach:
            self._attach_images(msgRoot, attach)

        msgHtml = MIMEText(_text=html, _subtype='html', _charset='utf-8')
        msgAlternative.attach(msgHtml)

        if self.mail_enabled:
            with smtplib.SMTP(host=self.mail_server, port=self.mail_port) as smtp:
                smtp.sendmail(
                    self._address(sender),
                    [self._address(r) for r in recipients], 
                    msgRoot.as_string()
                )
Example #18
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, 'wb') 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(r'[,.@()\[\]\\\:;]', name):
                name = '"%s"' % name
            from_header = Header(header_name='from')
            try:
                from_header.append(name, 'us-ascii')
            except UnicodeDecodeError:
                from_header.append(name, charset)
            from_header.append('<%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(datestr, 'us-ascii', 'date')
            subject_header = Header(header_name='subject')
            try:
                subject_header.append(commit_info['subject'], 'us-ascii')
            except UnicodeDecodeError:
                subject_header.append(commit_info['subject'], charset)
            msg['Subject'] = subject_header
            # Write message body
            if commit_info['body']:
                # Strip extra linefeeds
                body = commit_info['body'].rstrip() + '\n'
                try:
                    msg.set_payload(body.encode('us-ascii'))
                except (UnicodeEncodeError):
                    msg.set_payload(body, charset)
            policy = Compat32(max_line_length=77)
            patch.write(msg.as_bytes(unixfrom=False, policy=policy))

            # Write diff
            patch.write(b'---\n')
            patch.write(diff)
    except IOError as err:
        raise GbpError('Unable to create patch file: %s' % err)
    return filename
Example #19
0
    def get_emails_header(self, attr):
        c = Charset(self.charset)
        c.header_encoding = QP
        c.body_encoding = 0
        r = Charset(self.charset)
        r.header_encoding = 0
        r.body_encoding = 0

        h = Header()
        self.normalize_email_list(attr)
        emails = self.__getattribute__(attr)

        for i in range(len(emails)):
            name, email = emails[i]

            if i:
                h.append(',', r)

            if name:
                name = name.encode(self.charset, 'xmlcharrefreplace')
                h.append(name, r if is7bit(name) else c)
                h.append('<%s>' % email, r)
            else:
                h.append(email, r)
        return h
Example #20
0
def emailPlain(
    template, subject=None, toinform=[], context={}, attachments=[], forreal=True
):
    # try to stick to rfc822 (django default is base64) religiously; also
    # as it helps with spam filters.
    cs = Charset("utf-8")
    cs.body_encoding = QP

    # Weed out duplicates.
    to = list(set(flatten(toinform)))

    context["base"] = settings.BASE

    body = render_to_string(template, context)

    if not subject:
        body = body.split("\n")
        subject = body[0].rstrip()
        subject = re.sub("^Subject:\s+", string=subject, repl="", flags=re.IGNORECASE)
        body = "\n".join(body[1:])

    body_html = (
        "<html><head><title>%s</title></head><body><pre>%s</pre></body><html>"
        % (subject, body)
    )

    msg = MIMEMultipart("alternative")

    part1 = MIMEText(body, "plain", _charset=cs)

    part2 = MIMEMultipart("related")
    part2.attach(MIMEText(body_html, "html", _charset=cs))

    email = EmailMessage(
        subject.strip(), None, to=to, from_email=settings.DEFAULT_FROM_EMAIL
    )

    for attachment in attachments:
        part2.attach(attachment)

    msg.attach(part1)
    msg.attach(part2)

    email.attach(msg)
    if forreal:
        email.send()
    else:
        print("To:\t%s\nSubject: %s\n%s\n\n" % (to, subject, body))
Example #21
0
def to_cset_out(text, lcset):
    # Convert text from unicode or lcset to output cset.
    ocset = Charset(lcset).get_output_charset() or lcset
    if isinstance(text, (bytes, bytearray)):
        return text.encode(ocset, 'replace')
    else:
        return text.decode(lcset, 'replace').encode(ocset, 'replace')
Example #22
0
    def __init__(self, msg):
        '''
        Create a message that is fully utf-8 encoded.

        msg is the original message.
        '''
        if not isinstance(msg, email.message.Message):
            raise TypeError('msg is not a Message')
        self._msg = msg
        charset = msg.get_content_charset() or 'utf-8'
        self._body_charset = Charset(input_charset=charset)
        assert self._body_charset.header_encoding in [None, QP]
        assert self._body_charset.body_encoding in [None, QP]

        if not self._msg.has_key('Subject'):
            self._msg.add_header('Subject', '')
Example #23
0
def fix_text_required(encodingName):
    from email.charset import Charset
    from email.charset import QP

    charset = Charset(encodingName)
    bodyenc = charset.body_encoding
    return bodyenc in (None, QP)
Example #24
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
     return
Example #25
0
    def test_send_attach(self):
        attach = BytesIO()
        attach.write(b"content")
        attach.seek(0)
        attachs = [("attachment", "text/plain", attach)]

        mailsender = MailSender(debug=True)
        mailsender.send(
            to=["*****@*****.**"],
            subject="subject",
            body="body",
            attachs=attachs,
            _callback=self._catch_mail_sent,
        )

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

        msg = self.catched_msg["msg"]
        self.assertEqual(msg["to"], "*****@*****.**")
        self.assertEqual(msg["subject"], "subject")

        payload = msg.get_payload()
        assert isinstance(payload, list)
        self.assertEqual(len(payload), 2)

        text, attach = payload
        self.assertEqual(text.get_payload(decode=True), b"body")
        self.assertEqual(text.get_charset(), Charset("us-ascii"))
        self.assertEqual(attach.get_payload(decode=True), b"content")
Example #26
0
    def test_send_attach(self):
        attach = BytesIO()
        attach.write(b'content')
        attach.seek(0)
        attachs = [('attachment', 'text/plain', attach)]

        mailsender = MailSender(debug=True)
        mailsender.send(to=['*****@*****.**'], subject='subject', body='body',
                       attachs=attachs, _callback=self._catch_mail_sent)

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

        msg = self.catched_msg['msg']
        self.assertEqual(msg['to'], '*****@*****.**')
        self.assertEqual(msg['subject'], 'subject')

        payload = msg.get_payload()
        assert isinstance(payload, list)
        self.assertEqual(len(payload), 2)

        text, attach = payload
        self.assertEqual(text.get_payload(decode=True), b'body')
        self.assertEqual(text.get_charset(), Charset('us-ascii'))
        self.assertEqual(attach.get_payload(decode=True), b'content')
Example #27
0
class TestEncodeAddress(object):
    """ Address encoding tests

    See http://www.faqs.org/rfcs/rfc2822.html section 3.4.
    Address Specification.

    mailbox     =   name-addr / addr-spec
    name-addr   =   [display-name] angle-addr
    angle-addr  =   [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
    """
    charset = Charset(CHARSET)
    charset.header_encoding = QP
    charset.body_encoding = QP

    def testSimpleAddress(self):
        """ mail.sendmail: encode simple address: local@domain """
        address = u'local@domain'
        expected = address.encode(CHARSET)
        assert sendmail.encodeAddress(address, self.charset) == expected

    def testComposite(self):
        """ mail.sendmail: encode address: 'Phrase <local@domain>' """
        address = u'Phrase <local@domain>'
        expected = str(address)
        assert sendmail.encodeAddress(address, self.charset) == expected

    def testCompositeUnicode(self):
        """ mail.sendmail: encode Uncode address: 'ויקי <local@domain>' """
        address = u'ויקי <local@domain>'
        phrase = str(Header(u'ויקי'.encode('utf-8'), self.charset))
        expected = phrase + ' ' + '<local@domain>'
        assert sendmail.encodeAddress(address, self.charset) == expected

    def testEmptyPhrase(self):
        """ mail.sendmail: encode address with empty phrase: '<local@domain>' """
        address = u'<local@domain>'
        expected = 'local@domain'
        assert sendmail.encodeAddress(address, self.charset) == expected

    def testEmptyAddress(self):
        """ mail.sendmail: encode address with empty address: 'Phrase <>'

        Let the smtp server handle this. We may raise error in such
        case, but we don't do error checking for mail addresses.
        """
        address = u'Phrase <>'
        expected = str(address)
        assert sendmail.encodeAddress(address, self.charset) == expected

    def testInvalidAddress(self):
        """ mail.sendmail: encode invalid address 'Phrase <blah'

        Assume that this is a simple address. This address will
        probably cause an error when trying to send mail. Junk in, junk
        out.
        """
        address = u'Phrase <blah'
        expected = str(address)
        assert sendmail.encodeAddress(address, self.charset) == expected
Example #28
0
    def __init__(self,
                 s=None,
                 charset=None,
                 maxlinelen=None,
                 header_name=None,
                 continuation_ws=' ',
                 errors='strict'):
        """Create a MIME-compliant header that can contain many character sets.

        Optional s is the initial header value.  If None, the initial header
        value is not set.  You can later append to the header with .append()
        method calls.  s may be a byte string or a Unicode string, but see the
        .append() documentation for semantics.

        Optional charset serves two purposes: it has the same meaning as the
        charset argument to the .append() method.  It also sets the default
        character set for all subsequent .append() calls that omit the charset
        argument.  If charset is not provided in the constructor, the us-ascii
        charset is used both as s's initial charset and as the default for
        subsequent .append() calls.

        The maximum line length can be specified explicit via maxlinelen.  For
        splitting the first line to a shorter value (to account for the field
        header which isn't included in s, e.g. `Subject') pass in the name of
        the field in header_name.  The default maxlinelen is 76.

        continuation_ws must be RFC 2822 compliant folding whitespace (usually
        either a space or a hard tab) which will be prepended to continuation
        lines.

        errors is passed through to the .append() call.
        """
        if charset is None:
            charset = USASCII
        charset = USASCII

        print type(Charset)
        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))
        # BAW: I believe `chunks' and `maxlinelen' should be non-public.
        self._chunks = []
        if s is not None:
            self.append(s, charset, errors)
        if maxlinelen is None:
            maxlinelen = MAXLINELEN
        if header_name is None:
            # We don't know anything about the field header so the first line
            # is the same length as subsequent lines.
            self._firstlinelen = maxlinelen
        else:
            # The first line should be shorter to take into account the field
            # header.  Also subtract off 2 extra for the colon and space.
            self._firstlinelen = maxlinelen - len(header_name) - 2
        # Second and subsequent lines should subtract off the length in
        # columns of the continuation whitespace prefix.
        self._maxlinelen = maxlinelen - cws_expanded_len
Example #29
0
 def __init__(self, _text, _subtype='plain', _charset='utf-8'):
     if not isinstance(_charset, Charset):
         _charset = Charset(_charset)
     if isinstance(_text, unicode):
         _text = _text.encode(_charset.input_charset)
     MIMENonMultipart.__init__(self, 'text', _subtype,
                               **{'charset': _charset.input_charset})
     self.set_payload(_text, _charset)
Example #30
0
def header_to_mime_encoding(value, not_email=False):
    if not value: return ""

    encoder = Charset(DEFAULT_ENCODING)
    if type(value) == list:
        return "; ".join(properly_encode_header(v, encoder, not_email) for v in value)
    else:
        return properly_encode_header(value, encoder, not_email)
Example #31
0
def formataddr(pair, charset='utf-8'):
    (name, address) = pair
    address.encode('ascii')
    if name:
        try:
            name.encode('ascii')
        except UnicodeEncodeError:
            if isinstance(charset, str):
                charset = Charset(charset)
            encoded_name = charset.header_encode(name)
            return '%s <%s>' % (encoded_name, address)
        quotes = ''
        if specialsre.search(name):
            quotes = '"'
        name = escapesre.sub('\\\\\\g<0>', name)
        return '%s%s%s <%s>' % (quotes, name, quotes, address)
    return address
Example #32
0
def formataddr(pair, charset='utf-8'):
    (name, address) = pair
    address.encode('ascii')
    if name:
        try:
            name.encode('ascii')
        except UnicodeEncodeError:
            if isinstance(charset, str):
                charset = Charset(charset)
            encoded_name = charset.header_encode(name)
            return '%s <%s>' % (encoded_name, address)
        quotes = ''
        if specialsre.search(name):
            quotes = '"'
        name = escapesre.sub('\\\\\\g<0>', name)
        return '%s%s%s <%s>' % (quotes, name, quotes, address)
    return address
Example #33
0
    def _mail(self, fromaddr, to, subject, payload):

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

        # create method and set headers
        msg = Message()
        msg.set_payload(payload.encode("utf8"))
        msg.set_charset(charset)
        msg['Subject'] = Header(subject, "utf8")
        msg['From'] = fromaddr
        msg['To'] = to

        self.server.connect()
        self.server.sendmail(fromaddr, [to], msg.as_string())
        self.server.quit()
Example #34
0
    def _mail(self, fromaddr, to, subject, payload):

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

        # create method and set headers
        msg = Message()
        msg.set_payload(payload.encode("utf8"))
        msg.set_charset(charset)
        msg['Subject'] = Header(subject, "utf8")
        msg['From'] = fromaddr
        msg['To'] = to

        self.server.connect()
        self.server.sendmail(fromaddr, [to], msg.as_string())
        self.server.quit()
Example #35
0
 def _is_readable(self):
     if self.transfer_encoding == "base64":
         return False
     if self.content_type == "text/plain":
         return True
     if self.content_type == "text/html":
         return True
     if self.charset is not None:
         cs = Charset(self.charset)
     else:
         cs = Charset()
     if self.charset == "us-ascii":
         return True
     if cs.get_body_encoding() == "quoted-printable":
         return True
     if self.body_only:
         return True
     if self.charset is None:
         return False
     return False
Example #36
0
def fix_text_required(encodingname):
    """
    email包里的str/bytes组合错误的规避措施,由于MIME编码某些文本中某些类型
    的方式不同,Python3.1中MIMEText对不同Unicode编码要求不同的类型,唯一的
    替代方案是使用通用Message并重复其中大部分代码;
    """
    from email.charset import Charset, BASE64, QP

    charset = Charset(encodingname)  # email如何探知,怎样进行编码
    bodyenc = charset.body_encoding  # utf8,其它编码要求bytes类型输入数据
    return bodyenc in (None, QP)  # ascii, latin1, 其它编码要求str
Example #37
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 the charset is our faux 8bit charset, leave the string unchanged
        if charset != '8bit':
            # We need to test that the string can be converted to unicode and
            # back to a byte string, given the input and output codecs of the
            # charset.
            if isinstance(s, str):
                # Possibly raise UnicodeError if the byte string can't be
                # converted to a unicode with the input codec of the charset.
                incodec = charset.input_codec or 'us-ascii'
                ustr = unicode(s, incodec, errors)
                # Now make sure that the unicode could be converted back to a
                # byte string with the output codec, which may be different
                # than the iput coded.  Still, use the original byte string.
                outcodec = charset.output_codec or 'us-ascii'
                ustr.encode(outcodec, errors)
            elif isinstance(s, unicode):
                # Now we have to be sure the unicode string can be converted
                # to a byte string with a reasonable output codec.  We want to
                # use the byte string in the chunk.
                for charset in USASCII, charset, UTF8:
                    try:
                        outcodec = charset.output_codec or 'us-ascii'
                        s = s.encode(outcodec, errors)
                        break
                    except UnicodeError:
                        pass
                else:
                    assert False, 'utf-8 conversion failed'
        self._chunks.append((s, charset))
Example #38
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
Example #39
0
    def get_emails_header(self, attr):
        c = Charset(self.charset)
        c.header_encoding = QP
        c.body_encoding = 0
        r = Charset(self.charset)
        r.header_encoding = 0
        r.body_encoding = 0

        h = Header()
        self.normalize_email_list(attr)
        emails = self.__getattribute__(attr)

        for i in range(len(emails)):
            name, email = emails[i]

            if i:
                h.append(',', r)

            if name:
                name = name.encode(self.charset, 'xmlcharrefreplace')
                h.append(name, r if is7bit(name) else c)
                h.append('<%s>' % email, r)
            else:
                h.append(email, r)

        return h
Example #40
0
    def __init__(self, msg):
        '''
        Create a message that is fully utf-8 encoded.

        msg is the original message.
        '''
        if not isinstance(msg, email.message.Message):
            raise TypeError('msg is not a Message')
        self._msg = msg
        charset = msg.get_content_charset() or 'utf-8'
        self._body_charset = Charset(input_charset=charset)
        assert self._body_charset.header_encoding in [None, QP]
        assert self._body_charset.body_encoding in [None, QP]

        if not self._msg.has_key('Subject'):
            self._msg.add_header('Subject', '')
Example #41
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)
Example #42
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(
        '[email protected]',
Example #43
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:
			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())
Example #44
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.tls:
            smtp.starttls()
        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

            email.normalize_email_list('rcpt')
            email.normalize_email_list('cc')
            email.normalize_email_list('bcc')
            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(maxlinelen=1000) # FIXME: what is correct max length?
            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.reply_to:
                message['Reply-To'] = email.get_emails_header('reply_to')

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

            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)

                # Do this AFTER encode_base64(part), or Content-Transfer-Encoding header will duplicate,
                # or even happen 2 times with different values.
                if attachment.charset:
                    part.set_charset(attachment.charset)

                message.attach(part)

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

        smtp.quit()
Example #45
0
class UnicodeMessage(object):
    '''
    Wrapper around a email.message.Message, that allows to interact
    with the message using decoded unicode strings.

    Part of the interface to Message is supported. The interface
    methods return normal unicode strings, with the email-specific
    encoding parts removed.

    The underlying message might be transformed by this class and should not
    be used elsewhere.
    '''

    def __init__(self, msg):
        '''
        Create a message that is fully utf-8 encoded.

        msg is the original message.
        '''
        if not isinstance(msg, email.message.Message):
            raise TypeError('msg is not a Message')
        self._msg = msg
        charset = msg.get_content_charset() or 'utf-8'
        self._body_charset = Charset(input_charset=charset)
        assert self._body_charset.header_encoding in [None, QP]
        assert self._body_charset.body_encoding in [None, QP]

        if not self._msg.has_key('Subject'):
            self._msg.add_header('Subject', '')

    def __str__(self):
        return self.as_string()

    @property
    def id(self):
        return self['Message-Id']

    def as_string(self):
        """
        Returns the message as a string encoded with utf-8, avoiding the escaping
        of 'From' lines.
        """
        io = StringIO()
        g = Generator(io, False) # second argument means "should I mangle From?"
        g.flatten(self._msg)
        return io.getvalue()

    # Delegate to Message

    def __getitem__(self, name):
        '''Get a header value, from the message, decoded and as a
        unicode string.

        If the header does not exist, None is returned'''
        value = self._msg[name]
        if value is None:
            return None
        return u''.join(to_unicode(*tupl) for tupl in decode_header(value))

    def replace_header(self, name, value):
        '''Forwards the call to replace_header.

        name the id of the header. If it does not exist yet, it is
        newly created. This behavior is different from the standard
        message.

        value is passed as a unicode string. This method tries to
        avoid encoding the value with a Header (i.e when the value is
        an ascii string).
        '''
        assert isinstance(value, unicode)
        try:
            header = value.encode('ascii')
        except UnicodeEncodeError:
            header = Header(value.encode('utf-8'), 'UTF-8').encode()
        if self._msg.has_key(name):
            self._msg.replace_header(name, header)
        else:
            self._msg.add_header(name, header)

    def get_payload(self, i=None, decode=False):
        '''
        Forwards the call to get_payload.

        Instances of the type email.message.Message are wrapped as a
        UnicodeMessage. Strings are returned as unicode.
        '''
        payload = self._msg.get_payload(i, decode)
        if isinstance(payload, list):
            return [UnicodeMessage(msg) for msg in payload]
        elif isinstance(payload, email.message.Message):
            return UnicodeMessage(payload)
        elif isinstance(payload, str):
            return to_unicode(payload, self._msg.get_content_charset())
        return payload

    def get_clean_payload(self, forbidden_words):
        '''
        Gets a text payload, with the given forbidden words replaced.

        forbidden_words a dictionary containing pairs of
        (word_to_replace, replacement).
        '''
        assert isinstance(forbidden_words, dict)
        payload = self.get_payload(decode=True)
        assert isinstance(payload, unicode)

        payload = payload.split('\n')
        return '\n'.join(
            ' '.join(self._clean_word(word, forbidden_words) for word in line.split(' '))
            for line in payload)

    def _clean_word(self, word, forbidden_words):
        '''
        Returns a replacement if the given word is in the forbidden
        words dictionary. Otherwise, the word is returned unchanged.

        The word is striped of punctuation (i.e. period, asterisks)
        and converted to lower for the comparison.
        '''
        punctuation = '.!?*()\'"[]-_+=:;<>,/'
        match = word.lower().strip(punctuation)
        if match in forbidden_words:
            replacement = forbidden_words[match]
            word = re.sub(match, replacement, word, flags=re.IGNORECASE)
        return word

    def set_payload(self, payload):
        '''
        Forwards the call to set_payload.

        If the payload is text, it is passed as a unicode string. Text
        is encoded again before being passed. The content encoding is
        changed to quoted printable to avoid encoding
        incompatibilities.
        '''
        assert not isinstance(payload, str)
        if isinstance(payload, unicode):
            self.replace_header('Content-Transfer-Encoding', u'quoted-printable')
            payload = self._body_charset.body_encode(
                payload.encode(self._body_charset.input_charset), convert=False)
        self._msg.set_payload(payload)

    from email.Iterators import walk

    def __getattr__(self, name):
        return getattr(self._msg, name)
Example #46
0
def create_charset(mime_encoding):
    """Create an appropriate email charset for the given encoding.

    Valid options are 'base64' for Base64 encoding, 'qp' for
    Quoted-Printable, and 'none' for no encoding, in which case mails will
    be sent as 7bit if the content is all ASCII, or 8bit otherwise.
    """
    charset = Charset()
    charset.input_charset = 'utf-8'
    charset.output_charset = 'utf-8'
    charset.input_codec = 'utf-8'
    charset.output_codec = 'utf-8'
    pref = mime_encoding.lower()
    if pref == 'base64':
        charset.header_encoding = BASE64
        charset.body_encoding = BASE64
    elif pref in ('qp', 'quoted-printable'):
        charset.header_encoding = QP
        charset.body_encoding = QP
    elif pref == 'none':
        charset.header_encoding = SHORTEST
        charset.body_encoding = None
    else:
        raise TracError(_("Invalid email encoding setting: %(mime_encoding)s",
                          mime_encoding=mime_encoding))
    return charset
Example #47
0
File: draft.py Project: yhlam/gmail
 def encode_address(addr):
     name, email = parseaddr(addr)
     charset = Charset(guess_charset(name))
     encoded_name = charset.header_encode(name)
     return formataddr((encoded_name, email))
Example #48
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:
                        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:
            logging.exception("sendmail failed with an exception.")
            return 0, _("Mail not sent")

    logging.debug("Mail sent successfully")
    return 1, _("Mail sent successfully")
Example #49
0
    def __init__(self, payload, charset='utf-8'):
        MIMENonMultipart.__init__(self, 'text', 'plain', charset=charset)

        utf8qp = Charset(charset)
        utf8qp.body_encoding = QP
        self.set_payload(payload, charset=utf8qp)
 def _make_charset(self):
     charset = Charset()
     charset.input_charset = 'utf-8'
     pref = self.mime_encoding.lower()
     if pref == 'base64':
         charset.header_encoding = BASE64
         charset.body_encoding = BASE64
         charset.output_charset = 'utf-8'
         charset.input_codec = 'utf-8'
         charset.output_codec = 'utf-8'
     elif pref in ['qp', 'quoted-printable']:
         charset.header_encoding = QP
         charset.body_encoding = QP
         charset.output_charset = 'utf-8'
         charset.input_codec = 'utf-8'
         charset.output_codec = 'utf-8'
     elif pref == 'none':
         charset.header_encoding = None
         charset.body_encoding = None
         charset.input_codec = None
         charset.output_charset = 'ascii'
     else:
         raise TracError(_('Invalid email encoding setting: %s' % pref))
     return charset
Example #51
0
def send_email(subject, recipients, sender=None, attach=None,
               html_body=None, text_body=None, template=None, **kwargs):
    """
    Отправка самосборного письма.
    Ссылки на картинке в статике превращаются в аттачменты. Текст правильно кодируется, чтобы
    избежать багов с переносом строки в Flask-Mail

    recipients - Список
    attach - Вложения, словарь имя-путь
    template - Имя шаблона без расширения. Будет искатся пара файлов <template>.html и <template>.txt

    """

    if sender is None:
        cfg = current_app.config
        sender = cfg.get('MAIL_DEFAULT_SENDER', 'no-reply@{}'.format(cfg.get('SERVER_NAME', 'example.com')))

    if template:
        html_body, text_body = render_email(template, **kwargs)

    recipients_str = contact_list(recipients)

    charset = Charset(input_charset='utf-8')

    msgRoot = MIMEMultipart('related')
    msgRoot['Subject'] = charset.header_encode(subject)
    msgRoot['From'] = contact(sender)
    msgRoot['To'] = recipients_str
    msgRoot.preamble = 'This is a multi-part message in MIME format.'
    msgRoot.set_charset('utf-8')

    msgAlternative = MIMEMultipart(_subtype='alternative')
    msgAlternative.set_charset("utf-8")
    msgRoot.attach(msgAlternative)

    msgText = MIMEText(_text=text_body, _subtype='plain', _charset='utf-8')
    msgAlternative.attach(msgText)

    html, images = extract_statics(html_body)
    attach_images(msgRoot, images)
    if attach:
        attach_images(msgRoot, attach)

    msgHtml = MIMEText(_text=html, _subtype='html', _charset='utf-8')
    msgAlternative.attach(msgHtml)

    if current_app.config['MAIL_ENABLED']:
        with smtplib.SMTP(
                host=current_app.config['MAIL_SERVER'], 
                port=current_app.config['MAIL_PORT']) as smtp:
            try:
                smtp.sendmail(
                    address(sender),
                    [address(r) for r in recipients],
                    msgRoot.as_string()
                )
                if current_app.config.get('DEBUG'):
                    print('=== MAIL FROM "%s" TO "%s"' % (address(sender), repr([address(r) for r in recipients])))
                    print(msgRoot.as_string())
            except smtplib.SMTPRecipientsRefused:
                print('smtplib.SMTPRecipientsRefused: %s' % repr(recipients))