def send_text(self, text, envelop: Envelop):
     msg = Message()
     msg.set_payload(text)
     msg.add_header('Content-Type', 'text/html')
     return self.send(msg, envelop)
示例#2
0
    def sendMessage(self,
                    From,
                    To,
                    Subj,
                    extrahdrs,
                    bodytext,
                    attaches,
                    saveMailSeparator=(('=' * 80) + 'PY\n'),
                    bodytextEncoding='us-ascii',
                    attachesEncodings=None):
        """
        formats and sends an e-mail.
        saves the sent e-mail if sent sucessfully.
        arguments:
        bodytext - text part of the e-mail (assumes it is already in desired
                                                encoding.)
        attaches - list of files to be attached
        extrahdrs - list of tuples to be added (Name, Value)
        """
        # body text encoding
        if fix_text_required(bodytextEncoding):
            if not isinstance(bodytext, str):
                bodytext = bodytext.decode(bodytextEncoding)
        else:
            if not isinstance(bodytext, bytes):
                bodytext = bodytext.encode(bodytextEncoding)

        # attachments
        if not attaches:
            msg = Message()
            msg.set_payload(bodytext, charset=bodytextEncoding)
        else:
            msg = MIMEMultipart()
            self.addAttachments(msg, bodytext, attaches, bodytextEncoding,
                                attachesEncodings)

        # e-mail header encoding
        hdrenc = mailconfig.headersEncodeTo or 'utf-8'
        Subj = self.encodeHeader(Subj, hdrenc)
        From = self.encodeAddrHeader(From, hdrenc)
        To = [self.encodeAddrHeader(T, hdrenc) for T in To]
        Tos = ', '.join(To)

        # attach header to message
        msg['From'] = From
        msg['To'] = Tos
        msg['Subj'] = Subj
        msg['Date'] = email.utils.formatdate()
        recip = To

        for (name, value) in extrahdrs:
            if value:
                if name.lower() not in ['cc', 'bcc']:
                    value = self.encodeHeader(value, hdrenc)
                    msg[name] = value
                else:
                    value = [self.encodeAddrHeader(V, hdrenc) for V in value]
                    recip += value
                    if name.lower() != 'bcc':
                        msg[name] = ', '.join(value)

        # remove duplicates
        recip = list(set(recip))

        fullText = msg.as_string()

        self.trace('Sending to...' + str(recip))
        self.trace(fullText[:self.tracesize])

        # smtp connection.
        server = smtplib.SMTP_SSL(self.smtpservername)
        self.getPassword()
        self.authenticateServer(server)

        try:
            failed = server.sendmail(From, recip, fullText)
        except Exception:
            server.close()
            raise
        else:
            server.quit()

        self.saveSentMessage(fullText, saveMailSeparator)

        if failed:

            class SomeAddrsFailed(Exception):
                pass

            raise SomeAddrsFailed('Failed addrs:%s\n' % failed)
        self.trace('Send exit')
示例#3
0
    def insert(self, table, fields):
        def add_payload(message, obj):
            payload = Message()
            encoding = obj.get("encoding", "utf-8")
            if encoding and (encoding.upper()
                             in ("BASE64", "7BIT", "8BIT", "BINARY")):
                payload.add_header("Content-Transfer-Encoding", encoding)
            else:
                payload.set_charset(encoding)
            mime = obj.get("mime", None)
            if mime:
                payload.set_type(mime)
            if "text" in obj:
                payload.set_payload(obj["text"])
            elif "payload" in obj:
                payload.set_payload(obj["payload"])
            if "filename" in obj and obj["filename"]:
                payload.add_header("Content-Disposition",
                                   "attachment",
                                   filename=obj["filename"])
            message.attach(payload)

        mailbox = table.mailbox
        d = dict(((k.name, v) for k, v in fields))
        date_time = d.get("created") or datetime.datetime.now()
        struct_time = date_time.timetuple()
        if len(d) > 0:
            message = d.get("email", None)
            attachments = d.get("attachments", [])
            content = d.get("content", [])
            flags = " ".join([
                "\\%s" % flag.capitalize()
                for flag in ("answered", "deleted", "draft", "flagged",
                             "recent", "seen") if d.get(flag, False)
            ])
            if not message:
                from email.message import Message
                mime = d.get("mime", None)
                charset = d.get("encoding", None)
                message = Message()
                message["from"] = d.get("sender", "")
                message["subject"] = d.get("subject", "")
                message["date"] = self.convert_date(date_time, imf=True)

                if mime:
                    message.set_type(mime)
                if charset:
                    message.set_charset(charset)
                for item in ("to", "cc", "bcc"):
                    value = d.get(item, "")
                    if isinstance(value, basestring):
                        message[item] = value
                    else:
                        message[item] = ";".join([i for i in value])
                if (not message.is_multipart() and
                    (not message.get_content_type().startswith("multipart"))):
                    if isinstance(content, basestring):
                        message.set_payload(content)
                    elif len(content) > 0:
                        message.set_payload(content[0]["text"])
                else:
                    [add_payload(message, c) for c in content]
                    [add_payload(message, a) for a in attachments]
                message = message.as_string()

            result, data = self.connection.append(mailbox, flags, struct_time,
                                                  message)
            if result == "OK":
                uid = int(re.findall("\d+", str(data))[-1])
                return self.db(table.uid == uid).select(table.id).first().id
            else:
                raise Exception("IMAP message append failed: %s" % data)
        else:
            raise NotImplementedError("IMAP empty insert is not implemented")
示例#4
0
    def test_get_activities_fetch_extras(self):
        self.init()

        # Generate minimal fake responses for each request in the batch.
        #
        # Test with multiple activities to cover the bug described in
        # https://github.com/snarfed/bridgy/issues/22#issuecomment-56329848 :
        # util.CacheDict.get_multi() didn't originally handle generator args.
        batch = MIMEMultipart()
        for i, item in enumerate((COMMENT_GP, PLUSONER, RESHARER) * 2):
            msg = Message()
            msg.set_payload('HTTP/1.1 200 OK\n\r\n\r\n' +
                            json.dumps({'items': [item]}))
            msg['Content-ID'] = '<response-abc+%d>' % (i + 1)
            batch.attach(msg)

        # as_string() must be called before get_boundary() to generate the
        # boundaries between parts, but can't be called again, so we capture the
        # result.
        batch_str = batch.as_string()

        gpe_1 = ACTIVITY_GP_EXTRAS
        gpe_2 = copy.deepcopy(gpe_1)
        gpe_2['id'] = '002'
        http_seq = http.HttpMockSequence([
            ({
                'status': '200'
            }, json.dumps({'items': [gpe_1, gpe_2]})),
            ({
                'status':
                '200',
                'content-type':
                'multipart/mixed; boundary="%s"' % batch.get_boundary()
            }, batch_str),
            ({
                'status': '200'
            }, json.dumps({'items': [gpe_1, gpe_2]})),
        ])

        self.auth_entity.http = lambda: http_seq

        ase_1 = ACTIVITY_AS_EXTRAS
        ase_2 = copy.deepcopy(ase_1)
        ase_2['id'] = tag_uri('002')
        ase_2['object']['tags'][0]['id'] = tag_uri('002_liked_by_222')
        ase_2['object']['tags'][1]['id'] = tag_uri('002_shared_by_444')
        cache = util.CacheDict()
        self.assert_equals([ase_1, ase_2],
                           self.googleplus.get_activities(fetch_replies=True,
                                                          fetch_likes=True,
                                                          fetch_shares=True,
                                                          cache=cache))
        for id in '001', '002':
            for prefix in 'AGL ', 'AGS ':
                self.assertEquals(1, cache[prefix + id])

        # no new extras, so another request won't fill them in
        as_1 = copy.deepcopy(ACTIVITY_AS)
        for field in 'replies', 'plusoners', 'resharers':
            as_1['object'][field] = {'totalItems': 1}
        as_2 = copy.deepcopy(as_1)
        as_2['id'] = tag_uri('002')
        self.assert_equals([as_1, as_2],
                           self.googleplus.get_activities(fetch_replies=True,
                                                          fetch_likes=True,
                                                          fetch_shares=True,
                                                          cache=cache))
示例#5
0
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter12/pre-python-3.4/trad_gen_newhdrs.py
# Traditional Message Generation with Date and Message-ID

import email.utils
from email.message import Message

message = """Hello,

This is a test message from Chapter 12.  I hope you enjoy it!

-- Anonymous"""

msg = Message()
msg['To'] = '*****@*****.**'
msg['From'] = 'Test Sender <*****@*****.**>'
msg['Subject'] = 'Test Message, Chapter 12'
msg['Date'] = email.utils.formatdate(localtime=1)
msg['Message-ID'] = email.utils.make_msgid()
msg.set_payload(message)

print(msg.as_string())
def _getMessage(ctx, message):
    COMMASPACE = ', '
    sendermail = message.SenderAddress
    sendername = message.SenderName
    subject = message.Subject
    if isDebugMode():
        msg = getMessage(ctx, g_message, 251, subject)
        logMessage(ctx, INFO, msg, 'SmtpService', 'sendMailMessage()')
    textmsg = Message()
    content = message.Body
    flavors = content.getTransferDataFlavors()
    #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:
            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 message.hasAttachments():
        msg = MIMEMultipart()
        msg.epilogue = ''
        msg.attach(textmsg)
    else:
        msg = textmsg
    header = Header(sendername, 'utf-8')
    header.append('<' + sendermail + '>', 'us-ascii')
    msg['Subject'] = subject
    msg['From'] = header
    msg['To'] = COMMASPACE.join(message.getRecipients())
    msg['Message-ID'] = message.MessageId
    if message.ThreadId:
        msg['References'] = message.ThreadId
    if message.hasCcRecipients():
        msg['Cc'] = COMMASPACE.join(message.getCcRecipients())
    if message.ReplyToAddress != '':
        msg['Reply-To'] = message.ReplyToAddress
    xmailer = "LibreOffice / OpenOffice via smtpMailerOOo extention"
    try:
        configuration = getConfiguration(ctx, '/org.openoffice.Setup/Product')
        name = configuration.getByName('ooName')
        version = configuration.getByName('ooSetupVersion')
        xmailer = "%s %s via smtpMailerOOo extention" % (name, version)
    except:
        pass
    msg['X-Mailer'] = xmailer
    msg['Date'] = formatdate(localtime=True)
    for attachment in message.getAttachments():
        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))
        msg.attach(msgattachment)
    return msg
