From 61cf50effe1fa85649281c65f5ecfbb3215a05ce Mon Sep 17 00:00:00 2001 From: "Piotr F. Mieszkowski" Date: Sat, 22 Apr 2023 18:08:19 +0200 Subject: [PATCH] Fix MIME content sub-type handling for non-plain text messages --- lacre/core.py | 29 +++++++++++++++++------------ lacre/recipients.py | 6 ++++-- test/modules/test_contracts.py | 8 ++++++++ test/modules/test_lacre_core.py | 6 ++++-- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/lacre/core.py b/lacre/core.py index 45a3c39..35f345d 100644 --- a/lacre/core.py +++ b/lacre/core.py @@ -179,35 +179,42 @@ def _encrypt_all_payloads_mime(message: EmailMessage, gpg_to_cmdline): boundary = _make_boundary() if isinstance(message.get_payload(), str): + LOG.debug('Rewrapping a flat, text-only message') wrapped_payload = _rewrap_payload(message) encrypted_part.set_payload(wrapped_payload.as_string()) _set_type_and_boundary(message, boundary) - return [pgp_ver_part, _encrypt_payload(encrypted_part, gpg_to_cmdline, True)] + check_nested = True else: processed_payloads = _generate_message_from_payloads(message) encrypted_part.set_payload(processed_payloads.as_string()) _set_type_and_boundary(message, boundary) - return [pgp_ver_part, _encrypt_payload(encrypted_part, gpg_to_cmdline, False)] + check_nested = False + + return [pgp_ver_part, _encrypt_payload(encrypted_part, gpg_to_cmdline, check_nested)] def _rewrap_payload(message: EmailMessage) -> MIMEPart: # In PGP/MIME (RFC 3156), the payload has to be a valid MIME entity. In - # other words, we need to wrap text/plain message's payload in a new MIME + # other words, we need to wrap text/* message's payload in a new MIME # entity. - pld = MIMEPart() - pld.set_type(message.get_content_type()) - pld.set_content(message.get_content()) + wrapper = MIMEPart(policy=SMTPUTF8) + content = message.get_content() + wrapper.set_content(content) - # Make sure all Content-Type parameters are included. - for (k, v) in message.get_params(): - pld.set_param(k, v) + wrapper.set_type(message.get_content_type()) - return pld + # Copy all Content-Type parameters. + for (pname, pvalue) in message.get_params(): + # Skip MIME type that's also returned by get_params(). + if not '/' in pname: + wrapper.set_param(pname, pvalue) + + return wrapper def _make_boundary(): @@ -242,8 +249,6 @@ def _encrypt_payload(payload: EmailMessage, recipients, check_nested=True, **kwa if isAttachment: _append_gpg_extension(payload) - if not (payload.get('Content-Transfer-Encoding') is None): - payload.replace_header('Content-Transfer-Encoding', "7bit") return payload diff --git a/lacre/recipients.py b/lacre/recipients.py index 082ebd9..e2c97b9 100644 --- a/lacre/recipients.py +++ b/lacre/recipients.py @@ -90,12 +90,10 @@ class RecipientList: def emails(self): """Return list of recipients.""" - LOG.debug('Recipient emails from: %s', self._recipients) return [r.email() for r in self._recipients] def keys(self): """Return list of GPG identities.""" - LOG.debug('Recipient keys from: %s', self._recipients) return [r.key() for r in self._recipients] def __iadd__(self, recipient: GpgRecipient): @@ -116,6 +114,10 @@ class RecipientList: """ return len(self._recipients) + def __repr__(self): + """Returns textual object representation.""" + return '' % (len(self._recipients), ','.join(self.emails())) + def identify_gpg_recipients(recipients, keys: kcache.KeyCache): """Split recipient list into GPG and non-GPG ones.""" diff --git a/test/modules/test_contracts.py b/test/modules/test_contracts.py index ef63233..458e142 100644 --- a/test/modules/test_contracts.py +++ b/test/modules/test_contracts.py @@ -173,6 +173,14 @@ class EmailTest(unittest.TestCase): _ = mp.as_string() self.assertFalse(mp.get_boundary() is None) + def test_content_type_params_include_mime_type(self): + p = email.message.MIMEPart() + p.set_type('text/plain') + p.set_param('charset', 'UTF-8') + p.set_param('format', 'flowed') + + self.assertIn(('text/plain', ''), p.get_params()) + class RawConfigParserTest(unittest.TestCase): def test_config_parser_returns_str(self): diff --git a/test/modules/test_lacre_core.py b/test/modules/test_lacre_core.py index 0829c4b..5591308 100644 --- a/test/modules/test_lacre_core.py +++ b/test/modules/test_lacre_core.py @@ -36,5 +36,7 @@ class LacreCoreTest(unittest.TestCase): rewrapped = lacre.core._rewrap_payload(m) - self.assertFalse('Subject' in rewrapped) - self.assertEqual(rewrapped.get_content_type(), m.get_content_type()) + self.assertFalse('Subject' in rewrapped, + 'only content and content-type should be copied') + self.assertEqual(rewrapped.get_content_type(), 'text/plain', + 'rewrapped part should have initial message\'s content-type')