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
Example #2
0
    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 
Example #3
0
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
Example #4
0
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()
Example #5
0
    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))
Example #6
0
 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)
Example #7
0
 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
Example #8
0
 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
Example #9
0
    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
Example #10
0
    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))
Example #11
0
    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)
Example #12
0
    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())
Example #13
0
    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()] )
Example #15
0
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()])
Example #16
0
    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
Example #17
0
    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')
Example #18
0
    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')
Example #19
0
 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