示例#7
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())
示例#8
0
class MailParser(MailTool):
    """
    解析消息文本和附件的方法
    微妙细节:对于非多组分的消息,Message对象负载是一个简单的字符串,对于多组分消息,它
    是Message对象组成的列表(可能拥有嵌套结构),这里我们无须区别对待这两种情况,因为Message
    遍历生成器总是先返回自身,因此对于非多组分消息也可以运行无误(遍历了单个对象);

    对于简单消息,这里总是把消息看成邮件的一部分;对于多组分消息,组分列表包含主体消息文本
    及所有附件;这样使得不属于类型文本的简单消息在UI中当作附件来处理(如保存,打开);对于
    某些不常见的组分类型,Message负载也有可能为None;

    Py3.1中,使用decode=1时将文本组分的负载作为bytes返回,否则肯恩是str;在mailtools中,文件
    保存时将文本存储为bytes,而主文本字节负载则依照邮件题头信息或操作平台默认值及推测结果
    解码为Unicode str;客户端可能需要转换其它负载:PyMailGUI利用题头解码保存到二进制文件的组
    分;

    支持基于抓取来的消息内容的自动解码,包括Subject等厂家题头及From和To等地质题头中的姓名:
    客户端必须在解析后请求此操作;解析器在显示之前不进行解码;
    """
    def walkNamedParts(self, message):
        """
        为避免重复组分命名逻辑的生成器;跳过多组分消息的题头,生成组分文件名;消息
        已经在解析好的email.message.Message对象中;不跳过罕见类型:负载可能为None,
        必须在组分保存时予以处理;某些其它情况可能也可以跳过;
        """
        for (ix, part) in enumerate(message.walk()):         # 遍历包括消息
            fulltype = part.get_content_type()                # ix包括跳过的组分
            maintype = part.get_content_maintype()
            if maintype == 'multipart':                       # multipart/*:容器
                continue
            elif fulltype == 'message/rfc822':               # 跳过message/rfc822
                continue                                     # 跳过所有message/*?
            else:
                filename, contype = self.partName(part, ix)
                yield filename, contype, part

    def partName(self, part, ix):
        """
        从消息组分中提取文件名和内容类型;
        文件名:尝试Content-Disposition名称参数,然后尝试Content-Type。或者根据
        mimetype推测结果生成文件名;
        """
        filename = part.get_filename()                    # 文件名在消息题头中?
        contype = part.get_content_type()                 # 主类/亚类为小写
        if not filename:
            filename = part.get_param('name')             # 尝试content-type名
        if not filename:
            if contype == 'text/plain':                   # 硬编码纯文本后缀名
                ext = '.txt'                              # 不然就猜它是.ksh!
            else:
                ext = mimetypes.guess_extension(contype)
                if not ext:                               # 使用通用默认设置
                    ext = '.bin'
            filename = 'part-%03d%s' % (ix, ext)
        return self.decodeHeader(filename), contype

    def saveParts(self, savedir, message):
        """
        将消息的所有部分保存为本地目录下的多个文件;
        返回(['maintype/subtype', 'filename'])列表以供调用函数使用,不过这里不打开任何
        组分或附件;
        get_paykiad解码base64、quoted-printable和uuencoding编码的数据;
        对于罕见类型,邮件解析器可能给我们返回一个None负载,大概应该跳过:这里为了保险
        将其转换为str;
        """
        if not os.path.exists(savedir):
            os.mkdir(savedir)
        partfiles = []
        for (filename, contype, part) in self.walkNamedParts(message):
            fullname = os.path.join(savedir, filename)
            fileobj = open(fullname, 'wb')              # 使用二进制模式
            content = part.get_payload(decode=1)        # 解码base64、quoted-printable和uuencoding编码
            if not isinstance(contype, bytes):         # 打开文件的rb参数需要bytes
                content = b'(no content)'               # decode=1返回bytes
            fileobj.write(content)                      # 不过某些负载返回None
            fileobj.close()                             # 不采用str(content)
            partfiles.append((contype, fullname))       # 供调用函数打开
        return partfiles

    def saveOnePart(self, savedir, partname, message):
        """
        同上,不过只根据名称查找和保存一个部分
        """
        if not os.path.exists(savedir):
            os.mkdir(savedir)
        fullname = os.path.join(savedir, partname)
        (contype, content) = self.findOnePart(partname, message)
        if not isinstance(content, bytes):           # 打开文件的rb参数需要bytes
            content = b'(no content)'                 # decode=1返回bytes
        open(fullname, 'wb').write(content)           # 不过某些负载返回bytes
        return contype, fullname                     # 不采用str(content)

    def partsList(self, message):
        """
        返回列表,由已解析的消息的所有部分的文件名组成,和saveParts使用相同文件名
        逻辑,不过这里不保存组分文件
        """
        validParts = self.walkNamedParts(message)
        return [filename for (filename, contype, part) in validParts]

    def findOnePart(self, partname, message):
        """
        查找并返回给定名称的组分的内容;
        用来与partsList分配使用;
        在这里我们也可以采用mimetypes.guess_type(partname);
        我们还能通过保存到字典而避免这个搜索操作;
        内容可能为str或bytes ---- 必要时转换;
        """
        for (filename, contype, part) in self.walkNamedParts(message):
            if filename == partname:
                content = part.get_payload(decode=1)   # 解码base64,quoted-printable,uuencoding
                return contype, content               # 可能为bytes文本

    def decodePayload(self, part, asStr=True):
        """
        将文本组分bytes解码为Unicode str,以便显示、自动换行等;组分是一个Message
        对象,(decode=1)则取消MIME邮件编码(base64,quoted-printable和uuencoding编码),
        bytes.decode()进行额外的Unicode文本字符串解码;首先尝试消息题头中的字符集编码
        名称(如果有且正确),然后尝试操作平台默认值及尝试推测数次,最后放弃,返回错误
        字符串。
        """
        payload = part.get_payload(decode=1)              # 负载可能是bytes
        if asStr and isinstance(payload, bytes):         # decode=1返回bytes
            tries = []
            enchdr = part.get_content_charset()           # 想尝试消息题头
            if enchdr:
                tries += [enchdr]                         # 想尝试题头
            tries += [sys.getdefaultencoding()]           # 相当于bytes.decode()
            tries += ['latin1', 'utf8']                   # 尝试8比特编码,包括ascii
            for t in tries:                              # 尝试utf8
                try:
                    payload = payload.decode(t)           # 试一试
                    break
                except (UnicodeError, LookupError):      # LookupError: 不好的名字
                    pass
            else:
                payload = '--sorry: cannot decode Unicode text--'
        return payload

    def findMainText(self, message, asStr=True):
        """
        对于面向对文本的客户端,返回首个文本组分的str,对于简单消息的负载或多组分消息
        的所有部分,查找text/plain,然后查找text/html,接着查找text/*,最后断定没有文
        本可显示;这个操作时一个试探过程,不过能应付大多数简单的、multipart/alternative
        和multipart/mixed信息;如果简单消息中没有,则content-type默认为text/plain;

        通过遍历而非列表扫描处理最高层次的嵌套;如果是非多组分消息但类型为text/html,
        返回HTML,因为文本带有HTML类型,调用函数可能用网页浏览器打开,提取纯文本,等
        等,如果是非多组分消息而且不是文本,那么没有文本可显示:在UI中保存/打开消息内
        容,缺陷:没有尝试将类型的text/plain的部分的数行连接在一起;
        文本负载可能为bytes ---- 在此解码为str;
        保存HTML文件时使用asStr=False以获取原始字节
        """
        # 尝试查找纯文本
        for part in message.walk():                           # 遍历访问消息
            type = part.get_content_type()                     # 如果是非多组分消息
            if type == 'text/plain':                           # 可能是base64,quoted-printable,uuencoding
                return type, self.decodePayload(part, asStr)  # 也进行bytes到str的转换?

        # 尝试查找HTML组分
        for part in message.walk():
            type = part.get_content_type()                     # 调用函数渲染html
            if type == 'text/html':
                return type, self.decodePayload(part, asStr)

        # 尝试任何其他类型,包括XML
        for part in message.walk():
            maintype = part.get_content_maintype()
            type = part.get_content_type()
            if maintype == 'text':
                return type, self.decodePayload(part, asStr)

        # 赌博做法:可以使用首个组分,不过它还没有被标记为文本
        failtext = '[No text to display]' if asStr else b'[No text to display]'
        return 'text/plain', failtext

    def decodeHeader(self, rawheader):
        """
        根据其内容,依照电子邮件和Unicode标准解码已有的国际化题头文本;如果未被
        编码或解码失败,按原样返回;
        客户端必须调用这个函数来显示信息:解析得到Message对象不进行解码;
        国际化题头示例:
        '=?UTF-8?Q?Introducing=20Top=20Values=20..Savers?=';
        国际化题头示例:
        'Man where did you get that =?UTF-8?Q?assistant=3F?=';

        decode_header自动处理题头字符串中的任何换行,如果题头的任何子字符串被编码,
        则可能返回多个组分,如果找到任何编码名称则在组分列表中返回所有bytes(未编码组分
        编码为raw-unicode-escape格式且enc=None),然而如果整个题头均未经过编码,则在enc=None
        时在Py3.1中返回单独一个部分,类型为str而非bytes(这里必须对混合类型做处理);

        以下代码属于最初的尝试,只要没有经过编码的子字符串,或者enc作为None返回(抛出异常,
        返回做改变的原始题头),那么可以成功运行:
        hdr, enc = email.header.decode_header(rawheader)[0]
        return hrd.decode(enc) # 如果enc=None则失败:没有编码名称或经过编码的子字符串
        """
        try:
            parts = email.header.decode_header(rawheader)
            decoded = []
            for (part, enc) in parts:                   # 对于所有子字符串
                if enc is None:                         # 组分未经编码?
                    if not isinstance(part, bytes):     # str: 整个题头未经比啊那么
                        decoded += [part]                # 否则进行unicode解码
                    else:
                        decoded += [part.decode('raw-unicode-escape')]
                else:
                    decoded += [part.decode(enc)]
            return ' '.join(decoded)
        except:
            return rawheader                            # 搏一把!

    def decodeAddrHeader(self, rawheader):
        """
        根据其内容,依照电子邮件和Unicode标准解码已有的国际化题头文本;必须解析电子邮件
        地址的第一个部分以获取国际化部分:'"=?UTF-8?Walmart?="<*****@*****.**>';
        From大概只有一个地址,不过To、CC和Bcc可能有多个;

        decodeHeader处理完整题头内嵌套着编码过的子字节串,不过这里我们不能简单地对于整个
        题头调用这个函数,因为如果编码后的名称字符串以"引号而非泛空格符或首位字符串结尾的
        话,这个函数将允许失败;mailSender模块中的encodeAddrHeader是这个函数的逆向操作;"

        以下代码属于最初的尝试,如果存在编码过的子字符串,将在处理姓名中的编码过的子字符
        串时失败,并针对未编码的bytes部分抛出异常;
        namebytes, nameenc = email.header.decode_header(name)[0] (do email+MIME)
        if nameenc: name = namebytes.decode(nameenc) (do Unicode?)
        """
        try:
            pairs = email.utils.getaddresses([rawheader])         # 分割地址和组分
            decoded = []                                          # 处理姓名中的逗号
            for (name, addr) in pairs:
                try:
                    name = self.decodeHeader(name)                # 电子邮件+MIME+Unicode
                except:
                    name = None                                  # 如果decodeHeader抛出异常则使用编码过的姓名
                joined = email.utils.formataddr((name, addr))     # 合并各部分
                decoded.append(joined)
            return ', '.join(decoded)                             # >= 1地址
        except:
            return self.decodeHeader(rawheader)                  # 尝试解码整个字符串

    def splitAddresses(self, field):
        """
        在UI中对于多个地址使用逗号分隔符,使用getaddresses来正确进行分割并允许
        地址的姓名部分中使用逗号,PyMailGUI在必要时使用它来分割由用户输入和题头
        复制得到的收件人、抄送、密送;如果域为空或碰到了一场,则返回空列表;
        """
        try:
            pairs = email.utils.getaddresses([field])                 # [(姓名,地址)]
            return [email.utils.formataddr(pair) for pair in pairs]  # [(姓名,地址)]
        except:
            return ''          # 用户输入域中句法错误?或者其它错误

    # 解析失败时的返回
    errorMessage = Message()
    errorMessage.set_payload('[Unalbe to parse message - format error]')

    def parseHeaders(self, mailtext):
        """
        仅解析题头,返回email.message.Message根对象,在题头解析后停止,即便后面
        没有东西(top)email.message.Message对象包含邮件题头域的映射关系消息对象的
        负载为None,而非原始主体文本;
        """
        try:
            return email.parser.Parser().parsestr(mailtext, headersonly=True)
        except:
            return self.errorMessage

    def parseMessage(self, fulltext):
        """
        解析整个消息,返回email.message.Message根对象,如果不采用is_multipart(),
        消息对象的负载是一个字符串,如果含有多个组分,那么消息对象的负载是其它Message
        对象,这个调用相当于email.message_from_string()
        """
        try:
            return email.parser.Parser().parsestr(fulltext)   # 可能失败!
        except:
            return self.errorMessage                          # 或者让调用函数来处理?可检查返回值

    def parseMessageRaw(self, fulltext):
        """
        只解析题头,返回email.message.Message根对象
        出于运行效率考虑,在题头解析后停止(这里还没用上)消息对象
        的负载是题头之后的邮件原始文本
        """
        try:
            return email.parser.HeaderParser().parsestr(fulltext)
        except:
            return self.errorMessage
