Compare commits
No commits in common. "7fe4b8103cf8cfe12384869203e843dac3deb8aa" and "52405bf067e2e105c2626c058ef8cae5d6abe0ae" have entirely different histories.
7fe4b8103c
...
52405bf067
1 changed files with 118 additions and 18 deletions
136
gpgmymail
136
gpgmymail
|
@ -58,6 +58,124 @@ 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"""
|
||||
def decoded_bytes_to_return_value(decoded_bytes: bytes) -> email.message.Message:
|
||||
"""
|
||||
if at any point you want to return, return a call of this function and
|
||||
pass decoded_bytes
|
||||
|
||||
:param decoded_bytes: an email as an ASCII byte array; this should be
|
||||
stored in decoded_bytes
|
||||
:return: the expected return value of decode_email
|
||||
"""
|
||||
# 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"))
|
||||
|
||||
# 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
|
||||
|
||||
# sometimes this raises an exception and idk why
|
||||
try:
|
||||
decoded_bytes = message.as_bytes()
|
||||
except UnicodeEncodeError:
|
||||
decoded_bytes = message.as_string().encode()
|
||||
# if email doesn't need decoding
|
||||
has_quopri = b'Content-Transfer-Encoding: quoted-printable' in decoded_bytes
|
||||
has_base64 = b'Content-Transfer-Encoding: base64' in decoded_bytes
|
||||
if not (has_quopri or has_base64):
|
||||
return 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'
|
||||
)
|
||||
|
||||
# now exit if there's no base64 as i think that's the most fucky
|
||||
if not has_base64:
|
||||
return decoded_bytes_to_return_value(decoded_bytes)
|
||||
|
||||
# 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:
|
||||
"""
|
||||
change decoded_bytes such that part is decoded if base64 (unchanged if
|
||||
not)
|
||||
|
||||
see usage below for examples
|
||||
|
||||
:param part: email.message.Message to be decoded
|
||||
:param decoded_bytes: the email as a bytes object (ie not a string with
|
||||
encoding), will have modified version returned
|
||||
:param most_recent_boundary: str of the most recent boundary so we
|
||||
don't overwrite this
|
||||
:return: bytes of decoded_bytes with part decoded if base64
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
return decoded_bytes
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
return decoded_bytes_to_return_value(decoded_bytes)
|
||||
|
||||
def set_email_header(
|
||||
message: email.message.Message,
|
||||
name: str,
|
||||
|
@ -79,24 +197,6 @@ def set_email_header(
|
|||
else:
|
||||
message.add_header(name, value)
|
||||
|
||||
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"""
|
||||
if message.is_multipart():
|
||||
for part in message.walk():
|
||||
if part.get('Content-Transfer-Encoding') in ('quoted-printable', 'base64'):
|
||||
set_email_header(part, 'Content-Transfer-Encoding', '7bit')
|
||||
part.set_payload(part.get_payload(decode=True))
|
||||
else:
|
||||
if message.get('Content-Transfer-Encoding') in ('quoted-printable', 'base64'):
|
||||
set_email_header(message, 'Content-Transfer-Encoding', '7bit')
|
||||
message.set_payload(message.get_payload(decode=True))
|
||||
|
||||
return message
|
||||
|
||||
def encrypt(
|
||||
message: email.message.Message,
|
||||
recipients: typing.List[str],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue