7
\$\begingroup\$

NOTE: This is for Python 2.7 currently, NOT Python 3. There's some type of black magic at work for Content Disposition that doesn't properly work on Py3, so I have to fix that first before I have a Py3 version of this...

This is an excerpt of another program, but can work as a standalone tool. This tool works to take any inbound email message passed into it (in this case, stored in a file as a string) and output a DMARC-compliant message. DMARC operates on verifying SPF and DKIM signatures, so any received message has to be rebroadcast in a way that completely strips out any other authentication or signatures and basically use a 'brand new message' that originates from the mail server / listserv.

This basically means that, for any given email list system, we should accept an email message, retain all parts of its payload (text components and attachments), but retransmit the message as the ListServ with the "From" address being the listserv, while retaining the original sender's name in the descriptive "From" field and putting the original "From" address into the "Reply-To" field.

Now, this works. When put into a Python script, you can execute it with python dmarc_convert_message.py PATHTOMESSAGEFILE where PATHTOMESSAGEFILE is the path to an email message's source. It will then read the data in and process it.

There's two components - the script, and a test message to use. The third is the converted email message output to a file.

I've constructed a test message you can use to test things here, and a valid 'output' (note that your output may differ because the MIMEMultipart boundary IDs change with each message being generated, but the rest of the message content should be nearly identical). All these are in a Gist on GitHub but otherwise are here below.

Every suggestion for improvement is valid, however I use a lot of repeated code, so any way to reduce the amount of repeated code in here is going to be given a priority.

I would prefer not to be given recommendations on PEP8 or style rules, because for this, I don't care much about them. I care about improving the code within reason, but not redoing everything for style.


dmarc_convert_message.py:

# coding=utf-8
import sys
import email
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart


# noinspection PyTypeChecker
def disposition_dict(dispstr):
    # type: (str) -> dict
    """
    Converts a string which contains a Content-Disposition
    header's contents into a dict.
    :param dispstr: String with Content-Disposition value
    :return: Dictionary of Content-Disposition values.
    """
    return dict(
        [map(str.strip, pt.split("=", 1))
         if len(map(str.lower, pt.split("=", 1))) > 1 else [str(pt.strip().lower()), None]
         for pt in dispstr.split(";")]
    )


# Open the file path specified as the first argument to the script at runtime, which
# contains the full path to the raw source message.
msg = email.message_from_string(open(sys.argv[1], mode="r").read())

multipart = msg.is_multipart()
# Construct the msg_components dictionary to hold the components which will go into
# the new email message.  This is built from the original message.
msg_components = {'To': msg['To'], 'From': msg['From'], 'Subject': msg['Subject'],
                  'Message-ID': email.utils.make_msgid(), 'Date': msg['Date'], 'Payloads': []}


# Iterate through all parts of the email message.
for part in msg.walk():
    if not part.is_multipart():
        # If we hit a non-multipart piece, we need to do stuff with it.
        # First, extract MIMEType data.
        maintype, subtype = part.get_content_type().split('/', 1)
        # Next, get the Content-Disposition.
        cdisp = part.get("Content-Disposition")
        if cdisp and not part.is_multipart():
            # If we have a Content-Disposition and we're not in a multipart piece of the message...
            cdisp_d = disposition_dict(cdisp)  # ... create a dict for easier searching of the cdisp.
            if 'attachment' or 'inline' in cdisp_d:
                # And if we're an attachment of some sort...

                # Sanitize the filename.
                attachfname = cdisp_d['filename'].strip('"')
                attachfname = attachfname.replace('\r', '')
                attachfname = attachfname.replace('\n', '')

                # Construct the MIMEBase message part for this MIMEType...
                data = MIMEBase(maintype, subtype)
                # ... and sets the Content-Type parameters to whatever they were in the original part...
                for param in part.get_params():
                    if param not in data.get_params():
                        data.set_param(param[0], param[1])
                # ... and sets the Content-Transfer-Encoding from the original part...
                if 'Content-Transfer-Encoding' in part.keys():
                    data.add_header('Content-Transfer-Encoding', part['Content-Transfer-Encoding'])
                # ... then sets the Payload for the part...
                data.set_payload(part.get_payload())
                # ... and finally add it into the Payloads section of the components,
                # as a tuple so we can identify it's got a filename and is an attachment.
                msg_components['Payloads'].append((data, attachfname))
        else:
            # If we're not an attachment, we're probably something else.
            if part.is_multipart():
                # Skip if multipart part.
                continue

            if maintype == "text":
                # We might be text, either Plain or HTML, so we need to add that to the payloads
                # ... by constructing the text message part...
                data = MIMEText(part.get_payload(), _subtype=subtype)
                # ... and then adding it to the Payloads.
                msg_components['Payloads'].append(data)
            else:
                # Any other MIMEType isn't to be handled and we just skip over that part
                continue

if multipart:
    # The new message we send, however, is going to be a multipart message.
    newmsg = MIMEMultipart('alternative')  # Email compliance for Multipart: have it a multipart/alternative type.
    newmsg['Subject'] = msg_components['Subject']  # Set the subject to match.
    newmsg['To'] = msg_components['To']  # Set the "To" field to be the original "To" address
    if len(msg['From'].split('<')) > 1:  # Determine if 'From' is formatted a specific way.
        # If it has 'Thomas Ward <teward@foo.bar>' for example, we need to split out the name for the restructuring.
        newmsg.add_header('From', "'{}' via '{}' <{}>".format(msg['From'].split('<')[0].strip(),
                                                              'Example List', 'list@example.com'))
    else:
        # Otherwise, we just use the email address.
        newmsg.add_header('From', "'{}' via '{}' <{}>".format(msg['From'], 'Example List', 'list@example.com'))
    # Original 'From' address is now the Reply-To.
    newmsg['Reply-To'] = msg_components['From']
    # Some lists add these next two headers, only add them if present in original message.
    if 'List-Id' in msg.keys():
        newmsg['List-Id'] = msg['List-Id']
    if 'List-Post' in msg.keys():
        newmsg['List-Post'] = 'nowhere@dark-net.localtesting'
    # We created a new MessageID earlier, so we'll use that.
    newmsg['Message-ID'] = msg_components['Message-ID']
    # We are using the original date from the first message.
    newmsg['Date'] = msg_components['Date']
    # Iterate over all payload parts and attach them to the new multipart message.
    for payload in msg_components['Payloads']:
        if type(payload) is not tuple:
            del payload['MIME-Version']
            # Non-tuple payload types are standard MIME parts, just add them.
            newmsg.attach(payload)
        else:
            # Tuple payload values are attachments, so handle them as such...
            # ... by first adding the header with the filename...
            if payload[0].get_content_type().split('/', 1)[0] == "text":
                payload[0].set_param('name', payload[1])
            payload[0].add_header('Content-Disposition', 'attachment', filename=payload[1])
            del payload[0]['MIME-Version']
            # ... then adding the attachment to the message.
            newmsg.attach(payload[0])
else:
    # If we're not making a multipart message, then only build a single-part message to send.
    if len(msg_components['Payloads']) != 1:
        # However, if we aren't a multipart message and try to include more than one payload,
        # that's illegal and something is horribly wrong. So error.
        raise TypeError("You cannot have a non-multipart message with multiple payloads.")

    mimepart = msg_components['Payloads'][0]
    maintype, subtype = mimepart.get_content_type().split('/', 1)
    newmsg = MIMEBase(maintype, subtype)
    newmsg['Subject'] = msg_components['Subject']  # Set the subject to match.
    newmsg['To'] = msg_components['To']  # Set the "To" field to be the original "To" address
    if len(msg['From'].split('<')) > 1:  # Determine if 'From' is formatted a specific way.
        # If it has 'Thomas Ward <teward@foo.bar>' for example, we need to split out the name for the restructuring.
        newmsg.add_header('From', "'{}' via '{}' <{}>".format(msg['From'].split('<')[0].strip(),
                                                              'Example List', 'list@example.com'))
    else:
        # Otherwise, we just use the email address.
        newmsg.add_header('From', "'{}' via '{}' <{}>".format(msg['From'], 'Example List', 'list@example.com'))
    # Original 'From' address is now the Reply-To.
    newmsg['Reply-To'] = msg_components['From']
    # Some lists add these next two headers, only add them if present in original message.
    if 'List-Id' in msg.keys():
        newmsg['List-Id'] = msg['List-Id']
    if 'List-Post' in msg.keys():
        newmsg['List-Post'] = msg['List-Post']
    # We created a new MessageID earlier, so we'll use that.
    newmsg['Message-ID'] = msg_components['Message-ID']
    # We are using the original date from the first message.
    newmsg['Date'] = msg_components['Date']
    # Now we set the payload.
    newmsg.set_payload(mimepart.get_payload(), charset=mimepart.get_charset())