示例#9
0
    def emailException(self, htmlErrMsg):
        """Email the exception.

        Send the exception via mail, either as an attachment,
        or as the body of the mail.
        """
        message = Message()

        # Construct the message headers
        headers = self.setting('ErrorEmailHeaders').copy()
        headers['Date'] = formatdate()
        headers['Mime-Version'] = '1.0'
        headers['Subject'] = '{} {}: {}'.format(
            headers.get('Subject', '[Webware Error]'), *sys.exc_info()[:2])
        for header, value in headers.items():
            if isinstance(value, (list, tuple)):
                value = ','.join(value)
            message.add_header(header, value)

        # Construct the message body
        if self.setting('EmailErrorReportAsAttachment'):
            # start off with a text/plain part
            text = self._emailBody.format(
                path=self.servletPathname(),
                ascTime=asclocaltime(self._time), time=self._time)
            message.set_type('multipart/mixed')
            part = Message()
            part.set_type('text/plain')
            body = StringIO()
            body.write(text)
            traceback.print_exc(file=body)
            part.set_payload(body.getvalue())
            body.close()
            message.attach(part)
            part = Message()
            # now add htmlErrMsg
            part.add_header('Content-Transfer-Encoding', '7bit')
            part.add_header(
                'Content-Description',
                'HTML version of Webware error message')
            part.add_header(
                'Content-Disposition',
                'attachment', filename='WebwareErrorMsg.html')
            part.set_type('text/html')
            part.set_payload(htmlErrMsg)
            message.attach(part)
        else:
            message.set_type('text/html')
            message.set_payload(htmlErrMsg, 'us-ascii')

        # Send the message
        server = self.setting('ErrorEmailServer')
        # This setting can be: server, server:port, server:port:user:password
        # or server:port:user:password:popserver:popport for "smtp after pop".
        parts = server.split(':', 5)
        server = port = user = passwd = None
        popserver = popssl = popport = None
        try:
            # fetch individual parts until we get an IndexError
            server = parts[0]
            try:
                port = int(parts[1])
            except ValueError:
                pass
            user = parts[2]
            passwd = parts[3]
            popserver = parts[4]
            try:
                popport = int(parts[5])
            except ValueError:
                popport = None
            if parts[6].lower() == 'ssl':
                popssl = True
        except IndexError:
            pass
        if user and passwd and popserver:
            # SMTP after POP
            if popssl is None and popport == 995:
                popssl = True
            popssl = poplib.POP3_SSL if popssl else poplib.POP3
            if popport:
                popserver = popssl(popserver, popport)
            else:
                popserver = popssl(popserver)
            popserver.set_debuglevel(0)
            popserver.user(user)
            popserver.pass_(passwd)
            try:
                popserver.quit()
            except Exception:
                pass
        if port:
            server = smtplib.SMTP(server, port)
        else:
            server = smtplib.SMTP(server)
        try:
            server.set_debuglevel(0)
            if user and passwd and not popserver:
                # SMTP-AUTH
                server.ehlo()
                if server.has_extn('starttls'):
                    server.starttls()
                    server.ehlo()
                server.login(user, passwd)
            body = message.as_string()
            server.sendmail(headers['From'], headers['To'], body)
        finally:
            try:
                server.quit()
            except Exception:
                pass
示例#10
0
def test_prepare_multipart():
    fixture_boundary = b'===============3996062709511591449=='

    here = os.path.dirname(__file__)
    multipart = os.path.join(here, 'fixtures/multipart.txt')

    client_cert = os.path.join(here, 'fixtures/client.pem')
    client_key = os.path.join(here, 'fixtures/client.key')
    client_txt = os.path.join(here, 'fixtures/client.txt')
    fields = {
        'form_field_1': 'form_value_1',
        'form_field_2': {
            'content': 'form_value_2',
        },
        'form_field_3': {
            'content': '<html></html>',
            'mime_type': 'text/html',
        },
        'form_field_4': {
            'content': '{"foo": "bar"}',
            'mime_type': 'application/json',
        },
        'file1': {
            'content': 'file_content_1',
            'filename': 'fake_file1.txt',
        },
        'file2': {
            'content': '<html></html>',
            'mime_type': 'text/html',
            'filename': 'fake_file2.html',
        },
        'file3': {
            'content': '{"foo": "bar"}',
            'mime_type': 'application/json',
            'filename': 'fake_file3.json',
        },
        'file4': {
            'filename': client_cert,
            'mime_type': 'text/plain',
        },
        'file5': {
            'filename': client_key,
        },
        'file6': {
            'filename': client_txt,
        },
    }

    content_type, b_data = prepare_multipart(fields)

    headers = Message()
    headers['Content-Type'] = content_type
    assert headers.get_content_type() == 'multipart/form-data'
    boundary = headers.get_boundary()
    assert boundary is not None

    with open(multipart, 'rb') as f:
        b_expected = f.read().replace(fixture_boundary, boundary.encode())

    # Depending on Python version, there may or may not be a trailing newline
    assert b_data.rstrip(b'\r\n') == b_expected.rstrip(b'\r\n')
示例#11
0
 def _makeMessage(self):
     from email.message import Message
     return Message()
示例#12
0
文件: email_.py 项目: cerha/pytis
 def _create_message(self):
     """Return basic instance of Message."""
     self.msg = Message()
示例#13
0
    def apply(self, ui):
        # get message to forward if not given in constructor
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        envelope = Envelope()

        if self.inline:  # inline mode
            # set body text
            name, address = self.message.get_author()
            timestamp = self.message.get_date()
            qf = settings.get_hook('forward_prefix')
            if qf:
                quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
            else:
                quote = 'Forwarded message from %s (%s):\n' % (name or address,
                                                               timestamp)
            mailcontent = quote
            quotehook = settings.get_hook('text_quote')
            if quotehook:
                mailcontent += quotehook(self.message.accumulate_body())
            else:
                quote_prefix = settings.get('quote_prefix')
                for line in self.message.accumulate_body().splitlines():
                    mailcontent += quote_prefix + line + '\n'

            envelope.body = mailcontent

            for a in self.message.get_attachments():
                envelope.attach(a)

        else:  # attach original mode
            # attach original msg
            original_mail = Message()
            original_mail.set_type('message/rfc822')
            original_mail['Content-Disposition'] = 'attachment'
            original_mail.set_payload(email_as_string(mail))
            envelope.attach(Attachment(original_mail))

        # copy subject
        subject = decode_header(mail.get('Subject', ''))
        subject = 'Fwd: ' + subject
        forward_subject_hook = settings.get_hook('forward_subject')
        if forward_subject_hook:
            subject = forward_subject_hook(subject)
        else:
            fsp = settings.get('forward_subject_prefix')
            if not subject.startswith(('Fwd:', fsp)):
                subject = fsp + subject
        envelope.add('Subject', subject)

        # set From-header and sending account
        try:
            from_header, _ = determine_sender(mail, 'reply')
        except AssertionError as e:
            ui.notify(str(e), priority='error')
            return
        envelope.add('From', from_header)

        # continue to compose
        ui.apply_command(
            ComposeCommand(envelope=envelope, spawn=self.force_spawn))
