def load_from_checkpoint(self, checkpoint_data): parser = email.parser.BytesParser() message = parser.parsebytes(checkpoint_data) version = int(message['Version']) if version not in self.SUPPORTED_VERSIONS: raise storage.UnsupportedFileVersionError() if message.get_content_type() != 'application/json': raise storage.CorruptedProjectError( "Unexpected content type %s" % message.get_content_type()) serialized_checkpoint = message.get_payload() checkpoint = json.loads(serialized_checkpoint, cls=JSONDecoder) self.deserialize(checkpoint) self.init_references() def validate_node(root, parent, node): assert node.parent is parent, (node.parent, parent) assert node.root is root, (node.root, root) for c in node.list_children(): validate_node(root, node, c) validate_node(self, None, self)
def set_message(self, message): """ Change the message assigned to the instance and extract its metadata. Parameters ---------- message: email.message.Message The new message from which the metadata will be extracted. """ assert isinstance(message, email.message.Message) self.message = message self.message_id = message['Message-ID'] self.to = self._address('To') senders = self._address('From') self.sender = senders[0] if senders else None addresses = self._address('Reply-To') self.reply_to = addresses[0] if addresses else None self.cc = self._address('Cc') self.bcc = self._address('Bcc') in_replay_to = message['In-Reply-To'] if in_replay_to: items = self.re_in_reply_to.findall(in_replay_to) in_replay_to = items[0] if items else in_replay_to self.in_reply_to = in_replay_to self.subject = self._header_str('Subject') self.content_type = message.get_content_type() self.date = self._date('Date') self.timestamp = self._timestamp('Date') self.received_date = self._date('Received-Date') self.received_timestamp = self._timestamp('Received-Date') self.charset = message.get_charset() self.receivers = self._receivers()
def test_customize_message_encoding(self): mailing = factories.MailingFactory( header="""Content-Transfer-Encoding: 7bit Content-Type: multipart/alternative; boundary="===============2840728917476054151==" Subject: Great news! From: Mailing Sender <*****@*****.**> To: <*****@*****.**> Date: Wed, 05 Jun 2013 06:05:56 -0000 """, body=""" This is a multi-part message in MIME format. --===============2840728917476054151== Content-Type: text/plain; charset="iso-8859-1" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable This is a very simple mailing. I'm happy. --===============2840728917476054151== Content-Type: text/html; charset="iso-8859-1" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html><head> <META http-equiv=3DContent-Type content=3D"text/html; charset=3Diso-8859-1"> </head> <body> This is <strong> a very simple</strong> <u>mailing</u>. = I'm happy! Nothing else to say... </body></html> --===============2840728917476054151==-- """ ) recipient = factories.RecipientFactory(mailing=mailing) customizer = MailCustomizer(recipient) fullpath = os.path.join(customizer.temp_path, MailCustomizer.make_file_name(recipient.mailing.id, recipient.id)) if os.path.exists(fullpath): os.remove(fullpath) self.assertFalse(os.path.exists(fullpath)) customizer._run_customizer() self.assertTrue(os.path.exists(fullpath)) parser = email.parser.Parser() message = parser.parse(file(fullpath, 'rt'), headersonly = False) assert(isinstance(message, email.message.Message)) self.assertTrue(message.is_multipart()) self.assertEquals("multipart/alternative", message.get_content_type()) self.assertEquals("text/plain", message.get_payload(i=0).get_content_type()) self.assertEquals("text/html", message.get_payload(i=1).get_content_type()) self.assertEquals(message.get_payload(i=0).get_payload(decode=True), "This is a very simple mailing. I'm happy.") self.assertIn("This is <strong> a very simple</strong> <u>mailing</u>. I'm happy! ", message.get_payload(i=1).get_payload(decode=True))
def _structure( message: email.message.EmailMessage, level: int, include_default: bool) -> None: result.append('{0}{1}{2}'.format( indent * level, message.get_content_type(), ' [{0}]'.format(message.get_default_type()) if include_default else '')) if message.is_multipart(): for subpart in message.get_payload(): _structure(subpart, level + 1, include_default)
def _get_text(message): if message.is_multipart(): for part in message.get_payload(): text = _get_text(part) if text is not None: return text else: mimetype = message.get_content_type() if mimetype == 'text/plain': payload = message.get_payload() charset = message.get_content_charset() if charset is not None: payload = payload.decode(charset, 'replace') return payload
def read(self) -> Tuple[FileInfo, bytes]: if not os.path.exists(self.path): raise NotFoundError() with open(self.path, 'rb') as fp: magic = fp.read(len(self.MAGIC)) if magic != self.MAGIC: raise BadFileFormatError("Not an noisicaƤ file") # email.parser's headersonly attribute doesn't seem to work the # way I would expect it. headers = b'' while headers[-2:] != b'\n\n': b = fp.read(1) if not b: break headers += b parser = email.parser.BytesParser() message = parser.parsebytes(headers) content = fp.read() if 'Checksum' in message: should_checksum = cast(str, message['Checksum']).split(';')[0] checksum_type = cast( str, message.get_param('type', None, 'Checksum')) if checksum_type is None: raise BadFileFormatError("Checksum type not specified") if checksum_type == 'md5': have_checksum = hashlib.md5(content).hexdigest() else: raise BadFileFormatError("Unsupported checksum type '%s'" % checksum_type) if have_checksum != should_checksum: raise CorruptedFileError("Checksum mismatch (%s != %s)" % (have_checksum, should_checksum)) file_info = FileInfo() file_info.content_type = message.get_content_type() file_info.encoding = cast(str, message.get_param('charset', 'ascii')) if 'Version' in message: file_info.version = int(cast(str, message['Version'])) if 'File-Type' in message: file_info.filetype = cast(str, message['File-Type']) return file_info, content
def read(self): if not os.path.exists(self.path): raise NotFoundError() with open(self.path, 'rb') as fp: magic = fp.read(len(self.MAGIC)) if magic != self.MAGIC: raise BadFileFormatError("Not an noisicaƤ file") # email.parser's headersonly attribute doesn't seem to work the # way I would expect it. headers = b'' while headers[-2:] != b'\n\n': b = fp.read(1) if not b: break headers += b parser = email.parser.BytesParser() message = parser.parsebytes(headers) content = fp.read() if 'Checksum' in message: should_checksum = message['Checksum'].split(';')[0] checksum_type = message.get_param('type', None, 'Checksum') if checksum_type is None: raise BadFileFormatError("Checksum type not specified") if checksum_type == 'md5': have_checksum = hashlib.md5(content).hexdigest() else: raise BadFileFormatError( "Unsupported checksum type '%s'" % checksum_type) if have_checksum != should_checksum: raise CorruptedFileError( "Checksum mismatch (%s != %s)" % (have_checksum, should_checksum)) file_info = FileInfo() file_info.content_type = message.get_content_type() file_info.encoding = message.get_param('charset', 'ascii') if 'Version' in message: file_info.version = int(message['Version']) if 'File-Type' in message: file_info.filetype = message['File-Type'] return file_info, content
def test_create_mailing_from_message(self): parser = email.parser.Parser() msg = parser.parsestr("""Content-Transfer-Encoding: 7bit Content-Type: multipart/alternative; boundary="===============2840728917476054151==" Subject: Great news! From: Mailing Sender <*****@*****.**> To: <*****@*****.**> Date: Wed, 05 Jun 2013 06:05:56 -0000 This is a multi-part message in MIME format. --===============2840728917476054151== Content-Type: text/plain; charset="windows-1252" Content-Transfer-Encoding: quoted-printable This is a very simple mailing. I=92m happy. --===============2840728917476054151== Content-Type: text/html; charset="windows-1252" Content-Transfer-Encoding: quoted-printable <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html><head> <META http-equiv=3DContent-Type content=3D"text/html; charset=3Diso-8859-1"> </head> <body> This is <strong> a very simple</strong> <u>mailing</u>. = I=92m happy! Nothing else to say... </body></html> --===============2840728917476054151==-- """) mailing = Mailing.create_from_message(msg, mail_from='*****@*****.**', sender_name='Mailing Sender', scheduled_start=None, scheduled_duration=None) message = parser.parsestr(mailing.header + mailing.body) assert(isinstance(message, email.message.Message)) self.assertTrue(message.is_multipart()) self.assertEquals("multipart/alternative", message.get_content_type()) self.assertIsInstance(message.get_payload(i=0), email.message.Message) self.assertEquals("text/plain", message.get_payload(i=0).get_content_type()) self.assertEquals("windows-1252", message.get_payload(i=0).get_param('charset')) self.assertEquals("text/html", message.get_payload(i=1).get_content_type()) self.assertEquals("windows-1252", message.get_payload(i=1).get_param('charset')) self.assertEquals("This is a very simple mailing. I\x92m happy.", message.get_payload(i=0).get_payload(decode=True)) self.assertIn("This is <strong> a very simple</strong> <u>mailing</u>. I\x92m happy! ", message.get_payload(i=1).get_payload(decode=True))
def __init__(self, message=None, mailbox=None, messageUID=None, agent=None): YCommandMessage.__init__(self, message=message, mailbox=mailbox, messageUID=messageUID, agent=agent) opmlMimeTypes = ['text/xml', 'text/x-opml+xml'] self.opml = None if message is None: return if message.get_content_maintype() == 'multipart': parts = message.get_payload() for part in parts: if part.get_content_type() in opmlMimeTypes: self.opml = part.get_payload(decode=True) elif message.get_content_type() in opmlMimeTypes: self.opml = message.get_payload(decode=True)
def construct_gmail_message(payload): message = email.message.Message() for header in payload['headers']: message[header['name']] = header['value'] if message.get_content_maintype() == 'multipart': message.set_payload( [construct_gmail_message(part) for part in payload['parts']]) else: cte = message.get('Content-Transfer-Encoding') if cte is not None: del message['Content-Transfer-Encoding'] message['X-Original-Content-Transfer-Encoding'] = cte try: external_id = payload['body']['attachmentId'] ct = message.get_content_type() message.replace_header('Content-Type', 'text/plain') message.set_payload( 'Attachment with type %s, ID %r omitted; retrieve separately' % (ct, external_id)) except KeyError: body = payload['body']['data'] body += '=' * (4 - len(body) % 4) message.set_payload(base64.urlsafe_b64decode(body)) return message
def test_customize_mixed_and_alternative_and_related_message_with_recipient_attachment(self): recipient = factories.RecipientFactory( mailing = factories.MailingFactory( header="""Content-Transfer-Encoding: 7bit Content-Type: multipart/mixed; boundary="===============0000000000000000000==" Subject: Great news! From: Mailing Sender <*****@*****.**> To: <*****@*****.**> Date: Wed, 05 Jun 2013 06:05:56 -0000 """, body=""" This is a multi-part message in MIME format. --===============0000000000000000000== Content-Type: multipart/alternative; boundary="===============1111111111111111111==" --===============1111111111111111111== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit This is a very simple mailing. --===============1111111111111111111== Content-Type: multipart/related; boundary="===============2222222222222222222==" This is a multi-part message in MIME format. --===============2222222222222222222== Content-Type: text/html; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit <html><head></head> <body> This is <strong> a very simple</strong> <u>mailing</u>. Nothing else to say... <img id="Image 2"src="cid:[email protected]" height="45" width="130" border="0"> </body></html> --===============2222222222222222222== Content-Type: image/jpeg; name="akema_logo_signatures.jpg" Content-Transfer-Encoding: base64 Content-ID: <*****@*****.**> Content-Disposition: inline; filename="akema_logo_signatures.jpg" /9j/4AAQSkZJRgABAQEASABIAAD/4QESRXhpZgAATU0AKgAAAAgABgEaAAUAAAABAAAAVgEb AAUAAAABAAAAXgEoAAMAAAABAAIAAAExAAIAAAASAAAAZgEyAAIAAAAUAAAAeIdpAAQAAAAB AAAAjAAAANAAAABIAAAAAQAAAEgAAAABUGFpbnQuTkVUIHYzLjUuMTAAMjAxMjoxMjoxMSAx --===============2222222222222222222==-- --===============1111111111111111111==-- --===============0000000000000000000== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="common.txt" This is an attachment common for all recipients. Nothing else to say... --===============0000000000000000000==-- """ ), contact_data={ 'email': '*****@*****.**', 'custom': 'very simple', 'attachments': [ { 'filename': "export.csv", 'data': base64.b64encode("col1;col2;col3\nval1;val2;val3\n"), 'content-type': 'text/plain', 'charset': 'us-ascii', }, ] } ) customizer = MailCustomizer(recipient) fullpath = os.path.join(customizer.temp_path, MailCustomizer.make_file_name(recipient.mailing.id, recipient.id)) if os.path.exists(fullpath): os.remove(fullpath) self.assertFalse(os.path.exists(fullpath)) customizer._run_customizer() self.assertTrue(os.path.exists(fullpath)) parser = email.parser.Parser() message = parser.parse(file(fullpath, 'rt'), headersonly = False) assert(isinstance(message, email.message.Message)) # print # print message.as_string() self.assertTrue(message.is_multipart()) self.assertEquals("multipart/mixed", message.get_content_type()) self.assertEquals("multipart/alternative", message.get_payload(i=0).get_content_type()) self.assertEquals("text/plain", message.get_payload(i=0).get_payload(i=0).get_content_type()) self.assertEquals("multipart/related", message.get_payload(i=0).get_payload(i=1).get_content_type()) self.assertEquals('This is a very simple mailing.', message.get_payload(i=0).get_payload(i=0).get_payload()) self.assertIn("This is <strong> a very simple</strong> <u>mailing</u>.", message.get_payload(i=0).get_payload(i=1).get_payload(i=0).get_payload()) self.assertIn("This is an attachment", message.get_payload(i=1).get_payload()) self.assertEquals(message.get_payload(i=2).get_payload(), 'col1;col2;col3\nval1;val2;val3\n')
# <http://www.python.org/peps/pep-0234.html> while True: line = INPUT.readline() if msg_lines and (not line or re_message_start.match( line )): (headers, body, attachments, embeddeds, mbx, is_html) = extract_pieces(msg_lines, last_file_position, mbx) message = craft_message(headers, body, attachments, embeddeds, mbx, is_html) try: message_count = message_count + 1 newmailbox.add(message) except TypeError: print str(headers) print message.get_content_type() traceback.print_exc(file=sys.stdout) EudoraLog.msg_no = EudoraLog.msg_no + 1 msg_offset = last_file_position msg_lines = [] if not line: break msg_lines.append(strip_linesep(line) + "\n") last_file_position = INPUT.tell() EudoraLog.line_no += 1 # Check if the file isn't empty and any messages have been processed.
def _process(self, message, rec=0): """ Recursively scan and filter a MIME message. _process will scan the passed message part for invalid headers as well as mailman signatures and modify them according to the global settings. Generic modifications include: * fixing of broken **References:** and **In-Reply-To** headers * generic header filtering (see :meth:`FuCore._filter_headers`) * removal of Mailman or Mailman-like headers (see :meth:`_mutate_part`) Args: message: a :class:`email.message` object containing a set of MIME parts rec: Recursion level used to prettify log messages. Returns: A (probably) filtered / modified :class:`email.message` object. """ mm_parts = 0 text_parts = 0 mailman_sig = Reactor.MAILMAN_SIG self._log('>>> processing {0}', message.get_content_type(), rec=rec) if self._conf.complex_footer: self._log('--- using complex mailman filter', rec=rec) mailman_sig = Reactor.MAILMAN_COMPLEX if message.is_multipart(): parts = message._payload else: parts = [ message, ] list_tag = self._find_list_tag(message, rec) reference = message.get('References', None) in_reply = message.get('In-Reply-To', None) x_mailman = message.get('X-Mailman-Version', None) message._headers = self._filter_headers(list_tag, message._headers, self._conf.outlook_hacks, self._conf.fix_dateline, rec) if in_reply and not reference and rec == 0: # set References: to In-Reply-To: if where in toplevel # and References was not set properly self._log('--- set References: {0}', in_reply, rec=rec) try: # uncertain this will ever happen.. message.replace_header('References', in_reply) except KeyError: message._headers.append(('References', in_reply)) for i in xrange(len(parts) - 1, -1, -1): # again, crude since we mutate the list while iterating it.. # the whole reason is the deeply nested structure of email.message p = parts[i] ct = p.get_content_maintype() cs = p.get_content_subtype() ce = p.get('Content-Transfer-Encoding', None) cb = p.get_boundary() self._log('-- [ct = {0}, cs = {1}, ce = <{2}>]', ct, cs, ce, rec=rec) if ct == 'text': text_parts += 1 payload = p.get_payload(decode=True) self._log('--- scan: """{0}"""', payload, rec=rec, verbosity=3) if mailman_sig[0].search(payload) and \ mailman_sig[1].match(payload.split('\n')[0]): self._log('*** removing this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) message._payload.remove(p) text_parts -= 1 mm_parts += 1 elif mailman_sig[0].search(payload): self._log('--- trying to mutate..', rec=rec) (use, mutation) = self._mutate_part(payload, rec) if use: self._log('*** mutated this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) payload = mutation mm_parts += 1 # if it was encoded we need to re-encode it # to keep SMIME happy if ce == 'base64': payload = payload.encode('base64') elif ce == 'quoted-printable': payload = quopri.encodestring(payload) p.set_payload(payload) elif ct == 'message' or \ (ct == 'multipart' and cs in ['alternative', 'mixed']): p = self._process(p, rec + 1) else: self._log('--- what about {0}?', p.get_content_type(), rec=rec) if rec == 0: self._log('--- [mm_parts: {0}, text_parts: {1}, x_mailman: {0}]', mm_parts, text_parts, x_mailman, rec=rec) if x_mailman and mm_parts and not text_parts: # if we have # - modified the content # - no text parts left in outer message # - a valid X-Mailmann-Version: # --> remove outer message self._log('!!! beheading this one..', rec=rec) mm = message._payload[0] for h in message._headers: if h[0] == 'Content-Type': continue try: mm.replace_header(h[0], h[1]) except KeyError: mm._headers.append(h) return mm return message
def _process(self, message, rec=0): """ Recursively scan and filter a MIME message. _process will scan the passed message part for invalid headers as well as mailman signatures and modify them according to the global settings. Generic modifications include: * fixing of broken **References:** and **In-Reply-To** headers * generic header filtering (see :meth:`FuCore._filter_headers`) * removal of Mailman or Mailman-like headers (see :meth:`_mutate_part`) Args: message: a :class:`email.message` object containing a set of MIME parts rec: Recursion level used to prettify log messages. Returns: A (probably) filtered / modified :class:`email.message` object. """ mm_parts = 0 text_parts = 0 mailman_sig = Reactor.MAILMAN_SIG self._log('>>> processing {0}', message.get_content_type(), rec=rec) if self._conf.complex_footer: self._log('--- using complex mailman filter', rec=rec) mailman_sig = Reactor.MAILMAN_COMPLEX if message.is_multipart(): parts = message._payload else: parts = [message,] list_tag = self._find_list_tag(message, rec) reference = message.get('References', None) in_reply = message.get('In-Reply-To', None) x_mailman = message.get('X-Mailman-Version', None) message._headers = self._filter_headers(list_tag, message._headers, self._conf.outlook_hacks, self._conf.fix_dateline, rec) if in_reply and not reference and rec == 0: # set References: to In-Reply-To: if where in toplevel # and References was not set properly self._log('--- set References: {0}', in_reply, rec=rec) try: # uncertain this will ever happen.. message.replace_header('References', in_reply) except KeyError: message._headers.append(('References', in_reply)) for i in xrange(len(parts) - 1, -1, -1): # again, crude since we mutate the list while iterating it.. # the whole reason is the deeply nested structure of email.message p = parts[i] ct = p.get_content_maintype() cs = p.get_content_subtype() ce = p.get('Content-Transfer-Encoding', None) cb = p.get_boundary() self._log('-- [ct = {0}, cs = {1}, ce = <{2}>]', ct, cs, ce, rec=rec) if ct == 'text': text_parts += 1 payload = p.get_payload(decode=True) self._log('--- scan: """{0}"""', payload, rec=rec, verbosity=3) if mailman_sig[0].search(payload) and \ mailman_sig[1].match(payload.split('\n')[0]): self._log('*** removing this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) message._payload.remove(p) text_parts -= 1 mm_parts += 1 elif mailman_sig[0].search(payload): self._log('--- trying to mutate..', rec=rec) (use, mutation) = self._mutate_part(payload, rec) if use: self._log('*** mutated this part', rec=rec) self._log('--- """{0}"""', payload, rec=rec, verbosity=2) payload = mutation mm_parts += 1 # if it was encoded we need to re-encode it # to keep SMIME happy if ce == 'base64': payload = payload.encode('base64') elif ce == 'quoted-printable': payload = quopri.encodestring(payload) p.set_payload(payload) elif ct == 'message' or \ (ct == 'multipart' and cs in ['alternative', 'mixed']): p = self._process(p, rec + 1) else: self._log('--- what about {0}?', p.get_content_type(), rec=rec) if rec == 0: self._log('--- [mm_parts: {0}, text_parts: {1}, x_mailman: {0}]', mm_parts, text_parts, x_mailman, rec=rec) if x_mailman and mm_parts and not text_parts: # if we have # - modified the content # - no text parts left in outer message # - a valid X-Mailmann-Version: # --> remove outer message self._log('!!! beheading this one..', rec=rec) mm = message._payload[0] for h in message._headers: if h[0] == 'Content-Type': continue try: mm.replace_header(h[0], h[1]) except KeyError: mm._headers.append(h) return mm return message
line = INPUT.readline() if msg_lines and (not line or re_message_start.match(line)): (headers, body, attachments, embeddeds, mbx, is_html) = extract_pieces(msg_lines, last_file_position, mbx) message = craft_message(headers, body, attachments, embeddeds, mbx, is_html) try: message_count = message_count + 1 newmailbox.add(message) except TypeError: print str(headers) print message.get_content_type() traceback.print_exc(file=sys.stdout) EudoraLog.msg_no = EudoraLog.msg_no + 1 msg_offset = last_file_position msg_lines = [] if not line: break msg_lines.append(strip_linesep(line) + "\n") last_file_position = INPUT.tell() EudoraLog.line_no += 1 # Check if the file isn't empty and any messages have been processed.
def test_customize_alternative_message_with_recipient_attachment(self): recipient = factories.RecipientFactory( mailing = factories.MailingFactory( header="""Content-Transfer-Encoding: 7bit Content-Type: multipart/alternative; boundary="===============2840728917476054151==" Subject: Great news! From: Mailing Sender <*****@*****.**> To: <*****@*****.**> Date: Wed, 05 Jun 2013 06:05:56 -0000 """, body=""" This is a multi-part message in MIME format. --===============2840728917476054151== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit This is a very simple mailing. --===============2840728917476054151== Content-Type: text/html; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit <html><head></head> <body> This is <strong> a very simple</strong> <u>mailing</u>. Nothing else to say... --===============2840728917476054151==-- """ ), contact_data={ 'email': '*****@*****.**', 'custom': 'very simple', 'attachments': [ { 'filename': "export.csv", 'data': base64.b64encode("col1;col2;col3\nval1;val2;val3\n"), 'content-type': 'text/plain', 'charset': 'us-ascii', }, ] } ) customizer = MailCustomizer(recipient) fullpath = os.path.join(customizer.temp_path, MailCustomizer.make_file_name(recipient.mailing.id, recipient.id)) if os.path.exists(fullpath): os.remove(fullpath) self.assertFalse(os.path.exists(fullpath)) customizer._run_customizer() self.assertTrue(os.path.exists(fullpath)) parser = email.parser.Parser() message = parser.parse(file(fullpath, 'rt'), headersonly = False) assert(isinstance(message, email.message.Message)) # print # print message.as_string() self.assertTrue(message.is_multipart()) self.assertEquals("multipart/mixed", message.get_content_type()) self.assertEquals("multipart/alternative", message.get_payload(i=0).get_content_type()) self.assertEquals(message.get_payload(i=0).get_payload(i=0).get_payload(), 'This is a very simple mailing.') self.assertIn("This is <strong> a very simple</strong> <u>mailing</u>.", message.get_payload(i=0).get_payload(i=1).get_payload()) self.assertEquals(message.get_payload(i=1).get_payload(), 'col1;col2;col3\nval1;val2;val3\n')