# Write the newly constructed message to a file, so we can process it later as part of an email sender.
open('converted_message', mode='w').write(newmsg.as_string())

testmsg:

Return-Path: <teward@localtest>
X-Original-To: teward@localtest
Delivered-To: teward@localtest
Received: from localhost (localhost [127.0.0.1])
    by mail.localtest (Postfix) with ESMTP id 58318104458
    for <teward@localtest>; Sat, 11 Nov 2017 12:07:17 -0500 (EST)
X-Spam-Flag: NO
X-Spam-Score: 0.003
X-Spam-Level:
X-Spam-Status: No, score=0.003 tagged_above=-999 required=6.31
    tests=[ALL_TRUSTED=-1, DKIM_SIGNED=0.1, DKIM_VALID=-0.1,
    DKIM_VALID_AU=-0.1, HTML_MESSAGE=0.001, TRACKER_ID=1.102]
    autolearn=no autolearn_force=no
Received: from mail.localtest ([127.0.0.1])
    by localhost (mail.localtest [127.0.0.1]) (amavisd-new, port 10024)
    with ESMTP id cHuL-_keSoBU for <teward@localtest>;
    Sat, 11 Nov 2017 12:07:17 -0500 (EST)
Received: from [192.168.251.2] (NODATA [FUDGE])
    (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))
    (No client certificate requested)
    by mail.dark-net.io (Postfix) with ESMTPSA id CA598FF786
    for <teward@localtest>; Sat, 11 Nov 2017 12:07:16 -0500 (EST)
To: Thomas Ward <teward@localtest>
From: Thomas Ward <teward@localtest>
Subject: Testing Message
Message-ID: <91ccb8dc-a880-d872-c314-f0ec4a732aa1@localtest>
Date: Sat, 11 Nov 2017 12:07:15 -0500
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101
 Thunderbird/52.4.0
MIME-Version: 1.0
Content-Type: multipart/mixed;
 boundary="------------09F884363E8CBC93E6E5E0EF"
Content-Language: en-US

This is a multi-part message in MIME format.
--------------09F884363E8CBC93E6E5E0EF
Content-Type: multipart/alternative;
 boundary="------------4DF4329FEFF250E03CF3D624"


--------------4DF4329FEFF250E03CF3D624
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit

Test Message with Attachment.

--------------4DF4329FEFF250E03CF3D624
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: 7bit

<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=utf-8">
  </head>
  <body text="#000000" bgcolor="#FFFFFF">
    Test Message with Attachment.<br>
  </body>
</html>

--------------4DF4329FEFF250E03CF3D624--

--------------09F884363E8CBC93E6E5E0EF
Content-Type: application/msword;
 name="test.doc"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="test.doc"