示例#14
0
    def __init__(
        self,
        formal_name=None,
        app_id=None,
        app_name=None,
        id=None,
        icon=None,
        author=None,
        version=None,
        home_page=None,
        description=None,
        startup=None,
        on_exit=None,
        factory=None,
    ):
        # Keep an accessible copy of the app instance
        App.app = self

        # We need a module name to load app metadata. If an app_name has been
        # provided, we can set the app name now, and derive the module name
        # from there.
        if app_name:
            self._app_name = app_name
        else:
            # If the code is contained in appname.py, and you start the app
            # using `python -m appname`, the main module package will report
            # as ''. Set the initial app name as None.
            # If the code is contained in appname.py, and you start the app
            # using `python appname.py`, the main module will report as None.
            # If the code is contained in a folder, and you start the app
            # using `python -m appname`, the main module will report as the
            # name of the folder.
            main_module_pkg = sys.modules['__main__'].__package__
            if main_module_pkg == '':
                self._app_name = None
            else:
                self._app_name = main_module_pkg

            # During tests, and when running from a prompt, there won't be
            # a __main__ module.

            # Try deconstructing the app name from the app ID
            if self._app_name is None and app_id:
                self._app_name = app_id.split('.')[-1]

        # Load the app metdata (if it is available)
        # Apps packaged with Briefcase will have this metadata.
        try:
            self.metadata = importlib_metadata.metadata(self.module_name)
        except importlib_metadata.PackageNotFoundError:
            self.metadata = Message()

        # Now that we have metadata, we can fix the app name (in the case
        # where the app name and the module name differ - e.g., an app name
        # of ``hello-world`` will have a module name of ``hello_world``).
        # We use the PEP566-compliant key ``Name```, rather than the internally
        # consistent key ``App-Name```.
        if self.metadata['Name'] is not None:
            self._app_name = self.metadata['Name']

        # Whatever app name has been given, speculatively attempt to import
        # the app module. Single-file apps won't have an app folder; apps with
        # misleading or misconfigured app names haven't given us enough
        # metadata to determine the app folder. In those cases, fall back to
        # an app name that *will* exist (``toga```)
        try:
            sys.modules[self.module_name]
        except KeyError:
            # Well that didn't work...
            self._app_name = 'toga'

        # If a name has been provided, use it; otherwise, look to
        # the module metadata. However, a name *must* be provided.
        if formal_name:
            self._formal_name = formal_name
        else:
            self._formal_name = self.metadata['Formal-Name']

        if self._formal_name is None:
            raise RuntimeError('Toga application must have a formal name')

        # If an app_id has been provided, use it; otherwise, look to
        # the module metadata. However, an app_id *must* be provied
        if app_id:
            self._app_id = app_id
        else:
            self._app_id = self.metadata['App-ID']

        if self._app_id is None:
            raise RuntimeError('Toga application must have an App ID')

        # If an author has been provided, use it; otherwise, look to
        # the module metadata.
        if author:
            self._author = author
        elif self.metadata['Author']:
            self._author = self.metadata['Author']

        # If a version has been provided, use it; otherwise, look to
        # the module metadata.
        if version:
            self._version = version
        elif self.metadata['Version']:
            self._version = self.metadata['Version']

        # If a home_page has been provided, use it; otherwise, look to
        # the module metadata.
        if home_page:
            self._home_page = home_page
        elif self.metadata['Home-page']:
            self._home_page = self.metadata['home_page']

        # If a description has been provided, use it; otherwise, look to
        # the module metadata.
        if description:
            self._description = description
        elif self.metadata['description']:
            self._description = self.metadata['Summary']

        # Set the application DOM ID; create an ID if one hasn't been provided.
        self._id = id if id else identifier(self)

        # Get a platform factory, and a paths instance from the factory.
        self.factory = get_platform_factory(factory)
        self.paths = self.factory.paths

        # If an icon (or icon name) has been explicitly provided, use it;
        # otherwise, the icon will be based on the app name.
        if icon:
            self.icon = icon
        else:
            self.icon = 'resources/{app_name}'.format(app_name=self.app_name)

        self.commands = CommandSet(factory=self.factory)

        self._startup_method = startup

        self._main_window = None
        self._on_exit = None

        self._full_screen_windows = None

        self._impl = self._create_impl()
        self.on_exit = on_exit
示例#15
0
    def decorate(message, hide_sender, sender):
        """
        Notify the recipient about the sender rewrite.
        """

        footer = [
            '------------', 'Du kan ikke svare direkte på denne eposten.'
        ]

        if not hide_sender:
            footer.append(
                f'Opprinnelig avsender er {sender}, send svar til denne adressen.'
            )
            footer.append(
                'Denne eposten har uorginal avsender for å redusere risikoen for at '
                'meldingen oppfattes som spam.')
        else:
            footer.append(
                'Opprinnelig avsender har valgt å skjule sin adresse.')

        footer = '\n'.join(footer)
        charset = message.get_content_charset() or 'us-ascii'
        content_type = message.get_content_type()

        wrap = True
        if not message.is_multipart() and content_type == 'text/plain':
            format_param = message.get_param('format')
            delsp = message.get_param('delsp')
            transfer_encoding = message.get('content-transfer-encoding')

            try:
                old_payload = message.get_payload(decode=True).decode(charset)
                del message['content-transfer-encoding']

                footer_separator = '\n'
                payload = old_payload + footer_separator + footer

                for cset in (charset, 'utf-8'):
                    try:
                        message.set_payload(payload.encode(cset), cset)
                    except UnicodeError:
                        pass
                    else:
                        if format_param:
                            message.set_param('format', format_param)
                        if delsp:
                            message.set_param('delsp', delsp)
                        wrap = False
                        break
            except (LookupError, UnicodeError):
                if transfer_encoding:
                    del message['content-transfer-encoding']
                    message['Content-Transfer-Encoding'] = transfer_encoding

        elif message.get_content_type() == 'multipart/mixed':
            payload = message.get_payload()
            if not isinstance(payload, list):
                payload = [payload]

            mime_footer = MIMEText(footer.encode('utf-8'), 'plain', 'utf-8')
            mime_footer['Content-Disposition'] = 'inline'
            payload.append(mime_footer)
            message.set_payload(payload)
            wrap = False

        if not wrap:
            return

        inner = Message()
        for h, v in message.items():
            if h.lower().startswith('content-'):
                inner[h] = v
        inner.set_payload(message.get_payload())
        inner.set_unixfrom(message.get_unixfrom())
        inner.preamble = message.preamble
        inner.epilogue = message.epilogue
        inner.set_default_type(message.get_default_type())
        if hasattr(message, '__version__'):
            inner.__version__ = message.__version__
        payload = [inner]
        mime_footer = MIMEText(footer.encode('utf-8'), 'plain', 'utf-8')
        mime_footer['Content-Disposition'] = 'inline'
        payload.append(mime_footer)
        message.set_payload(payload)
        del message['content-type']
        del message['content-transfer-encoding']
        del message['content-disposition']
        message['Content-Type'] = 'multipart/mixed'
示例#16
0
def header_check (db, cl, nodeid, new_values) :
    """ Check header of new messages and determine original customer
        from that header -- only if sender is the support special
        account (any account with system status).  If send_to_customer
        flag is set *and* account is not a system account, munge
        the headers and add X-ROUNDUP-TO and X-ROUNDUP-CC headers.
    """
    send_to_customer = False
    # Be sure to alway set send_to_customer to False!
    if 'send_to_customer' in new_values :
        send_to_customer = new_values ['send_to_customer']
        new_values ['send_to_customer'] = False
    newmsgs = new_values.get ('messages')
    if not newmsgs :
        return
    newmsgs = set (newmsgs)
    if nodeid :
        oldmsgs = set (cl.get (nodeid, 'messages'))
    else :
        oldmsgs = set ()
    system  = db.user_status.lookup ('system')
    cemail  = db.contact_type.lookup ('Email')
    for msgid in newmsgs.difference (oldmsgs) :
        msg    = db.msg.getnode (msgid)
        h      = None
        if msg.header :
            h = Parser ().parsestr (msg.header, headersonly = True)
        else :
            h = Message ()
        if db.user.get (msg.author, 'status') == system :
            frm  = fix_emails (h.get_all ('From'))
            subj = header_utf8 (h.get_all ('Subject') [0])
            if  (   frm
                and 'customer' not in new_values
                and 'emails' not in new_values
                ) :
                cc  = {}
                if not nodeid :
                    # use only first 'From' address (there shouldn't be more)
                    rn, mail = getaddresses (frm) [0]
                    # the *to* address in this mail is the support user we
                    # want as a from-address for future mails *to* this user
                    autad = None
                    hto = fix_emails (h.get_all ('To'))
                    if hto :
                        torn, autad = getaddresses (hto) [0]
                        if not autad.startswith ('support') :
                            autad = None
                    c = find_or_create_contact \
                        (db, mail, rn, frm = autad, subject = subj)
                    cust  = new_values ['customer'] = \
                        db.contact.get (c, 'customer')
                    new_values ['emails'] = [c]
                else :
                    supi = cl.getnode (nodeid)
                    cust = supi.customer
                    new_values ['emails']    = supi.emails
                    new_values ['cc_emails'] = supi.cc_emails
                    if supi.cc :
                        cc = dict.fromkeys \
                            (x.strip ().lower () for x in supi.cc.split (','))
                # Parse To and CC headers to find more customer email
                # addresses. Check if these contain the same domain
                # part as the From.
                ccs = h.get_all ('CC') or []
                tos = h.get_all ('To') or []
                if nodeid :
                    tos.extend (frm)
                cfrm = db.customer.get (cust, 'fromaddress')
                alltocc = dict.fromkeys (new_values ['emails'])
                if 'cc_emails' in new_values :
                    alltocc.update (dict.fromkeys (new_values ['cc_emails']))
                for addrs, field in ((tos, 'emails'), (ccs, 'cc_emails')) :
                    addrs = fix_emails (addrs)
                    for rn, mail in getaddresses (addrs) :
                        if mail == cfrm :
                            continue
                        c = find_or_create_contact \
                            (db, mail, rn, customer = cust)
                        if c :
                            if field not in new_values :
                                new_values [field] = []
                            if c not in alltocc :
                                new_values [field].append (c)
                                alltocc [c] = 1
                        elif uidFromAddress (db, (rn, mail), create = 0) :
                            # ignore internal addresses
                            pass
                        else :
                            cc [mail.lower ()] = 1
                if cc :
                    new_values ['cc'] = ', '.join (cc.keys ())
        else :
            if send_to_customer :
                mails = []
                cc = []
                if 'emails' in new_values :
                    mails = new_values ['emails']
                elif nodeid :
                    mails = cl.get (nodeid, 'emails')
                if 'cc_emails' in new_values :
                    mcc = new_values ['cc_emails']
                elif nodeid :
                    mcc = cl.get (nodeid, 'cc_emails')
                mails = mails or []
                mcc   = mcc or []
                mails = (db.contact.get (x, 'contact') for x in mails)
                mcc   = (db.contact.get (x, 'contact') for x in mcc)
                if 'cc' in new_values :
                    cc = new_values ['cc']
                elif nodeid :
                    cc = cl.get (nodeid, 'cc')
                m  = ','.join (mails)
                mc = ','.join (mcc)
                if mc :
                    if cc :
                        mc = ','.join ((mc, cc))
                else :
                    mc = cc
                if not m and not mc :
                    raise Reject, \
                        _ ("Trying to send to customer with empty CC and "
                           "without configured contact-email for customer"
                          )
                if m :
                    h.add_header ('X-ROUNDUP-TO', m)
                if mc :
                    h.add_header ('X-ROUNDUP-CC', mc)
                if 'bcc' in new_values :
                    bcc = new_values ['bcc']
                elif nodeid :
                    bcc = cl.get (nodeid, 'bcc')
                if bcc :
                    h.add_header ('X-ROUNDUP-BCC', bcc)
                # Time-Stamp of first reply to customer
                if not nodeid or not cl.get (nodeid, 'first_reply') :
                    new_values ['first_reply'] = Date ('.')
        h = h.as_string ()
        if h != '\n' and h != msg.header :
            db.msg.set (msgid, header = h)
