From ed57c8f0d46a4369d9bf30dc7c711e45fd7d57a8 Mon Sep 17 00:00:00 2001 From: revsuine Date: Wed, 13 Nov 2024 17:22:21 +0000 Subject: [PATCH 01/17] first attempt to implement decoding as 7or8bit (non-working) --- gpgmymail | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gpgmymail b/gpgmymail index a3c230f..8b51754 100755 --- a/gpgmymail +++ b/gpgmymail @@ -49,6 +49,23 @@ 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""" + payload = [] + + for part in message.walk(): + if not part.is_multipart(): + payload.append(part.get_payload(decode=True)) + + message["Content-Transfer-Encoding"] = "7bit" + message.set_payload(payload) + + return message + def encrypt( message: email.message.Message, recipients: typing.List[str], @@ -67,10 +84,14 @@ 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 From f895e87ec03e30913b04794f5d51ef2e0387e27b Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 15:19:57 +0000 Subject: [PATCH 02/17] account for non-multipart emails --- gpgmymail | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gpgmymail b/gpgmymail index 8b51754..21c46e6 100755 --- a/gpgmymail +++ b/gpgmymail @@ -55,14 +55,17 @@ def decode_email(message: email.message.Message) -> email.message.Message: :param message: email.message.Message to be decoded :return: decoded email.message.Message""" - payload = [] + if message.is_multipart(): + payload = [] + for part in message.walk(): + if not part.is_multipart(): + payload.append(part.get_payload(decode=True)) - for part in message.walk(): - if not part.is_multipart(): - payload.append(part.get_payload(decode=True)) + message.set_payload(payload) + else: + message.set_payload(message.get_payload(decode=True)) message["Content-Transfer-Encoding"] = "7bit" - message.set_payload(payload) return message From b78f1f04af81196538523896888bdbf5d6d445a3 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 15:59:20 +0000 Subject: [PATCH 03/17] more testing scripts --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 68ecb9b..6e12153 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ venv/ .idea/ -testing.py +testing*.py From 8755e0e1cc81b761f2cb291f2fa25f6162df8482 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 17:39:25 +0000 Subject: [PATCH 04/17] gitignore some testing files --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 6e12153..5deb70a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ 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 From e7a8f40b915b7d169b95e63cfb1ad2d98b0a150e Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:13:16 +0000 Subject: [PATCH 05/17] first attempt to implement decoding as 7or8bit through quopri, base64, and byte replacement --- gpgmymail | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/gpgmymail b/gpgmymail index 21c46e6..2010fc3 100755 --- a/gpgmymail +++ b/gpgmymail @@ -34,12 +34,16 @@ import email.mime.application import email.mime.multipart import email.mime.message import typing +# for decode_email: +import quopri +import base64 # see: https://gnupg.readthedocs.io/en/latest/ import gnupg # constants DEFAULT_ENCODING='utf-8' # default is latin-1 which fails w some unicode chars +CTES_TO_BE_DECODED = ("quoted-printable", "base64") def is_message_encrypted(message: email.message.Message) -> bool: """Determines whether or not an email message is encrypted. @@ -55,19 +59,43 @@ def decode_email(message: email.message.Message) -> email.message.Message: :param message: email.message.Message to be decoded :return: decoded email.message.Message""" - if message.is_multipart(): - payload = [] - for part in message.walk(): - if not part.is_multipart(): - payload.append(part.get_payload(decode=True)) + # 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 - message.set_payload(payload) - else: - message.set_payload(message.get_payload(decode=True)) + msg_ctes = message.get_all("Content-Transfer-Encoding") + + # this list will be populated with any encoding that needs to be decoded, + # e.g. base64 + # empty if no decoding needed + # set used to avoid dupes + decodes_needed = set() + # check if any of the parts of the message need decoding + for cte in CTES_TO_BE_DECODED: + if cte in msg_ctes: + decodes_needed.add(cte) + # no decoding needed, go ahead with message + if not decodes_needed: + return message - message["Content-Transfer-Encoding"] = "7bit" + # decoding needed: + # as_string() gives us str, encode() gives us bytes + decoded_bytes = msg.as_string().encode() + if "quoted-printable" in decodes_needed: + decoded_bytes = quopri.decodestring(decoded_bytes) + if "base64" in decodes_needed: + decoded_bytes = base64.b64decode(decoded_bytes) - return message + # replace any instances of the Content-Transfer-Encoding header + decoded_bytes = decoded_bytes.replace( + b'Content-Transfer-Encoding: quoted-printable', + b'Content-Transfer-Encoding: 7bit' + ) + decoded_bytes = decoded_bytes.replace( + b'Content-Transfer-Encoding: base64', + b'Content-Transfer-Encoding: 7bit' + ) + + return email.message_from_bytes(decoded_bytes) def encrypt( message: email.message.Message, From 2955e990dadb8f2b493059b6d4bc624e2ace529f Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:15:06 +0000 Subject: [PATCH 06/17] var name typo --- gpgmymail | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpgmymail b/gpgmymail index 2010fc3..0164660 100755 --- a/gpgmymail +++ b/gpgmymail @@ -79,7 +79,7 @@ def decode_email(message: email.message.Message) -> email.message.Message: # decoding needed: # as_string() gives us str, encode() gives us bytes - decoded_bytes = msg.as_string().encode() + decoded_bytes = message.as_string().encode() if "quoted-printable" in decodes_needed: decoded_bytes = quopri.decodestring(decoded_bytes) if "base64" in decodes_needed: From a21ee759c87e4419ec0bb34f5d05af8f482ee953 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:26:22 +0000 Subject: [PATCH 07/17] temp remove base64 decoding and unconditionally quopri decode --- gpgmymail | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/gpgmymail b/gpgmymail index 0164660..d7575d9 100755 --- a/gpgmymail +++ b/gpgmymail @@ -36,14 +36,12 @@ import email.mime.message import typing # for decode_email: import quopri -import base64 # see: https://gnupg.readthedocs.io/en/latest/ import gnupg # constants DEFAULT_ENCODING='utf-8' # default is latin-1 which fails w some unicode chars -CTES_TO_BE_DECODED = ("quoted-printable", "base64") def is_message_encrypted(message: email.message.Message) -> bool: """Determines whether or not an email message is encrypted. @@ -61,39 +59,19 @@ def decode_email(message: email.message.Message) -> email.message.Message: :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 - - msg_ctes = message.get_all("Content-Transfer-Encoding") - - # this list will be populated with any encoding that needs to be decoded, - # e.g. base64 - # empty if no decoding needed - # set used to avoid dupes - decodes_needed = set() - # check if any of the parts of the message need decoding - for cte in CTES_TO_BE_DECODED: - if cte in msg_ctes: - decodes_needed.add(cte) - # no decoding needed, go ahead with message - if not decodes_needed: - return message - # decoding needed: # as_string() gives us str, encode() gives us bytes decoded_bytes = message.as_string().encode() - if "quoted-printable" in decodes_needed: - decoded_bytes = quopri.decodestring(decoded_bytes) - if "base64" in decodes_needed: - decoded_bytes = base64.b64decode(decoded_bytes) + decoded_bytes = quopri.decodestring(decoded_bytes) # replace any instances of the Content-Transfer-Encoding header decoded_bytes = decoded_bytes.replace( b'Content-Transfer-Encoding: quoted-printable', b'Content-Transfer-Encoding: 7bit' ) - decoded_bytes = decoded_bytes.replace( - b'Content-Transfer-Encoding: base64', - b'Content-Transfer-Encoding: 7bit' - ) + + # TODO: base64 decoding, which is more difficult due to the need to not + # treat the whole email like it's base64 return email.message_from_bytes(decoded_bytes) From 82c5144e58807ce026fe055fb823c90f90f59e34 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:44:28 +0000 Subject: [PATCH 08/17] implement really hacky way of decoding b64 parts --- gpgmymail | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/gpgmymail b/gpgmymail index d7575d9..708d9d2 100755 --- a/gpgmymail +++ b/gpgmymail @@ -65,13 +65,36 @@ def decode_email(message: email.message.Message) -> email.message.Message: 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' ) - # TODO: base64 decoding, which is more difficult due to the need to not - # treat the whole email like it's base64 + # 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 + quopri_decoded_message = email.message_from_bytes(decoded_bytes) + if quopri_decoded_message.is_multipart(): + for part in quopri_decoded_message.walk(): + if not part.is_multipart(): + if part.get("Content-Transfer-Encoding") == "base64": + b64_str = part.get_payload() + decoded_b64_str = part.get_payload(decode=True) + decoded_bytes = decoded_bytes.replace( + b64_str, + decoded_b64_str + ) + else: + # TODO + + decoded_bytes = decoded_bytes.replace( + b'Content-Transfer-Encoding: base64', + b'Content-Transfer-Encoding: 7bit' + ) return email.message_from_bytes(decoded_bytes) From 158f5356a1b660445cfee022624e2544bb39f3a3 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:45:30 +0000 Subject: [PATCH 09/17] syntax error fix --- gpgmymail | 1 + 1 file changed, 1 insertion(+) diff --git a/gpgmymail b/gpgmymail index 708d9d2..1d23a23 100755 --- a/gpgmymail +++ b/gpgmymail @@ -90,6 +90,7 @@ def decode_email(message: email.message.Message) -> email.message.Message: ) else: # TODO + pass decoded_bytes = decoded_bytes.replace( b'Content-Transfer-Encoding: base64', From 9a3a1f2cb73ea71245e15292429f31db3f282bfc Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:46:30 +0000 Subject: [PATCH 10/17] encode the find and replaces for b64 decode --- gpgmymail | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpgmymail b/gpgmymail index 1d23a23..c725fbf 100755 --- a/gpgmymail +++ b/gpgmymail @@ -82,8 +82,8 @@ def decode_email(message: email.message.Message) -> email.message.Message: for part in quopri_decoded_message.walk(): if not part.is_multipart(): if part.get("Content-Transfer-Encoding") == "base64": - b64_str = part.get_payload() - decoded_b64_str = part.get_payload(decode=True) + b64_str = part.get_payload().encode() + decoded_b64_str = part.get_payload(decode=True).encode() decoded_bytes = decoded_bytes.replace( b64_str, decoded_b64_str From b213a0ab52c6b645c566722979643d6d22c5a394 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:52:18 +0000 Subject: [PATCH 11/17] don't encode decoded --- gpgmymail | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpgmymail b/gpgmymail index c725fbf..84fefb4 100755 --- a/gpgmymail +++ b/gpgmymail @@ -83,7 +83,7 @@ def decode_email(message: email.message.Message) -> email.message.Message: if not part.is_multipart(): if part.get("Content-Transfer-Encoding") == "base64": b64_str = part.get_payload().encode() - decoded_b64_str = part.get_payload(decode=True).encode() + decoded_b64_str = part.get_payload(decode=True) decoded_bytes = decoded_bytes.replace( b64_str, decoded_b64_str From 6590baafd19ae3ff422dc6d4daede19d60f32d0c Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 18:55:06 +0000 Subject: [PATCH 12/17] try do this by replacing entire payload --- gpgmymail | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gpgmymail b/gpgmymail index 84fefb4..41f6813 100755 --- a/gpgmymail +++ b/gpgmymail @@ -60,8 +60,7 @@ def decode_email(message: email.message.Message) -> 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_string().encode() + decoded_bytes = message.as_bytes() decoded_bytes = quopri.decodestring(decoded_bytes) # replace any instances of the Content-Transfer-Encoding header @@ -82,11 +81,12 @@ def decode_email(message: email.message.Message) -> email.message.Message: for part in quopri_decoded_message.walk(): if not part.is_multipart(): if part.get("Content-Transfer-Encoding") == "base64": - b64_str = part.get_payload().encode() - decoded_b64_str = part.get_payload(decode=True) + new_part = part + new_part.replace_header("Content-Transfer-Encoding", "7bit") + new_part.set_payload(part.get_payload(decode=True)) decoded_bytes = decoded_bytes.replace( - b64_str, - decoded_b64_str + part.as_bytes(), + new_part.as_bytes() ) else: # TODO From 10f25158bfeb450ed7e03d57e43128f46a06caae Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 19:05:21 +0000 Subject: [PATCH 13/17] try to decode w/o boundary --- gpgmymail | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/gpgmymail b/gpgmymail index 41f6813..d5e3033 100755 --- a/gpgmymail +++ b/gpgmymail @@ -36,6 +36,7 @@ import email.mime.message import typing # for decode_email: import quopri +import base64 # see: https://gnupg.readthedocs.io/en/latest/ import gnupg @@ -60,7 +61,8 @@ def decode_email(message: email.message.Message) -> 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: - decoded_bytes = message.as_bytes() + # as_string() gives us str, encode() gives us bytes + decoded_bytes = message.as_string().encode() decoded_bytes = quopri.decodestring(decoded_bytes) # replace any instances of the Content-Transfer-Encoding header @@ -78,15 +80,20 @@ def decode_email(message: email.message.Message) -> email.message.Message: # lol 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(): - if not part.is_multipart(): + # multipart and has boundary (not None) + if part.is_multipart() and part.get_boundary(): + most_recent_boundary = part.get_boundary() + else: if part.get("Content-Transfer-Encoding") == "base64": - new_part = part - new_part.replace_header("Content-Transfer-Encoding", "7bit") - new_part.set_payload(part.get_payload(decode=True)) + b64_str = part.get_payload() + # remove the boundary as we don't want to change this + b64_str = b64_str.replace(most_recent_boundary, "") + decoded_b64_str = base64.b64decode(b64_str) decoded_bytes = decoded_bytes.replace( - part.as_bytes(), - new_part.as_bytes() + b64_str, + decoded_b64_str ) else: # TODO From f559fef2ed11e69f766e928bbc3a927135ea5eed Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 19:15:37 +0000 Subject: [PATCH 14/17] strip trailing hyphens from base64 --- gpgmymail | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gpgmymail b/gpgmymail index d5e3033..4329858 100755 --- a/gpgmymail +++ b/gpgmymail @@ -36,7 +36,6 @@ import email.mime.message import typing # for decode_email: import quopri -import base64 # see: https://gnupg.readthedocs.io/en/latest/ import gnupg @@ -90,7 +89,13 @@ def decode_email(message: email.message.Message) -> email.message.Message: b64_str = part.get_payload() # remove the boundary as we don't want to change this b64_str = b64_str.replace(most_recent_boundary, "") - decoded_b64_str = base64.b64decode(b64_str) + # 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 + decoded_b64_str = part.get_payload(decode=True) decoded_bytes = decoded_bytes.replace( b64_str, decoded_b64_str From f1a07cb1e0af372b11ebc5d756aa3a22a1e1ddac Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 19:23:51 +0000 Subject: [PATCH 15/17] use message_from_string instead of message_from_bytes cause message_from_bytes bizarrely changes it back to base64 --- gpgmymail | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gpgmymail b/gpgmymail index 4329858..e7c6073 100755 --- a/gpgmymail +++ b/gpgmymail @@ -61,7 +61,7 @@ def decode_email(message: email.message.Message) -> email.message.Message: # 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_string().encode() + decoded_bytes = message.as_bytes() decoded_bytes = quopri.decodestring(decoded_bytes) # replace any instances of the Content-Transfer-Encoding header @@ -108,8 +108,10 @@ def decode_email(message: email.message.Message) -> email.message.Message: b'Content-Transfer-Encoding: base64', b'Content-Transfer-Encoding: 7bit' ) - - return email.message_from_bytes(decoded_bytes) + + # 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, From 218ee51ac2c72457151571e5541488535e78c6b4 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 19:37:15 +0000 Subject: [PATCH 16/17] abstract b64 decoding behaviour to a function --- gpgmymail | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/gpgmymail b/gpgmymail index e7c6073..b2f539f 100755 --- a/gpgmymail +++ b/gpgmymail @@ -71,6 +71,29 @@ def decode_email(message: email.message.Message) -> email.message.Message: b'Content-Transfer-Encoding: 7bit' ) + 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 + 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 + ) + # 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 @@ -85,21 +108,11 @@ def decode_email(message: email.message.Message) -> email.message.Message: if part.is_multipart() and part.get_boundary(): most_recent_boundary = part.get_boundary() else: - if part.get("Content-Transfer-Encoding") == "base64": - b64_str = part.get_payload() - # remove the boundary as we don't want to change this - 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 - decoded_b64_str = part.get_payload(decode=True) - decoded_bytes = decoded_bytes.replace( - b64_str, - decoded_b64_str - ) + decoded_bytes = decode_b64_part( + part, + decoded_bytes, + most_recent_boundary + ) else: # TODO pass From 633a54e2b1ed0cd1cf2a1f67dd8d4812ff1a11f9 Mon Sep 17 00:00:00 2001 From: revsuine Date: Thu, 14 Nov 2024 19:39:17 +0000 Subject: [PATCH 17/17] decode b64 bytes for non-multipart message too --- gpgmymail | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/gpgmymail b/gpgmymail index b2f539f..4d78d91 100755 --- a/gpgmymail +++ b/gpgmymail @@ -71,6 +71,13 @@ def decode_email(message: email.message.Message) -> email.message.Message: 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, @@ -79,7 +86,8 @@ def decode_email(message: email.message.Message) -> email.message.Message: if part.get("Content-Transfer-Encoding") == "base64": b64_str = part.get_payload() # remove the boundary as we don't want to change this - b64_str = b64_str.replace(most_recent_boundary, "") + 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 @@ -94,12 +102,6 @@ def decode_email(message: email.message.Message) -> email.message.Message: decoded_b64_str ) - # 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 quopri_decoded_message = email.message_from_bytes(decoded_bytes) if quopri_decoded_message.is_multipart(): most_recent_boundary = None @@ -114,8 +116,11 @@ def decode_email(message: email.message.Message) -> email.message.Message: most_recent_boundary ) else: - # TODO - pass + decoded_bytes = decode_b64_part( + quopri_decoded_message, + decoded_bytes, + None + ) decoded_bytes = decoded_bytes.replace( b'Content-Transfer-Encoding: base64',