def gpg_decrypt_all_payloads(message): # We don't want to modify the original message decrypted_message = copy.deepcopy(message) # Check if message is PGP/MIME encrypted if not (message.get_param('protocol') is None) and message.get_param( 'protocol' ) == 'application/pgp-encrypted' and message.is_multipart(): decrypted_message = decrypt_mime(decrypted_message) # At this point the message could only be PGP/INLINE encrypted, unencrypted or # encrypted with a mechanism not covered by GPG-Mailgate elif get_bool_from_cfg('default', 'no_inline_dec', 'no'): # Check if message is PGP/INLINE encrypted and has attachments (or unencrypted with attachments) if message.is_multipart(): # Set message's payload to list so payloads can be attached later on decrypted_message.set_payload(list()) # We only need to hand over the original message here. Not needed for other decrypt implementations. decrypted_message, success = decrypt_inline_with_attachments( message, False, decrypted_message) # Add header here to avoid it being appended several times if get_bool_from_cfg('default', 'add_header', 'yes') and success: decrypted_message[ 'X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate' # Check if message is PGP/INLINE encrypted without attachments (or unencrypted without attachments) else: decrypted_message = decrypt_inline_without_attachments( decrypted_message) return decrypted_message
def get_form_data(self, form=False): ct = self.headers.get('content-type', self.headers.get('CONTENT-TYPE', '')).lower() resp = {} # application/x-www-form-urlencoded if ct == 'application/x-www-form-urlencoded': body = yield from self.body.read() resp['form'] = urllib.parse.parse_qs(body.decode('latin1')) # multipart/form-data elif ct.startswith('multipart/form-data'): # pragma: no cover out = io.BytesIO() for key, val in self.headers.items(): out.write(bytes('{}: {}\r\n'.format(key, val), 'latin1')) out.write(b'\r\n') out.write(self.body) out.write(b'\r\n') out.seek(0) message = email.parser.BytesParser().parse(out) if message.is_multipart(): for msg in message.get_payload(): if msg.is_multipart(): logging.warn('multipart msg is not expected') else: key, params = cgi.parse_header( msg.get('content-disposition', '')) params['data'] = msg.get_payload() params['content-type'] = msg.get_content_type() resp['multipart-data'].append(params) return resp['form'] if form else resp
def gpg_decrypt_all_payloads( message ): # We don't want to modify the original message decrypted_message = copy.deepcopy(message) # Check if message is PGP/MIME encrypted if not (message.get_param('protocol') is None) and message.get_param('protocol') == 'application/pgp-encrypted' and message.is_multipart(): decrypted_message = decrypt_mime(decrypted_message) # At this point the message could only be PGP/INLINE encrypted, unencrypted or # encrypted with a mechanism not covered by GPG-Mailgate elif get_bool_from_cfg('default', 'no_inline_dec', 'no'): # Check if message is PGP/INLINE encrypted and has attachments (or unencrypted with attachments) if message.is_multipart(): # Set message's payload to list so payloads can be attached later on decrypted_message.set_payload(list()) # We only need to hand over the original message here. Not needed for other decrypt implementations. decrypted_message, success = decrypt_inline_with_attachments(message, False, decrypted_message) # Add header here to avoid it being appended several times if get_bool_from_cfg('default', 'add_header', 'yes') and success: decrypted_message['X-GPG-Mailgate'] = 'Decrypted by GPG Mailgate' # Check if message is PGP/INLINE encrypted without attachments (or unencrypted without attachments) else: decrypted_message = decrypt_inline_without_attachments(decrypted_message) return decrypted_message
def extractpayload(message, **kwargs): if message.is_multipart(): headerlen = kwargs.get('headerlen', 78) messagestr = mimetostring(message, headerlen) #.replace('\n','\r\n') boundary = '--' + message.get_boundary() temp = messagestr.split(boundary) temp.pop(0) return boundary + boundary.join(temp) else: return message.get_payload()
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 convert_nested_message(message, top_level = False): if top_level: # Leave only content-related headers for tree purposes, do not # dupluicate all the headers. Also, leave From and Subject for # display purposes node = {k : v for k, v in message.items() if k.startswith("Content-") or k == 'From' or k == 'Subject'} else: node = dict(message) if message.is_multipart(): node['contents'] = map(convert_nested_message, message.get_payload()) else: node['contents'] = cas.add(db, message.get_payload(decode=True)) return node
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 __headerline(self, message, number): """Format message header info into a single text line""" fields = [None] * 5 fields[0] = str(number + 1).rjust(2) #fields[0] = str(number + 1) if message.is_multipart(): fields[1] = 'a' else: fields[1] = ' ' fields[2] = self.__pretty_date(utils.get_header_param(message, 'date')) fields[3] = self.__pretty_from(utils.get_header_param(message, 'from')) fields[4] = utils.get_header_param(message, 'subject').strip() return ' '.join(fields)
def test_customize_message(self): mailing = factories.MailingFactory() 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)) # print file(fullpath, 'rt').read() parser = email.parser.Parser() message = parser.parse(file(fullpath, 'rt'), headersonly = False) assert(isinstance(message, email.message.Message)) self.assertFalse(message.is_multipart()) self.assertTrue('Date' in message) self.assertEquals('This is a very simple mailing.', message.get_payload())
def test_customize_simple_message_with_recipient_attachment(self): recipient = factories.RecipientFactory( 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', }, ] } ) #factories.MailingContentFactory(mailing=recipient.mailing) #print recipient.mailing.content 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()) # print # print message.as_string() self.assertEquals(message.get_payload(i=0).get_payload(), 'This is a very simple mailing.') self.assertEquals(message.get_payload(i=1).get_payload(), 'col1;col2;col3\nval1;val2;val3\n')
def get_msg( message ): if not message.is_multipart(): return message.get_payload() return '\n\n'.join( [str(m) for m in message.get_payload()] )
def get_msg(message): if not message.is_multipart(): return message.get_payload() return '\n\n'.join([str(m) for m in message.get_payload()])
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 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')
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')
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