0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAOwADAP7/CQAGAAAAAAAAAAAAAAABAAAADwAAAAAA
AAAAEAAAAgAAAAEAAAD+////AAAAAAAAAAD/////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
///////////////////////////////////9//////////7///8EAAAABQAAAAYAAAAHAAAA
CAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAP7///8QAAAA/v//////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////1IA
bwBvAHQAIABFAG4AdAByAHkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAWAAUA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////8AAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//
/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7///8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA/v///wAAAAAAAAAAAQAAAP7////+////BAAAAAUAAAAGAAAABwAAAAgA
AAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAA
FgAAABcAAAAYAAAAGQAAABoAAAAbAAAAHAAAAB0AAAD+////HwAAACAAAAAhAAAAIgAAAP7/
//8kAAAAJQAAACYAAAAnAAAAKAAAACkAAAAqAAAAKwAAACwAAAAtAAAALgAAAC8AAAAwAAAA
MQAAADIAAAAzAAAANAAAADUAAAA2AAAANwAAADgAAAA5AAAAOgAAADsAAAA8AAAAPQAAAD4A
AAA/AAAAQAAAAEEAAABCAAAAQwAAAEQAAABFAAAARgAAAEcAAABIAAAASQAAAEoAAABLAAAA
TAAAAE0AAABOAAAATwAAAFAAAABRAAAAUgAAAFMAAABUAAAAVQAAAFYAAABXAAAAWAAAAFkA
AABaAAAAWwAAAP7///9dAAAA/v//////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////8BAP7/
AwoAAP////8GCQIAAAAAAMAAAAAAAABGGAAAAE1pY3Jvc29mdCBXb3JkLURva3VtZW50AAoA
AABNU1dvcmREb2MAEAAAAFdvcmQuRG9jdW1lbnQuOAD0ObJxAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAEAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASABQACgABAFsADwACAAAAAAAAAFoAABDx/wIA
WgAAAAYATgBvAHIAbQBhAGwAAAAIAAAAMSQBKiQBMwBCKgBPSgMAUUoDAENKGABtSAkEc0gJ
BEtIAQBQSgQAbkgECHRIBAheSgUAYUoYAF9IOQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAEYA/h8BAAIBRgAAAAcASABlAGEAZABpAG4AZwAAAA0ADwATpPAAFKR4AAYkAQAYAE9K
BgBRSgYAQ0ocAFBKBABeSgUAYUocADQAQhABAAIBNAAAAAkAVABlAHgAdAAgAEIAbwBkAHkA
AAAQABAAEmQgAQEAE6QAABSkjAAAACAALxABARIBIAAAAAQATABpAHMAdAAAAAIAEQAEAF5K
BwBAACIQAQAiAUAAAAAHAEMAYQBwAHQAaQBvAG4AAAANABIAE6R4ABSkeAAMJAEAEgBDShgA
NggBXkoHAGFKGABdCAEmAP4fAQAyASYAAAAFAEkAbgBkAGUAeAAAAAUAEwAMJAEABABeSgcA
AAAAAA8AAAAEAAAOAAAAAP////8ACAAAHggAAAUAAAAACAAAHggAAAYAAAAAAAAADwAAAAAA
AAACEAAAAAAAAAAPAAAAUAAACAAAAAAIAAAARxaQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAFQAaQBtAGUAcwAgAE4AZQB3ACAAUgBvAG0AYQBuAAAANRaQAQIAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFMAeQBtAGIAbwBsAAAAMyaQAQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEEAcgBpAGEAbAAAAGkWkAEBEQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAGkAYgBlAHIAYQB0AGkAbwBuACAAUwBl
AHIAaQBmAAAAVABpAG0AZQBzACAATgBlAHcAIABSAG8AbQBhAG4AAABZBpABAQAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATgBvAHQAbwAgAFMAYQBuAHMAIABDAEoASwAg
AFMAQwAgAFIAZQBnAHUAbABhAHIAAAA5BpABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAARgByAGUAZQBTAGEAbgBzAAAAUyaQAQEQAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAEwAaQBiAGUAcgBhAHQAaQBvAG4AIABTAGEAbgBzAAAAQQByAGkAYQBs
AAAAOSSQAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYAcgBlAGUAUwBh
AG4AcwAAAEIABAABCI0YAADFAgAAaAEAAAAAG1tbxxtbW8cAAAAAAQAAAAAAAQAAAA4AAAAB
AAEAAAAEAIOQAQAAAAEAAAAOAAAAAQABAAAAAQAAAAAAAABnBAAgAAAAAAAEAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEjAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAIAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/wAAAQACAAAAAAAAAAAAAAAAAAAAAAABAAAA
4IWf8vlPaBCrkQgAKyez2TAAAAAIAQAADQAAAAEAAABwAAAAAgAAAHgAAAADAAAAhAAAAAQA
AACQAAAABQAAAJwAAAAGAAAAqAAAAAcAAAC0AAAACAAAAMAAAAAJAAAAzAAAAAoAAADYAAAA
CwAAAOQAAAAMAAAA8AAAAA0AAAD8AAAAAgAAAOn9AAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAA
AAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAAAAAeAAAA
AQAAAAAAAAAeAAAAAgAAADEAAABAAAAAALTEBAAAAABAAAAAAAAAAAAAAABAAAAAZX65XhJb
0wFAAAAAKp7PYhJb0wEAAAAAAAAAAOylAQFNIAkEAADwEr8AAAAAAAAwAAAAAAAIAAAeCAAA
DgBDYW9sYW44MAAAAAAAAAAAAAAAAAAAAAAAAAkEFgAkDgAAAAAAAAAAAAAPAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//DwAFAAAAAQAAAP//DwAGAAAAAQAAAP//DwAAAAAA
AAAAAAAAAAAAAAAAiAAAAAAAlgEAAAAAAACWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAACWAQAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACqAQAADAAAALYBAAAMAAAAAAAAAAAAAADjAQAAQgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCAQAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUEAABiAgAA
AAAAAAAAAADOAQAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADCAQAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAACANkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAYgBlAHcAbwB3AGgAZQByAG8AYQByAHEAZgBhAA0AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAHggAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAQAIAAAeCAAA/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABIgAfsNAvILDgPSGw
bgQisG4EI5BuBCSQbgQzUAAAKDIADjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7/
AAABAAIAAAAAAAAAAAAAAAAAAAAAAAIAAAAC1c3VnC4bEJOXCAArLPmuRAAAAAXVzdWcLhsQ
k5cIACss+a5cAAAAGAAAAAEAAAABAAAAEAAAAAIAAADp/QAAGAAAAAEAAAABAAAAEAAAAAIA
AADp/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSAG8AbwB0ACAARQBuAHQA
cgB5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAFAP//
////////AQAAAAYJAgAAAAAAwAAAAAAAAEYAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAACAFwAA
AAAAAAEAQwBvAG0AcABPAGIAagAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAASAAIAAgAAAAQAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAGoAAAAAAAAAAQBPAGwAZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAgD/////AwAAAP////8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAFAAAAAAAAAAxAFQAYQBiAGwA
ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DgACAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA
AACHBgAAAAAAAAUAUwB1AG0AbQBhAHIAeQBJAG4AZgBvAHIAbQBhAHQAaQBvAG4AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAoAAIABQAAAAYAAAD/////AAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAHgAAADgBAAAAAAAAVwBvAHIAZABEAG8AYwB1AG0AZQBuAHQA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAgD/////////////
//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjAAAAJA4AAAAAAAAFAEQA
bwBjAHUAbQBlAG4AdABTAHUAbQBtAGEAcgB5AEkAbgBmAG8AcgBtAGEAdABpAG8AbgAAAAAA
AAAAAAAAOAACAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAFwAAAB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////AAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v///wAAAAAAAAAA
--------------09F884363E8CBC93E6E5E0EF
Content-Type: text/plain; charset=UTF-8;
 name="foo.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="foo.txt"