示例#17
0
    def send_raw(self, job, message, config):
        config = dict(self.middleware.call_sync('mail.config'), **config)

        if config['fromname']:
            from_addr = Header(config['fromname'], 'utf-8')
            try:
                config['fromemail'].encode('ascii')
            except UnicodeEncodeError:
                from_addr.append(f'<{config["fromemail"]}>', 'utf-8')
            else:
                from_addr.append(f'<{config["fromemail"]}>', 'ascii')
        else:
            try:
                config['fromemail'].encode('ascii')
            except UnicodeEncodeError:
                from_addr = Header(config['fromemail'], 'utf-8')
            else:
                from_addr = Header(config['fromemail'], 'ascii')

        interval = message.get('interval')
        if interval is None:
            interval = timedelta()
        else:
            interval = timedelta(seconds=interval)

        sw_name = self.middleware.call_sync('system.info')['version'].split(
            '-', 1)[0]

        channel = message.get('channel')
        if not channel:
            channel = sw_name.lower()
        if interval > timedelta():
            channelfile = '/tmp/.msg.%s' % (channel)
            last_update = datetime.now() - interval
            try:
                last_update = datetime.fromtimestamp(
                    os.stat(channelfile).st_mtime)
            except OSError:
                pass
            timediff = datetime.now() - last_update
            if (timediff >= interval) or (timediff < timedelta()):
                # Make sure mtime is modified
                # We could use os.utime but this is simpler!
                with open(channelfile, 'w') as f:
                    f.write('!')
            else:
                raise CallError(
                    'This message was already sent in the given interval')

        verrors = self.__password_verify(config['pass'], 'mail-config.pass')
        if verrors:
            raise verrors
        to = message.get('to')
        if not to:
            to = [
                self.middleware.call_sync('user.query',
                                          [('username', '=', 'root')],
                                          {'get': True})['email']
            ]
            if not to[0]:
                raise CallError('Email address for root is not configured')

        if message.get('attachments'):
            job.check_pipe("input")

            def read_json():
                f = job.pipes.input.r
                data = b''
                i = 0
                while True:
                    read = f.read(1048576)  # 1MiB
                    if read == b'':
                        break
                    data += read
                    i += 1
                    if i > 50:
                        raise ValueError(
                            'Attachments bigger than 50MB not allowed yet')
                if data == b'':
                    return None
                return json.loads(data)

            attachments = read_json()
        else:
            attachments = None

        if 'html' in message or attachments:
            msg = MIMEMultipart()
            msg.preamble = 'This is a multi-part message in MIME format.'
            if 'html' in message:
                msg2 = MIMEMultipart('alternative')
                msg2.attach(
                    MIMEText(message['text'], 'plain', _charset='utf-8'))
                msg2.attach(MIMEText(message['html'], 'html',
                                     _charset='utf-8'))
                msg.attach(msg2)
            if attachments:
                for attachment in attachments:
                    m = Message()
                    m.set_payload(attachment['content'])
                    for header in attachment.get('headers'):
                        m.add_header(header['name'], header['value'],
                                     **(header.get('params') or {}))
                    msg.attach(m)
        else:
            msg = MIMEText(message['text'], _charset='utf-8')

        msg['Subject'] = message['subject']

        msg['From'] = from_addr
        msg['To'] = ', '.join(to)
        if message.get('cc'):
            msg['Cc'] = ', '.join(message.get('cc'))
        msg['Date'] = formatdate()

        local_hostname = socket.gethostname()

        msg['Message-ID'] = "<%s-%s.%s@%s>" % (
            sw_name.lower(), datetime.utcnow().strftime("%Y%m%d.%H%M%S.%f"),
            base64.urlsafe_b64encode(os.urandom(3)), local_hostname)

        extra_headers = message.get('extra_headers') or {}
        for key, val in list(extra_headers.items()):
            # We already have "Content-Type: multipart/mixed" and setting "Content-Type: text/plain" like some scripts
            # do will break python e-mail module.
            if key.lower() == "content-type":
                continue

            if key in msg:
                msg.replace_header(key, val)
            else:
                msg[key] = val

        syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_MAIL)
        try:
            server = self._get_smtp_server(config,
                                           message['timeout'],
                                           local_hostname=local_hostname)
            # NOTE: Don't do this.
            #
            # If smtplib.SMTP* tells you to run connect() first, it's because the
            # mailserver it tried connecting to via the outgoing server argument
            # was unreachable and it tried to connect to 'localhost' and barfed.
            # This is because FreeNAS doesn't run a full MTA.
            # else:
            #    server.connect()
            headers = '\n'.join([f'{k}: {v}' for k, v in msg._headers])
            syslog.syslog(f"sending mail to {', '.join(to)}\n{headers}")
            server.sendmail(from_addr.encode(), to, msg.as_string())
            server.quit()
        except Exception as e:
            # Don't spam syslog with these messages. They should only end up in the
            # test-email pane.
            # We are only interested in ValueError, not subclasses.
            if e.__class__ is ValueError:
                raise CallError(str(e))
            syslog.syslog(f'Failed to send email to {", ".join(to)}: {str(e)}')
            if isinstance(e, smtplib.SMTPAuthenticationError):
                raise CallError(
                    f'Authentication error ({e.smtp_code}): {e.smtp_error}',
                    errno.EAUTH if osc.IS_FREEBSD else errno.EPERM)
            self.logger.warn('Failed to send email: %s', str(e), exc_info=True)
            if message['queue']:
                with MailQueue() as mq:
                    mq.append(msg)
            raise CallError(f'Failed to send email: {e}')
        return True
示例#18
0
 def setUp(self):
     self.message = EmailMessage(recipient='recipient',
                                 sender='sender',
                                 message=Message())
示例#19
0
    def do_POST(self):
        try:
            # Connect to destination
            self._connect_to_host()
        except Exception as e:
            self.send_error(500, str(e))
            return

        # The request that contains the request to the website is located
        # inside the POST request body from the stegoclient
        log.debug("Got stego-request from stegoclient")
        # io stream that represents the stego medium (i.e. image)
        req_body = io.BytesIO(
            self.rfile.read(int(self.headers.get("Content-Length", 0))))
        stego_message = stego.extract(medium=req_body)

        # Get Host and Port from the original request
        host, port = self._get_hostaddr_from_headers(stego_message)

        # establish connection to the website
        log.info(f"Connecting to {host}:{port}")
        self.server = Server(host=host, port=port)

        # Just relay the original request to the website
        log.debug("Relaying extracted request to website")
        self.server.send(stego_message)

        # Parse response from website
        h = HTTPResponse(self.server.conn)
        h.begin()

        # Get rid of hop-by-hop headers
        self.filter_headers(h.msg)

        # Build response from website
        log.debug("Building response from website")
        stego_resp = self._build_stego_response(self.request_version, h.status,
                                                h.reason, h.msg, h.read())

        # Build header to stegoclient
        header = Message()
        header.add_header("Host", f"{cfg.REMOTE_ADDR[0]}:{cfg.REMOTE_ADDR[1]}")
        header.add_header("Connection", "keep-alive")
        cover = self._get_cover_object()
        max_size = self._calc_max_size(cover)
        resp_len = len(stego_resp)

        if (max_size is not None and resp_len > max_size):
            log.debug(
                f"Can't fit response ({resp_len} bytes) into stego-response - "
                f"splitting into chunks of {max_size} bytes.")

            header.add_header("Transfer-Encoding", "chunked")
            resp_to_client = self._build_response_header(
                cfg.STEGO_HTTP_VERSION, h.status, h.reason, header)

            # Send headers to client
            log.debug("Sending chunked stego-response header to stegoclient")
            self.client.send(resp_to_client)

            start = time.time()
            chunk_count = 0
            for chunk in self._split_into_chunks(stego_resp, max_size):
                if isinstance(cover, Image.Image):
                    tmp_cover = cover.copy()
                else:
                    tmp_cover = cover

                # Send chunks
                log.debug(f"Sending chunk with size: {len(chunk)} bytes")
                self._write_chunks(stego.embed(cover=tmp_cover, message=chunk))
                chunk_count += 1

            end = time.time()
            # send "end of chunks" trailer
            self._write_end_of_chunks()
            log.debug(f"{chunk_count} chunks sent in {end - start:.2f}s.")
        else:
            # Encapsulate response inside response to stego client
            log.debug("Embedding response from website in stego-response")

            start = time.time()
            stego_medium = stego.embed(cover=cover, message=stego_resp)
            end = time.time()
            log.debug(
                f"Took {end - start:.2f}s to embed response in stego-response")

            header.add_header("Content-Length", str(len(stego_medium)))

            resp_to_client = self._build_response(cfg.STEGO_HTTP_VERSION,
                                                  h.status, h.reason, header,
                                                  stego_medium)

            # Relay the message
            log.debug("Relaying stego-response to stegoclient")
            self.client.send(resp_to_client)

        if isinstance(cover, Image.Image):
            cover.close()

        # Let's close off the remote end
        h.close()
        self.server.close()
