gpgmymail: remove all 'decoding' code
This commit is contained in:
parent
e0c200c95e
commit
f7bb04e5ec
1 changed files with 0 additions and 170 deletions
170
gpgmymail
170
gpgmymail
|
@ -40,15 +40,12 @@ import email.mime.application
|
||||||
import email.mime.multipart
|
import email.mime.multipart
|
||||||
import email.mime.message
|
import email.mime.message
|
||||||
import typing
|
import typing
|
||||||
# for decode_email:
|
|
||||||
import quopri
|
|
||||||
|
|
||||||
# see: https://gnupg.readthedocs.io/en/latest/
|
# see: https://gnupg.readthedocs.io/en/latest/
|
||||||
import gnupg
|
import gnupg
|
||||||
|
|
||||||
# constants
|
# constants
|
||||||
DEFAULT_ENCODING='utf-8' # default is latin-1 which fails w some unicode chars
|
DEFAULT_ENCODING='utf-8' # default is latin-1 which fails w some unicode chars
|
||||||
PEP_SUBJECT='=?utf-8?Q?p=E2=89=A1p?='
|
|
||||||
|
|
||||||
def is_message_encrypted(message: email.message.Message) -> bool:
|
def is_message_encrypted(message: email.message.Message) -> bool:
|
||||||
"""Determines whether or not an email message is encrypted.
|
"""Determines whether or not an email message is encrypted.
|
||||||
|
@ -58,125 +55,6 @@ def is_message_encrypted(message: email.message.Message) -> bool:
|
||||||
|
|
||||||
return message.get_content_subtype() == "encrypted"
|
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 = is_base64_text(message) # we don't want to decode non-text
|
|
||||||
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) and text (so will not decode base64 images etc)
|
|
||||||
|
|
||||||
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" and \
|
|
||||||
part.get_content_maintype() == "text":
|
|
||||||
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(
|
def set_email_header(
|
||||||
message: email.message.Message,
|
message: email.message.Message,
|
||||||
name: str,
|
name: str,
|
||||||
|
@ -198,25 +76,6 @@ def set_email_header(
|
||||||
else:
|
else:
|
||||||
message.add_header(name, value)
|
message.add_header(name, value)
|
||||||
|
|
||||||
def is_base64_text(message: email.message.Message) -> bool:
|
|
||||||
"""
|
|
||||||
Return whether or not there is base64-encoded text in a multipart
|
|
||||||
message, i.e. will be False if it's only e.g. images that are encoded as
|
|
||||||
base64.
|
|
||||||
|
|
||||||
:param message: the email Message to check
|
|
||||||
:return: True if there is a text part that's base64 encoded, False if not
|
|
||||||
"""
|
|
||||||
if message.is_multipart():
|
|
||||||
for part in message.walk():
|
|
||||||
if part.get('Content-Transfer-Encoding') == "base64" and \
|
|
||||||
part.get_content_maintype() == "text":
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return message.get('Content-Transfer-Encoding') == "base64" and \
|
|
||||||
message.get_content_maintype() == "text"
|
|
||||||
|
|
||||||
def encrypt(
|
def encrypt(
|
||||||
message: email.message.Message,
|
message: email.message.Message,
|
||||||
recipients: typing.List[str],
|
recipients: typing.List[str],
|
||||||
|
@ -224,7 +83,6 @@ def encrypt(
|
||||||
unconditionally_encrypt: bool = False,
|
unconditionally_encrypt: bool = False,
|
||||||
encoding: str = DEFAULT_ENCODING,
|
encoding: str = DEFAULT_ENCODING,
|
||||||
ignore_errors: bool = False,
|
ignore_errors: bool = False,
|
||||||
decode_before_encrypting: bool = False
|
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Encrypt given message
|
"""Encrypt given message
|
||||||
|
|
||||||
|
@ -234,17 +92,7 @@ def encrypt(
|
||||||
False (default), will NOT encrypt if any of the following conditions are
|
False (default), will NOT encrypt if any of the following conditions are
|
||||||
met:
|
met:
|
||||||
- The message is already encrypted
|
- The message is already encrypted
|
||||||
- The message is encoded with base64
|
|
||||||
:param encoding: string for encoding to use for the gnupg.GPG object
|
:param encoding: string for encoding to use for the gnupg.GPG object
|
||||||
:param ignore_errors: bool, puts some parts of the function in
|
|
||||||
|
|
||||||
try:
|
|
||||||
do_stuff()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
blocks. find ignore_errors to see instances where this occurs
|
|
||||||
|
|
||||||
:return: The encrypted email as a string"""
|
:return: The encrypted email as a string"""
|
||||||
|
|
||||||
# mark the email as having passed through us
|
# mark the email as having passed through us
|
||||||
|
@ -256,21 +104,6 @@ def encrypt(
|
||||||
if not unconditionally_encrypt:
|
if not unconditionally_encrypt:
|
||||||
if is_message_encrypted(message):
|
if is_message_encrypted(message):
|
||||||
return message.as_string()
|
return message.as_string()
|
||||||
# bc i just have a bunch of issues w b64
|
|
||||||
if is_base64_text(message):
|
|
||||||
return message.as_string()
|
|
||||||
|
|
||||||
# make necessary changes to message
|
|
||||||
# this function is quite clunky and seems to throw exceptions from time to
|
|
||||||
# time; if we can't make the necessary changes we want to just continue
|
|
||||||
if decode_before_encrypting:
|
|
||||||
try:
|
|
||||||
message = decode_email(message)
|
|
||||||
except Exception as e:
|
|
||||||
if ignore_errors:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
gpg = gnupg.GPG()
|
gpg = gnupg.GPG()
|
||||||
gpg.encoding = encoding
|
gpg.encoding = encoding
|
||||||
|
@ -333,8 +166,6 @@ def main() -> None:
|
||||||
help="Encrypt mail unconditionally. By default, mail is not encrypted if it is already encrypted.")
|
help="Encrypt mail unconditionally. By default, mail is not encrypted if it is already encrypted.")
|
||||||
parser.add_argument('--ignore-errors', action="store_true",
|
parser.add_argument('--ignore-errors', action="store_true",
|
||||||
help="Ignore errors at certain error-prone points of the script.")
|
help="Ignore errors at certain error-prone points of the script.")
|
||||||
parser.add_argument('--decode', action="store_true",
|
|
||||||
help="Attempt to decode quoted-printable and base64 parts to latin-1 before encrypting a message")
|
|
||||||
parser.add_argument('recipient', nargs='*',
|
parser.add_argument('recipient', nargs='*',
|
||||||
help="Key ID or email of keys to encrypt for")
|
help="Key ID or email of keys to encrypt for")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
@ -349,7 +180,6 @@ def main() -> None:
|
||||||
unconditionally_encrypt=args.unconditional,
|
unconditionally_encrypt=args.unconditional,
|
||||||
encoding=args.encoding,
|
encoding=args.encoding,
|
||||||
ignore_errors=args.ignore_errors,
|
ignore_errors=args.ignore_errors,
|
||||||
decode_before_encrypting=args.decode
|
|
||||||
))
|
))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in a new issue