aW9hd2VocnA5YXdoZnA5OGFod2VyZnA5YXI4cDl0ODlhZXJoZWFyZmFlcmYK
--------------09F884363E8CBC93E6E5E0EF--

Proper output (in a file called converted_message):

Content-Type: multipart/alternative;
 boundary="===============0260810760593589585=="
MIME-Version: 1.0
Subject: Testing Message
To: Thomas Ward <teward@localtest>
From: 'Thomas Ward' via 'Example List' <list@example.com>
Reply-To: Thomas Ward <teward@localtest>
Message-ID: <151042736894.30764.610196434709261357@overlord>
Date: Sat, 11 Nov 2017 12:07:15 -0500

--===============0260810760593589585==
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit

Test Message with Attachment.

--===============0260810760593589585==
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: 7bit

<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=utf-8">
  </head>
  <body text="#000000" bgcolor="#FFFFFF">
    Test Message with Attachment.<br>
  </body>
</html>

--===============0260810760593589585==
Content-Type: application/msword; name="test.doc"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="test.doc"

0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAOwADAP7/CQAGAAAAAAAAAAAAAAABAAAADwAAAAAA
AAAAEAAAAgAAAAEAAAD+////AAAAAAAAAAD/////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
///////////////////////////////////9//////////7///8EAAAABQAAAAYAAAAHAAAA
CAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAP7///8QAAAA/v//////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////1IA
bwBvAHQAIABFAG4AdAByAHkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAWAAUA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////8AAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//
/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7///8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA/v///wAAAAAAAAAAAQAAAP7////+////BAAAAAUAAAAGAAAABwAAAAgA
AAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAA
FgAAABcAAAAYAAAAGQAAABoAAAAbAAAAHAAAAB0AAAD+////HwAAACAAAAAhAAAAIgAAAP7/
//8kAAAAJQAAACYAAAAnAAAAKAAAACkAAAAqAAAAKwAAACwAAAAtAAAALgAAAC8AAAAwAAAA
MQAAADIAAAAzAAAANAAAADUAAAA2AAAANwAAADgAAAA5AAAAOgAAADsAAAA8AAAAPQAAAD4A
AAA/AAAAQAAAAEEAAABCAAAAQwAAAEQAAABFAAAARgAAAEcAAABIAAAASQAAAEoAAABLAAAA
TAAAAE0AAABOAAAATwAAAFAAAABRAAAAUgAAAFMAAABUAAAAVQAAAFYAAABXAAAAWAAAAFkA
AABaAAAAWwAAAP7///9dAAAA/v//////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////8BAP7/
AwoAAP////8GCQIAAAAAAMAAAAAAAABGGAAAAE1pY3Jvc29mdCBXb3JkLURva3VtZW50AAoA
AABNU1dvcmREb2MAEAAAAFdvcmQuRG9jdW1lbnQuOAD0ObJxAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAEAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASABQACgABAFsADwACAAAAAAAAAFoAABDx/wIA
WgAAAAYATgBvAHIAbQBhAGwAAAAIAAAAMSQBKiQBMwBCKgBPSgMAUUoDAENKGABtSAkEc0gJ
BEtIAQBQSgQAbkgECHRIBAheSgUAYUoYAF9IOQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAEYA/h8BAAIBRgAAAAcASABlAGEAZABpAG4AZwAAAA0ADwATpPAAFKR4AAYkAQAYAE9K
BgBRSgYAQ0ocAFBKBABeSgUAYUocADQAQhABAAIBNAAAAAkAVABlAHgAdAAgAEIAbwBkAHkA
AAAQABAAEmQgAQEAE6QAABSkjAAAACAALxABARIBIAAAAAQATABpAHMAdAAAAAIAEQAEAF5K
BwBAACIQAQAiAUAAAAAHAEMAYQBwAHQAaQBvAG4AAAANABIAE6R4ABSkeAAMJAEAEgBDShgA
NggBXkoHAGFKGABdCAEmAP4fAQAyASYAAAAFAEkAbgBkAGUAeAAAAAUAEwAMJAEABABeSgcA
AAAAAA8AAAAEAAAOAAAAAP////8ACAAAHggAAAUAAAAACAAAHggAAAYAAAAAAAAADwAAAAAA
AAACEAAAAAAAAAAPAAAAUAAACAAAAAAIAAAARxaQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAFQAaQBtAGUAcwAgAE4AZQB3ACAAUgBvAG0AYQBuAAAANRaQAQIAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFMAeQBtAGIAbwBsAAAAMyaQAQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEEAcgBpAGEAbAAAAGkWkAEBEQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAGkAYgBlAHIAYQB0AGkAbwBuACAAUwBl
AHIAaQBmAAAAVABpAG0AZQBzACAATgBlAHcAIABSAG8AbQBhAG4AAABZBpABAQAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATgBvAHQAbwAgAFMAYQBuAHMAIABDAEoASwAg
AFMAQwAgAFIAZQBnAHUAbABhAHIAAAA5BpABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAARgByAGUAZQBTAGEAbgBzAAAAUyaQAQEQAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAEwAaQBiAGUAcgBhAHQAaQBvAG4AIABTAGEAbgBzAAAAQQByAGkAYQBs
AAAAOSSQAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYAcgBlAGUAUwBh
AG4AcwAAAEIABAABCI0YAADFAgAAaAEAAAAAG1tbxxtbW8cAAAAAAQAAAAAAAQAAAA4AAAAB
AAEAAAAEAIOQAQAAAAEAAAAOAAAAAQABAAAAAQAAAAAAAABnBAAgAAAAAAAEAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEjAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAIAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/wAAAQACAAAAAAAAAAAAAAAAAAAAAAABAAAA
4IWf8vlPaBCrkQgAKyez2TAAAAAIAQAADQAAAAEAAABwAAAAAgAAAHgAAAADAAAAhAAAAAQA
AACQAAAABQAAAJwAAAAGAAAAqAAAAAcAAAC0AAAACAAAAMAAAAAJAAAAzAAAAAoAAADYAAAA
CwAAAOQAAAAMAAAA8AAAAA0AAAD8AAAAAgAAAOn9AAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAA
AAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAAAAAeAAAAAQAAAAAAAAAeAAAA
AQAAAAAAAAAeAAAAAgAAADEAAABAAAAAALTEBAAAAABAAAAAAAAAAAAAAABAAAAAZX65XhJb
0wFAAAAAKp7PYhJb0wEAAAAAAAAAAOylAQFNIAkEAADwEr8AAAAAAAAwAAAAAAAIAAAeCAAA
DgBDYW9sYW44MAAAAAAAAAAAAAAAAAAAAAAAAAkEFgAkDgAAAAAAAAAAAAAPAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//DwAFAAAAAQAAAP//DwAGAAAAAQAAAP//DwAAAAAA
AAAAAAAAAAAAAAAAiAAAAAAAlgEAAAAAAACWAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAACWAQAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACqAQAADAAAALYBAAAMAAAAAAAAAAAAAADjAQAAQgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCAQAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUEAABiAgAA
AAAAAAAAAADOAQAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADCAQAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAACANkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAYgBlAHcAbwB3AGgAZQByAG8AYQByAHEAZgBhAA0AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAHggAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAQAIAAAeCAAA/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABIgAfsNAvILDgPSGw
bgQisG4EI5BuBCSQbgQzUAAAKDIADjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7/
AAABAAIAAAAAAAAAAAAAAAAAAAAAAAIAAAAC1c3VnC4bEJOXCAArLPmuRAAAAAXVzdWcLhsQ
k5cIACss+a5cAAAAGAAAAAEAAAABAAAAEAAAAAIAAADp/QAAGAAAAAEAAAABAAAAEAAAAAIA
AADp/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSAG8AbwB0ACAARQBuAHQA
cgB5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAFAP//
////////AQAAAAYJAgAAAAAAwAAAAAAAAEYAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAACAFwAA
AAAAAAEAQwBvAG0AcABPAGIAagAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAASAAIAAgAAAAQAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAGoAAAAAAAAAAQBPAGwAZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAgD/////AwAAAP////8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAFAAAAAAAAAAxAFQAYQBiAGwA
ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DgACAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA
AACHBgAAAAAAAAUAUwB1AG0AbQBhAHIAeQBJAG4AZgBvAHIAbQBhAHQAaQBvAG4AAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAoAAIABQAAAAYAAAD/////AAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAHgAAADgBAAAAAAAAVwBvAHIAZABEAG8AYwB1AG0AZQBuAHQA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAgD/////////////
//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjAAAAJA4AAAAAAAAFAEQA
bwBjAHUAbQBlAG4AdABTAHUAbQBtAGEAcgB5AEkAbgBmAG8AcgBtAGEAdABpAG8AbgAAAAAA
AAAAAAAAOAACAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAFwAAAB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////AAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v///wAAAAAAAAAA
--===============0260810760593589585==
Content-Type: text/plain; charset="UTF-8"; name="foo.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="foo.txt"