示例#20
0
def run_and_output_changes(env, pool, send_via_email):
    import json
    from difflib import SequenceMatcher

    if not send_via_email:
        out = ConsoleOutput()
    else:
        import io
        out = FileOutput(io.StringIO(""), 70)

    # Run status checks.
    cur = BufferedOutput()
    run_checks(True, env, cur, pool)

    # Load previously saved status checks.
    cache_fn = "/var/cache/mailinabox/status_checks.json"
    if os.path.exists(cache_fn):
        prev = json.load(open(cache_fn))

        # Group the serial output into categories by the headings.
        def group_by_heading(lines):
            from collections import OrderedDict
            ret = OrderedDict()
            k = []
            ret["No Category"] = k
            for line_type, line_args, line_kwargs in lines:
                if line_type == "add_heading":
                    k = []
                    ret[line_args[0]] = k
                else:
                    k.append((line_type, line_args, line_kwargs))
            return ret

        prev_status = group_by_heading(prev)
        cur_status = group_by_heading(cur.buf)

        # Compare the previous to the current status checks
        # category by category.
        for category, cur_lines in cur_status.items():
            if category not in prev_status:
                out.add_heading(category + " -- Added")
                BufferedOutput(with_lines=cur_lines).playback(out)
            else:
                # Actual comparison starts here...
                prev_lines = prev_status[category]

                def stringify(lines):
                    return [json.dumps(line) for line in lines]

                diff = SequenceMatcher(None, stringify(prev_lines),
                                       stringify(cur_lines)).get_opcodes()
                for op, i1, i2, j1, j2 in diff:
                    if op == "replace":
                        out.add_heading(category + " -- Previously:")
                    elif op == "delete":
                        out.add_heading(category + " -- Removed")
                    if op in ("replace", "delete"):
                        BufferedOutput(
                            with_lines=prev_lines[i1:i2]).playback(out)

                    if op == "replace":
                        out.add_heading(category + " -- Currently:")
                    elif op == "insert":
                        out.add_heading(category + " -- Added")
                    if op in ("replace", "insert"):
                        BufferedOutput(
                            with_lines=cur_lines[j1:j2]).playback(out)

        for category, prev_lines in prev_status.items():
            if category not in cur_status:
                out.add_heading(category)
                out.print_warning("This section was removed.")

    if send_via_email:
        # If there were changes, send off an email.
        buf = out.buf.getvalue()
        if len(buf) > 0:
            # create MIME message
            from email.message import Message
            msg = Message()
            msg['From'] = "\"%s\" <administrator@%s>" % (
                env['PRIMARY_HOSTNAME'], env['PRIMARY_HOSTNAME'])
            msg['To'] = "administrator@%s" % env['PRIMARY_HOSTNAME']
            msg['Subject'] = "[%s] Status Checks Change Notice" % env[
                'PRIMARY_HOSTNAME']
            msg.set_payload(buf, "UTF-8")

            # send to administrator@
            import smtplib
            mailserver = smtplib.SMTP('localhost', 25)
            mailserver.ehlo()
            mailserver.sendmail(
                "administrator@%s" % env['PRIMARY_HOSTNAME'],  # MAIL FROM
                "administrator@%s" % env['PRIMARY_HOSTNAME'],  # RCPT TO
                msg.as_string())
            mailserver.quit()

    # Store the current status checks output for next time.
    os.makedirs(os.path.dirname(cache_fn), exist_ok=True)
    with open(cache_fn, "w") as f:
        json.dump(cur.buf, f, indent=True)
示例#21
0
    def new_message(self):
        from email.message import Message

        return Message()
