diff --git a/.gitignore b/.gitignore index 5deb70a..68ecb9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,4 @@ venv/ -__pycache__/ .idea/ -# misc testing scripts -testing*.py -# i have a symlink here so testing scripts can import gpgmymail -# as python expects `import gpgmymail` to be importing `gpgmymail.py` -gpgmymail.py +testing.py diff --git a/gpgmymail b/gpgmymail index 4d78d91..a3c230f 100755 --- a/gpgmymail +++ b/gpgmymail @@ -34,8 +34,6 @@ import email.mime.application import email.mime.multipart import email.mime.message import typing -# for decode_email: -import quopri # see: https://gnupg.readthedocs.io/en/latest/ import gnupg @@ -51,86 +49,6 @@ def is_message_encrypted(message: email.message.Message) -> bool: return message.get_content_subtype() == "encrypted" -def decode_email(message: email.message.Message) -> email.message.Message: - """Turn a quoted-printable or base64 encoded email into a 7or8bit encoded - email - - :param message: email.message.Message to be decoded - :return: decoded email.message.Message""" - # this is a kinda hacky way to do this by manipulating the message as a - # string but i couldn't get it to work any other way - # decoding needed: - # as_string() gives us str, encode() gives us bytes - decoded_bytes = message.as_bytes() - decoded_bytes = quopri.decodestring(decoded_bytes) - - # replace any instances of the Content-Transfer-Encoding header - # quopri version, we do base64 version down there - decoded_bytes = decoded_bytes.replace( - b'Content-Transfer-Encoding: quoted-printable', - b'Content-Transfer-Encoding: 7bit' - ) - - # REALLY hacky but i had issues with the more sensible ways to do this. - # iterates through a Message object to find CTEs of base64 - # gets the b64 payload and the decoded payload - # then find and replaces in decoded_bytes the b64 payload - # with the decoded payload - # lol - - def decode_b64_part( - part: email.message.Message, - decoded_bytes: bytes, - most_recent_boundary: str = None - ) -> bytes: - if part.get("Content-Transfer-Encoding") == "base64": - b64_str = part.get_payload() - # remove the boundary as we don't want to change this - if most_recent_boundary: - b64_str = b64_str.replace(most_recent_boundary, "") - # sometimes we have leftover hyphens from a boundary, so strip: - # hyphens not in base64 so we know not to use them - # strip whitespace first - b64_str = b64_str.strip() - b64_str = b64_str.strip('-') - b64_str = b64_str.encode() # turn into bytes-like object - # this will also decode the boundary so there'll be some nonsese - # chars at end of email but it's nbd - decoded_b64_str = part.get_payload(decode=True) - return decoded_bytes.replace( - b64_str, - decoded_b64_str - ) - - quopri_decoded_message = email.message_from_bytes(decoded_bytes) - if quopri_decoded_message.is_multipart(): - most_recent_boundary = None - for part in quopri_decoded_message.walk(): - # multipart and has boundary (not None) - if part.is_multipart() and part.get_boundary(): - most_recent_boundary = part.get_boundary() - else: - decoded_bytes = decode_b64_part( - part, - decoded_bytes, - most_recent_boundary - ) - else: - decoded_bytes = decode_b64_part( - quopri_decoded_message, - decoded_bytes, - None - ) - - decoded_bytes = decoded_bytes.replace( - b'Content-Transfer-Encoding: base64', - b'Content-Transfer-Encoding: 7bit' - ) - - # if i do message_from_bytes it bizarrely changes it back to base64? - # utf-8 has encoding issues so do latin1 - return email.message_from_string(decoded_bytes.decode("latin1")) - def encrypt( message: email.message.Message, recipients: typing.List[str], @@ -149,14 +67,10 @@ def encrypt( :return: The encrypted email as a string""" - # exclusion criteria: # some mail clients like Thunderbird don't like twice-encrypted emails, # so we return the message as-is if it's already encrypted if is_message_encrypted(message) and not unconditionally_encrypt: return message.as_string() - - # make necessary changes to message - message = decode_email(message) gpg = gnupg.GPG() gpg.encoding = encoding