aW9hd2VocnA5YXdoZnA5OGFod2VyZnA5YXI4cDl0ODlhZXJoZWFyZmFlcmYK
--===============0260810760593589585==--
\$\endgroup\$
2
\$\begingroup\$

Possible improvements:

  • disposition_dict doesn't need the outer list comprehension, you can just use an implicit generator comprehension. This just removes some unneeded complexity.
  • Your turnery didn't look like one at first, and so threw me when I noticed it was one.
  • Your conditional is over complicated, you don't need the map.
  • You don't need a conditional, you can instead use a slice.

    (map(str.strip, pt.split('=', 1)) + [None])[:2]
    
  • It's discouraged to use map, I'd recommend that you instead just use a list comprehension.

  • Always use with when using open. Your code doesn't guarantee that any file you open is actually closed.
  • Use if __name__ == '__main__':.
  • Use more functions, you won't need comments if you have well named functions.
  • You may want to look into argparse, rather than using sys.argv.
  • if 'attachment' or 'inline' in cdisp_d: is always true, you probably should use something like:

    any(key in cdisp_d for key in ('attachment', 'inline'))
    'attachment' in cdisp_d or 'inline' in cdisp_d
    
  • You can use str.translate, rather than multiple str.replaces or str.strips. Since you're str.replaceing to an empty string, on both your attachfnames, you don't even need to use string.maketrans.

    >>> 'a\rb\nc"d'.translate(None, '\r\n"')
    'abcd'
    
  • You can restructure your first main for loop, so that it's easier to read, by moving the part.is_multipart to the beginning of the if, so the rest of the code doesn't include it. You can also reduce the second if, from multiple ifs, to just one elif.

    if part.is_multipart():
        continue
    elif cdisp:
        ...
    elif maintype == "text":
        ...
    
  • Don't use a hanging indent, if you have only like 10 chars left of vertical space. Instead I'd recomend using:

    newmsg.add_header('From', "'{}' via '{}' <{}>".format(
        msg['From'].split('<')[0].strip(),
        'Example List',
        'list@example.com'
    ))
    
  • Make your code a little more dry, by merging your ifs for the above print. Take the following conditional, and usages:

    len(msg['From'].split('<')) > 1
    if: msg['From'].split('<')[0].strip()
    else: msg['From']
    

    The only difference is that one uses .strip(), where the other doesn't. And so I'd use:

    from_, other = msg['From'].split('<')
    if other:
        from_ = from_.strip()
    ...
    

    Even better, you may want to strip both, and so you can just use one line of code for them.

  • It's discouraged to use type(a) is b. Instead use isinstance(a, b).

  • You can rearrange your if multipart code, so that it's less WET. You only change how the newmsg is created, and have quite a lot of the same code. And some final changes.