示例#22
0
class MailParser(MailTool):
    """
    methods for parsing message text, attachments

    subtle thing: Message object payloads are either a simple
    string for non-multipart messages, or a list of Message
    objects if multipart (possibly nested); we don't need to
    distinguish between the two cases here, because the Message
    walk generator always returns self first, and so works fine
    on non-multipart messages too (a single object is walked);

    for simple messages, the message body is always considered
    here to be the sole part of the mail;  for multipart messages,
    the parts list includes the main message text, as well as all
    attachments;  this allows simple messages not of type text to
    be handled like attachments in a UI (e.g., saved, opened);
    Message payload may also be None for some oddball part types;

    4E note: in Py 3.1, text part payloads are returned as bytes 
    for decode=1, and might be str otherwise; in mailtools, text
    is stored as bytes for file saves, but main-text bytes payloads
    are decoded to Unicode str per mail header info or platform 
    default+guess; clients may need to convert other payloads:
    PyMailGUI uses headers to decode parts saved to binary files;

    4E supports fetched message header auto-decoding per its own
    content, both for general headers such as Subject, as well as
    for names in address header such as From and To; client must 
    request this after parse, before display: parser doesn't decode;
    """

    def walkNamedParts(self, message):
        """
        generator to avoid repeating part naming logic;
        skips multipart headers, makes part filenames;
        message is already parsed email.message.Message object;
        doesn't skip oddball types: payload may be None, must
        handle in part saves; some others may warrant skips too;
        """
        for (ix, part) in enumerate(message.walk()):    # walk includes message
            fulltype = part.get_content_type()          # ix includes parts skipped
            maintype = part.get_content_maintype()
            if maintype == 'multipart':                 # multipart/*: container
                continue                                
            elif fulltype == 'message/rfc822':          # 4E: skip message/rfc822
                continue                                # skip all message/* too?
            else:
                filename, contype = self.partName(part, ix)
                yield (filename, contype, part)

    def partName(self, part, ix):
        """
        extract filename and content type from message part;
        filename: tries Content-Disposition, then Content-Type
        name param, or generates one based on mimetype guess;
        """
        filename = part.get_filename()                # filename in msg hdrs?
        contype  = part.get_content_type()            # lowercase maintype/subtype
        if not filename:
            filename = part.get_param('name')         # try content-type name
        if not filename:
            if contype == 'text/plain':               # hardcode plain text ext
                ext = '.txt'                          # else guesses .ksh!
            else:
                ext = mimetypes.guess_extension(contype)
                if not ext: ext = '.bin'              # use a generic default
            filename = 'part-%03d%s' % (ix, ext)
        return (self.decodeHeader(filename), contype) # oct 2011: decode i18n fnames


    def saveParts(self, savedir, message):
        """
        store all parts of a message as files in a local directory;
        returns [('maintype/subtype', 'filename')] list for use by
        callers, but does not open any parts or attachments here;
        get_payload decodes base64, quoted-printable, uuencoded data;
        mail parser may give us a None payload for oddball types we
        probably should skip over: convert to str here to be safe;
        """
        if not os.path.exists(savedir):
            os.mkdir(savedir)
        partfiles = []
        for (filename, contype, part) in self.walkNamedParts(message):
            fullname = os.path.join(savedir, filename)
            fileobj  = open(fullname, 'wb')             # use binary mode
            content  = part.get_payload(decode=1)       # decode base64,qp,uu
            if not isinstance(content, bytes):          # 4E: need bytes for rb
                content = b'(no content)'               # decode=1 returns bytes,
            fileobj.write(content)                      # but some payloads None
            fileobj.close()                             # 4E: not str(content)
            partfiles.append((contype, fullname))       # for caller to open
        return partfiles

    def saveOnePart(self, savedir, partname, message):
        """
        ditto, but find and save just one part by name
        """
        if not os.path.exists(savedir):
            os.mkdir(savedir)
        fullname = os.path.join(savedir, partname)
        (contype, content) = self.findOnePart(partname, message)
        if not isinstance(content, bytes):          # 4E: need bytes for rb
            content = b'(no content)'               # decode=1 returns bytes,
        open(fullname, 'wb').write(content)         # but some payloads None
        return (contype, fullname)                  # 4E: not str(content)

    def partsList(self, message):
        """"
        return a list of filenames for all parts of an
        already parsed message, using same filename logic
        as saveParts, but do not store the part files here
        """
        validParts = self.walkNamedParts(message)
        return [filename for (filename, contype, part) in validParts]

    def findOnePart(self, partname, message):
        """
        find and return part's content, given its name;
        intended to be used in conjunction with partsList;
        we could also mimetypes.guess_type(partname) here;
        we could also avoid this search by saving in dict;
        4E: content may be str or bytes--convert as needed;
        """
        for (filename, contype, part) in self.walkNamedParts(message):
            if filename == partname:
                content = part.get_payload(decode=1)          # does base64,qp,uu
                return (contype, content)                     # may be bytes text

    def decodedPayload(self, part, asStr=True):
        """
        4E: decode text part bytes to Unicode str for display, line wrap, 
        etc.; part is a Message; (decode=1) undoes MIME email encodings 
        (base64, uuencode, qp), bytes.decode() performs additional Unicode 
        text string decodings; tries charset encoding name in message 
        headers first (if present, and accurate), then tries platform 
        defaults and a few guesses before giving up with error string;
        """
        payload = part.get_payload(decode=1)           # payload may be bytes
        if asStr and isinstance(payload, bytes):       # decode=1 returns bytes
            tries = []
            enchdr = part.get_content_charset()        # try msg headers first!
            if enchdr:
                tries += [enchdr]                      # try headers first   
            tries += [sys.getdefaultencoding()]        # same as bytes.decode()
            tries += ['latin1', 'utf8']                # try 8-bit, incl ascii
            for trie in tries:                         # try utf8 (windows dflt)
                try:
                    payload = payload.decode(trie)     # give it a shot, eh?
                    break
                except (UnicodeError, LookupError):    # lookuperr: bad name
                    pass
            else:
                payload = '--Sorry: cannot decode Unicode text--' 
        return payload

    def findMainText(self, message, asStr=True):
        """
        for text-oriented clients, return first text part's str;
        for the payload of a simple message, or all parts of
        a multipart message, looks for text/plain, then text/html,
        then text/*, before deducing that there is no text to
        display;  this is a heuristic, but covers most simple,
        multipart/alternative, and multipart/mixed messages;
        content-type defaults to text/plain if not in simple msg;

        handles message nesting at top level by walking instead
        of list scans;  if non-multipart but type is text/html,
        returns the HTML as the text with an HTML type: caller
        may open in web browser, extract plain text, etc;  if 
        nonmultipart and not text, there is  no text to display: 
        save/open message content in UI; caveat: does not try 
        to concatenate multiple inline text/plain parts if any;
        4E: text payloads may be bytes--decodes to str here;
        4E: asStr=False to get raw bytes for HTML file saves;
        """

        # try to find a plain text
        for part in message.walk():                            # walk visits message
            type = part.get_content_type()                     # if nonmultipart
            if type == 'text/plain':                           # may be base64,qp,uu
                return type, self.decodedPayload(part, asStr)  # bytes to str too?

        # try to find an HTML part
        for part in message.walk():
            type = part.get_content_type()                     # caller renders html
            if type == 'text/html':
                return type, self.decodedPayload(part, asStr)

        # try any other text type, including XML
        for part in message.walk():
            if part.get_content_maintype() == 'text':
                return part.get_content_type(), self.decodedPayload(part, asStr)

        # punt: could use first part, but it's not marked as text
        failtext = '[No text to display]' if asStr else b'[No text to display]'
        return 'text/plain', failtext

    def decodeHeader(self, rawheader):
        """
        4E: decode existing i18n message header text per both email and Unicode 
        standards, according to its content; return as is if unencoded or fails;
        client must call this to display: parsed Message object does not decode;
        i18n header example: '=?UTF-8?Q?Introducing=20Top=20Values=20..Savers?=';
        i18n header example: 'Man where did you get that =?UTF-8?Q?assistant=3F?=';

        decode_header handles any line breaks in header string automatically, may
        return multiple parts if any substrings of hdr are encoded, and returns all
        bytes in parts list if any encodings found (with unencoded parts encoded as
        raw-unicode-escape and enc=None) but returns a single part with enc=None
        that is str instead of bytes in Py3.1 if the entire header is unencoded 
        (must handle mixed types here); see Chapter 13 for more details/examples;

        the following first attempt code was okay unless any encoded substrings, or
        enc was returned as None (raised except which returned rawheader unchanged):
        hdr, enc = email.header.decode_header(rawheader)[0]
        return hdr.decode(enc) # fails if enc=None: no encoding or encoded substrs
        """
        try:
            parts = email.header.decode_header(rawheader)
            decoded = []
            for (part, enc) in parts:                      # for all substrings
                if enc == None:                            # part unencoded?
                    if not isinstance(part, bytes):        # str: full hdr unencoded
                        decoded += [part]                  # else do unicode decode
                    else:
                        decoded += [part.decode('raw-unicode-escape')]
                else:
                    decoded += [part.decode(enc)]
            return ' '.join(decoded)
        except:
            return rawheader         # punt!

    def decodeAddrHeader(self, rawheader):
        """
        4E: decode existing i18n address header text per email and Unicode,
        according to its content; must parse out first part of email address
        to get i18n part: '"=?UTF-8?Q?Walmart?=" <*****@*****.**>';
        From will probably have just 1 addr, but To, Cc, Bcc may have many;

        decodeHeader handles nested encoded substrings within an entire hdr,
        but we can't simply call it for entire hdr here because it fails if 
        encoded name substring ends in " quote instead of whitespace or endstr; 
        see also encodeAddrHeader in mailSender module for the inverse of this;

        the following first attempt code failed to handle encoded substrings in
        name, and raised exc for unencoded bytes parts if any encoded substrings;
        namebytes, nameenc = email.header.decode_header(name)[0]  (do email+MIME)
        if nameenc: name = namebytes.decode(nameenc)              (do Unicode?)
        """
        try:
            pairs = email.utils.getaddresses([rawheader])  # split addrs and parts
            decoded = []                                   # handles name commas 
            for (name, addr) in pairs:
                try:
                    name = self.decodeHeader(name)                # email+MIME+Uni
                except:
                    name = None   # but uses encooded name if exc in decodeHeader
                joined = email.utils.formataddr((name, addr))     # join parts
                decoded.append(joined)
            return ', '.join(decoded)                             # >= 1 addrs 
        except:
            return self.decodeHeader(rawheader)    # try decoding entire string

    def splitAddresses(self, field):
        """
        4E: use comma separator for multiple addrs in the UI, and 
        getaddresses to split correctly and allow for comma in the 
        name parts of addresses; used by PyMailGUI to split To, Cc, 
        Bcc as needed for user inputs and copied headers;  returns 
        empty list if field is empty, or any exception occurs;
        """
        try:
            pairs = email.utils.getaddresses([field])                # [(name,addr)]
            return [email.utils.formataddr(pair) for pair in pairs]  # [name <addr>]
        except:
            return ''   # syntax error in user-entered field?, etc.

    # returned when parses fail
    errorMessage = Message()
    errorMessage.set_payload('[Unable to parse message - format error]')

    def parseHeaders(self, mailtext):
        """
        parse headers only, return root email.message.Message object
        stops after headers parsed, even if nothing else follows (top)
        email.message.Message object is a mapping for mail header fields
        payload of message object is None, not raw body text
        """
        try:
            return email.parser.Parser().parsestr(mailtext, headersonly=True)
        except:
            return self.errorMessage

    def parseMessage(self, fulltext):
        """
        parse entire message, return root email.message.Message object
        payload of message object is a string if not is_multipart()
        payload of message object is more Messages if multiple parts
        the call here same as calling email.message_from_string()
        """
        try:
            return email.parser.Parser().parsestr(fulltext)       # may fail!
        except:
            return self.errorMessage     # or let call handle? can check return

    def parseMessageRaw(self, fulltext):
        """
        parse headers only, return root email.message.Message object
        stops after headers parsed, for efficiency (not yet used here)
        payload of message object is raw text of mail after headers
        """
        try:
            return email.parser.HeaderParser().parsestr(fulltext)
        except:
            return self.errorMessage
示例#23
0
        def test_readfile_operations(self):
            class ITest(Interface):
                title = schema.TextLine()
                body = schema.Text()

            alsoProvides(ITest['body'], IPrimaryField)

            fti_mock = DexterityFTI(u'testtype')
            fti_mock.lookupSchema = Mock(return_value=ITest)
            fti_mock.behaviors = [ITestBehavior.__identifier__]

            self.mock_utility(fti_mock, IDexterityFTI, name=u"testtype")

            item = Item('item')
            item.portal_type = 'testtype'

            readfile = DefaultReadFile(item)

            message = Message()
            message['title'] = 'Test title'
            message['foo'] = '10'
            message['bar'] = 'xyz'
            message.set_payload('<p>body</p>')

            from plone.rfc822 import constructMessageFromSchemata
            self.patch_global(constructMessageFromSchemata,
                              return_value=message)

            body = b"""\
title: Test title
foo: 10
bar: xyz
Portal-Type: testtype

<p>body</p>"""

            # iter
            # next

            self.assertEqual(body, readfile.read())
            self.assertEqual(69, readfile.size())
            self.assertEqual('utf-8', readfile.encoding)
            self.assertEqual(None, readfile.name)
            self.assertEqual('text/plain', readfile.mimeType)

            readfile.seek(2)
            self.assertEqual(2, readfile.tell())
            self.assertEqual(b'tl', readfile.read(2))
            self.assertEqual(4, readfile.tell())

            readfile.seek(0, 2)
            self.assertEqual(69, readfile.tell())

            readfile.seek(0)
            self.assertEqual(b'foo: 10\n', readfile.readlines()[1])

            readfile.seek(0)
            self.assertEqual(b'foo: 10\n', readfile.readlines(100)[1])

            readfile.seek(0)
            self.assertEqual(b'title: Test title\n', readfile.readline())

            readfile.seek(0)
            self.assertEqual(b'title: Test title\n', readfile.readline(100))

            readfile.seek(0)
            self.assertEqual(b'foo: 10\n', list(iter(readfile))[1])

            self.assertEqual(False, readfile.closed)
            readfile.close()
示例#24
0
def test_bytes_generator():
    """Test the email bytes generator class."""
    message = Message()
    message.set_type('application/pkcs7-mime')
    assert utils.mime_to_bytes(message) == b'MIME-Version: 1.0\r\n' \
                                           b'Content-Type: application/pkcs7-mime\r\n\r\n'