And so I'd change your code more to:

# coding=utf-8
import sys
import email
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart


# noinspection PyTypeChecker
def disposition_dict(dispstr):
    # type: (str) -> dict
    """
    Converts a string which contains a Content-Disposition
    header's contents into a dict.
    :param dispstr: String with Content-Disposition value
    :return: Dictionary of Content-Disposition values.
    """
    return dict(
         ([i.strip() for i in pt.split('=', 1)] + [None])[:2]
         for pt in dispstr.split(";")
    )

with open(sys.argv[1], mode="r") as f:
    msg = email.message_from_string(f.read())
multipart = msg.is_multipart()
msg_components = {'To': msg['To'], 'From': msg['From'], 'Subject': msg['Subject'],
                  'Message-ID': email.utils.make_msgid(), 'Date': msg['Date'], 'Payloads': []}

for part in msg.walk():
    if not part.is_multipart():
        maintype, subtype = part.get_content_type().split('/', 1)
        cdisp = part.get("Content-Disposition")
        if part.is_multipart():
            continue
        elif cdisp:
            cdisp_d = disposition_dict(cdisp)
            if any(key in cdisp_d for key in ('attachment', 'inline')):
                attachfname = cdisp_d['filename'].translate(None, '\r\n"')
                data = MIMEBase(maintype, subtype)
                for param in part.get_params():
                    if param not in data.get_params():
                        data.set_param(param[0], param[1])
                if 'Content-Transfer-Encoding' in part.keys():
                    data.add_header('Content-Transfer-Encoding', part['Content-Transfer-Encoding'])
                data.set_payload(part.get_payload())
                msg_components['Payloads'].append((data, attachfname))
        elif maintype == "text":
            data = MIMEText(part.get_payload(), _subtype=subtype)
            msg_components['Payloads'].append(data)