示例#25
0
    def createEmail(self,
                    msgdict,
                    builderName,
                    title,
                    results,
                    builds=None,
                    patches=None,
                    logs=None):
        text = msgdict['body'].encode(ENCODING)
        type = msgdict['type']
        if 'subject' in msgdict:
            subject = msgdict['subject'].encode(ENCODING)
        else:
            subject = self.subject % {
                'result': Results[results],
                'projectName': title,
                'title': title,
                'builder': builderName,
            }

        assert '\n' not in subject, \
            "Subject cannot contain newlines"

        assert type in ('plain', 'html'), \
            "'%s' message type must be 'plain' or 'html'." % type

        if patches or logs:
            m = MIMEMultipart()
            m.attach(MIMEText(text, type, ENCODING))
        else:
            m = Message()
            m.set_payload(text, ENCODING)
            m.set_type("text/%s" % type)

        m['Date'] = formatdate(localtime=True)
        m['Subject'] = subject
        m['From'] = self.fromaddr
        # m['To'] is added later

        if patches:
            for (i, patch) in enumerate(patches):
                a = self.patch_to_attachment(patch, i)
                m.attach(a)
        if logs:
            for log in logs:
                name = "%s.%s" % (log.getStep().getName(), log.getName())
                if (self._shouldAttachLog(log.getName())
                        or self._shouldAttachLog(name)):
                    # Use distinct filenames for the e-mail summary
                    if self.buildSetSummary:
                        filename = "%s.%s.%s" % (
                            log.getStep().getBuild().getBuilder().getName(),
                            log.getStep().getName(), log.getName())
                    else:
                        filename = name

                    text = log.getText()
                    if not isinstance(text, unicode):
                        # guess at the encoding, and use replacement symbols
                        # for anything that's not in that encoding
                        text = text.decode(LOG_ENCODING, 'replace')
                    a = MIMEText(text.encode(ENCODING), _charset=ENCODING)
                    a.add_header('Content-Disposition',
                                 "attachment",
                                 filename=filename)
                    m.attach(a)

        #@todo: is there a better way to do this?
        # Add any extra headers that were requested, doing WithProperties
        # interpolation if only one build was given
        if self.extraHeaders:
            if len(builds) == 1:
                d = builds[0].render(self.extraHeaders)
            else:
                d = defer.succeed(self.extraHeaders)

            @d.addCallback
            def addExtraHeaders(extraHeaders):
                for k, v in extraHeaders.items():
                    if k in m:
                        twlog.msg("Warning: Got header " + k +
                                  " in self.extraHeaders "
                                  "but it already exists in the Message - "
                                  "not adding it.")
                    m[k] = v

            d.addCallback(lambda _: m)
            return d

        return defer.succeed(m)
示例#26
0
文件: http.py 项目: marviniter/vlcp
 def parseform(self, limit = 67108864, tostr = True, safename = True):
     '''
     Parse form-data with multipart/form-data or application/x-www-form-urlencoded
     In Python3, the keys of form and files are unicode, but values are bytes
     If the key ends with '[]', it is considered to be a list:
     a=1&b=2&b=3          =>    {'a':1,'b':3}
     a[]=1&b[]=2&b[]=3    =>    {'a':[1],'b':[2,3]}
     :param limit: limit total input size, default to 64MB. None = no limit. Note that all the form
     data is stored in memory (including upload files), so it is dangerous to accept a very large input.
     :param tostr: convert values to str in Python3. Only apply to form, files data are always bytes
     :param safename: if True, extra security checks are performed on filenames to reduce known security risks.
     '''
     if tostr:
         def _str(s):
             try:
                 if not isinstance(s, str):
                     return s.decode(self.encoding)
                 else:
                     return s
             except:
                 raise HttpInputException('Invalid encoding in post data: ' + repr(s))
     else:
         def _str(s):
             return s
     try:
         form = {}
         files = {}
         # If there is not a content-type header, maybe there is not a content.
         if b'content-type' in self.headerdict and self.inputstream is not None:
             contenttype = self.headerdict[b'content-type']
             m = Message()
             # Email library expects string, which is unicode in Python 3
             try:
                 m.add_header('Content-Type', str(contenttype.decode('ascii')))
             except UnicodeDecodeError:
                 raise HttpInputException('Content-Type has non-ascii characters')
             if m.get_content_type() == 'multipart/form-data':
                 fp = BytesFeedParser()
                 fp.feed(b'Content-Type: ' + contenttype + b'\r\n\r\n')
                 total_length = 0
                 while True:
                     try:
                         for m in self.inputstream.prepareRead(self.container):
                             yield m
                         data = self.inputstream.readonce()
                         total_length += len(data)
                         if limit is not None and total_length > limit:
                             raise HttpInputException('Data is too large')
                         fp.feed(data)
                     except EOFError:
                         break
                 msg = fp.close()
                 if not msg.is_multipart() or msg.defects:
                     # Reject the data
                     raise HttpInputException('Not valid multipart/form-data format')
                 for part in msg.get_payload():
                     if part.is_multipart() or part.defects:
                         raise HttpInputException('Not valid multipart/form-data format')
                     disposition = part.get_params(header='content-disposition')
                     if not disposition:
                         raise HttpInputException('Not valid multipart/form-data format')
                     disposition = dict(disposition)
                     if 'form-data' not in disposition or 'name' not in disposition:
                         raise HttpInputException('Not valid multipart/form-data format')
                     if 'filename' in disposition:
                         name = disposition['name']
                         filename = disposition['filename']
                         if safename:
                             filename = _safename(filename)
                         if name.endswith('[]'):
                             files.setdefault(name[:-2], []).append({'filename': filename, 'content': part.get_payload(decode=True)})
                         else:
                             files[name] = {'filename': filename, 'content': part.get_payload(decode=True)}
                     else:
                         name = disposition['name']
                         if name.endswith('[]'):
                             form.setdefault(name[:-2], []).append(_str(part.get_payload(decode=True)))
                         else:
                             form[name] = _str(part.get_payload(decode=True))
             elif m.get_content_type() == 'application/x-www-form-urlencoded' or \
                     m.get_content_type() == 'application/x-url-encoded':
                 if limit is not None:
                     for m in self.inputstream.read(self.container, limit + 1):
                         yield m
                     data = self.container.data
                     if len(data) > limit:
                         raise HttpInputException('Data is too large')
                 else:
                     for m in self.inputstream.read(self.container):
                         yield m
                     data = self.container.data
                 result = parse_qs(data, True)
                 def convert(k,v):
                     try:
                         k = str(k.decode('ascii'))
                     except:
                         raise HttpInputException('Form-data key must be ASCII')
                     if not k.endswith('[]'):
                         v = _str(v[-1])
                     else:
                         k = k[:-2]
                         v = [_str(i) for i in v]
                     return (k,v)
                 form = dict(convert(k,v) for k,v in result.items())
             else:
                 # Other formats, treat like no data
                 pass
         self.form = form
         self.files = files                
     except Exception as exc:
         raise HttpInputException('Failed to parse form-data: ' + str(exc))
    def prepare_multipart_body(self, content_index=0):
        # type: (int) -> int
        """Will prepare the body of this request according to the multipart information.

        This call assumes the on_request policies have been applied already in their
        correct context (sync/async)

        Does nothing if "set_multipart_mixed" was never called.

        :param int content_index: The current index of parts within the batch message.
        :returns: The updated index after all parts in this request have been added.
        :rtype: int
        """
        if not self.multipart_mixed_info:
            return 0

        requests = self.multipart_mixed_info[0]  # type: List[HttpRequest]
        boundary = self.multipart_mixed_info[2]  # type: Optional[str]

        # Update the main request with the body
        main_message = Message()
        main_message.add_header("Content-Type", "multipart/mixed")
        if boundary:
            main_message.set_boundary(boundary)

        for req in requests:
            part_message = Message()
            if req.multipart_mixed_info:
                content_index = req.prepare_multipart_body(
                    content_index=content_index)
                part_message.add_header("Content-Type",
                                        req.headers['Content-Type'])
                payload = req.serialize()
                # We need to remove the ~HTTP/1.1 prefix along with the added content-length
                payload = payload[payload.index(b'--'):]
            else:
                part_message.add_header("Content-Type", "application/http")
                part_message.add_header("Content-Transfer-Encoding", "binary")
                part_message.add_header("Content-ID", str(content_index))
                payload = req.serialize()
                content_index += 1
            part_message.set_payload(payload)
            main_message.attach(part_message)

        try:
            from email.policy import HTTP

            full_message = main_message.as_bytes(policy=HTTP)
            eol = b"\r\n"
        except ImportError:  # Python 2.7
            # Right now we decide to not support Python 2.7 on serialization, since
            # it doesn't serialize a valid HTTP request (and our main scenario Storage refuses it)
            raise NotImplementedError(
                "Multipart request are not supported on Python 2.7")
            # full_message = main_message.as_string()
            # eol = b'\n'
        _, _, body = full_message.split(eol, 2)
        self.set_bytes_body(body)
        self.headers["Content-Type"] = ("multipart/mixed; boundary=" +
                                        main_message.get_boundary())
        return content_index
示例#28
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")
示例#29
0
    def createEmail(self, msgdict, builderName, title, results, builds=None,
                    patches=None, logs=None):
        text = msgdict['body']
        type = msgdict['type']
        if msgdict.get('subject') is not None:
            subject = msgdict['subject']
        else:
            subject = self.subject % {'result': Results[results],
                                      'projectName': title,
                                      'title': title,
                                      'builder': builderName}

        assert '\n' not in subject, \
            "Subject cannot contain newlines"

        assert type in ('plain', 'html'), \
            "'%s' message type must be 'plain' or 'html'." % type

        if patches or logs:
            m = MIMEMultipart()
            txt = MIMEText(text, type, ENCODING)
            m.attach(txt)
        else:
            m = Message()
            m.set_payload(text, ENCODING)
            m.set_type("text/%s" % type)

        m['Date'] = formatdate(localtime=True)
        m['Subject'] = subject
        m['From'] = self.fromaddr
        # m['To'] is added later

        if patches:
            for (i, patch) in enumerate(patches):
                a = self.patch_to_attachment(patch, i)
                m.attach(a)
        if logs:
            for log in logs:
                name = "%s.%s" % (log['stepname'],
                                  log['name'])
                if (self._shouldAttachLog(log['name']) or
                        self._shouldAttachLog(name)):
                    # Use distinct filenames for the e-mail summary
                    if self.buildSetSummary:
                        filename = "%s.%s" % (log['buildername'],
                                              name)
                    else:
                        filename = name

                    text = log['content']['content']
                    a = MIMEText(text.encode(ENCODING),
                                 _charset=ENCODING)
                    a.add_header('Content-Disposition', "attachment",
                                 filename=filename)
                    m.attach(a)

        # @todo: is there a better way to do this?
        # Add any extra headers that were requested, doing WithProperties
        # interpolation if only one build was given
        if self.extraHeaders:
            extraHeaders = self.extraHeaders
            if len(builds) == 1:
                props = Properties.fromDict(builds[0]['properties'])
                extraHeaders = yield props.render(extraHeaders)

            for k, v in iteritems(extraHeaders):
                if k in m:
                    twlog.msg("Warning: Got header " + k +
                              " in self.extraHeaders "
                              "but it already exists in the Message - "
                              "not adding it.")
                m[k] = v
        defer.returnValue(m)
示例#30
0
def blank():
    return Message()