if multipart:
    newmsg = MIMEMultipart('alternative')
else:
    if len(msg_components['Payloads']) != 1:
        raise TypeError("You cannot have a non-multipart message with multiple payloads.")

    mimepart = msg_components['Payloads'][0]
    maintype, subtype = mimepart.get_content_type().split('/', 1)
    newmsg = MIMEBase(maintype, subtype)

newmsg['Subject'] = msg_components['Subject']
newmsg['To'] = msg_components['To']
from_, other = msg['From'].split('<')
if other:
    from_ = from_.strip()
newmsg.add_header('From', "'{}' via '{}' <{}>".format(
    from_,
    'Example List',
    'list@example.com'
))
newmsg['Reply-To'] = msg_components['From']
if 'List-Id' in msg.keys():
    newmsg['List-Id'] = msg['List-Id']
if 'List-Post' in msg.keys():
    if multipart:
        newmsg['List-Post'] = 'nowhere@dark-net.localtesting'
    else:
        newmsg['List-Post'] = msg['List-Post']
newmsg['Message-ID'] = msg_components['Message-ID']
newmsg['Date'] = msg_components['Date']

if not multipart:
    newmsg.set_payload(mimepart.get_payload(), charset=mimepart.get_charset())
else:
    for payload in msg_components['Payloads']:
        if not isinstance(payload, tuple):
            del payload['MIME-Version']
            newmsg.attach(payload)
        else:
            if payload[0].get_content_type().split('/', 1)[0] == "text":
                payload[0].set_param('name', payload[1])
            payload[0].add_header('Content-Disposition', 'attachment', filename=payload[1])
            del payload[0]['MIME-Version']
            newmsg.attach(payload[0])

with open('converted_message', mode='w') as f:
    f.write(newmsg.as_string())
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.