Inbound: improve inline content handling

* refactor: derive `AnymailInboundMessage` from `email.message.EmailMessage`
  rather than legacy Python 2.7 `email.message.Message`

* feat(inbound): replace confusing `inline_attachments` with `content_id_map`
  and `inlines`; rename `is_inline_attachment` to `is_inline`; deprecate old names 

Closes #328

---------

Co-authored-by: Mike Edmunds <medmunds@gmail.com>
This commit is contained in:
Léo Martinez
2023-07-28 00:10:58 +02:00
committed by GitHub
parent bc8ef9af0f
commit 0ac248254e
13 changed files with 212 additions and 60 deletions

View File

@@ -6,6 +6,7 @@ from textwrap import dedent
from django.core.mail import SafeMIMEText
from django.test import SimpleTestCase
from anymail.exceptions import AnymailDeprecationWarning
from anymail.inbound import AnymailInboundMessage
from .utils import SAMPLE_IMAGE_FILENAME, sample_email_path, sample_image_content
@@ -200,7 +201,7 @@ class AnymailInboundMessageConstructionTests(SimpleTestCase):
content_id="inline-id",
)
self.assertEqual(att.get_filename(), "Simulácia.txt")
self.assertTrue(att.is_inline_attachment())
self.assertTrue(att.is_inline())
self.assertEqual(att.get_content_text(), "Unicode ✓")
def test_parse_raw_mime(self):
@@ -446,7 +447,7 @@ class AnymailInboundMessageConveniencePropTests(SimpleTestCase):
# Default empty list
self.assertEqual(AnymailInboundMessage().attachments, [])
def test_inline_attachments_prop(self):
def test_content_id_map_prop(self):
att = AnymailInboundMessage.construct_attachment(
"image/png",
SAMPLE_IMAGE_CONTENT,
@@ -455,10 +456,64 @@ class AnymailInboundMessageConveniencePropTests(SimpleTestCase):
)
msg = AnymailInboundMessage.construct(attachments=[att])
self.assertEqual(msg.inline_attachments, {"abc123": att})
self.assertEqual(msg.content_id_map, {"abc123": att})
with self.assertWarnsMessage(
AnymailDeprecationWarning,
"inline_attachments has been renamed to content_id_map and will be removed "
"in the near future.",
):
self.assertEqual(msg.inline_attachments, {"abc123": att})
# Default empty dict
self.assertEqual(AnymailInboundMessage().inline_attachments, {})
self.assertEqual(AnymailInboundMessage().content_id_map, {})
def test_inlines_prop(self):
raw = dedent(
"""\
MIME-Version: 1.0
Subject: Message with inline parts
Content-Type: multipart/mixed; boundary="boundary-orig"
--boundary-orig
Content-Type: text/html; charset="UTF-8"
<img src="cid:abc123"> Here is a message!
--boundary-orig
Content-Type: image/png; name="sample_image.png"
Content-Disposition: inline
Content-ID: <abc123>
Content-Transfer-Encoding: base64
{image_content_base64}
--boundary-orig
Content-Type: image/png; name="sample_image_without_cid.png"
Content-Disposition: inline
Content-Transfer-Encoding: base64
{image_content_base64}
--boundary-orig--
"""
).format(image_content_base64=b64encode(SAMPLE_IMAGE_CONTENT).decode("ascii"))
msg = AnymailInboundMessage.parse_raw_mime(raw)
inlines = msg.inlines
self.assertEqual(len(inlines), 2)
self.assertEqual(inlines[0].get_content_type(), "image/png")
self.assertEqual(inlines[0].as_uploaded_file().name, "sample_image.png")
self.assertEqual(inlines[1].get_content_type(), "image/png")
self.assertEqual(
inlines[1].as_uploaded_file().name, "sample_image_without_cid.png"
)
self.assertEqual(len(msg.content_id_map.items()), 1)
self.assertIn("abc123", msg.content_id_map)
def test_attachment_as_uploaded_file(self):
raw = dedent(
@@ -609,9 +664,16 @@ class AnymailInboundMessageAttachedMessageTests(SimpleTestCase):
orig_inline_att = orig_msg.get_payload(1)
self.assertEqual(orig_inline_att.get_content_type(), "image/png")
self.assertTrue(orig_inline_att.is_inline_attachment())
self.assertTrue(orig_inline_att.is_inline())
self.assertEqual(orig_inline_att.get_payload(decode=True), SAMPLE_IMAGE_CONTENT)
with self.assertWarnsMessage(
AnymailDeprecationWarning,
"is_inline_attachment has been renamed to is_inline and will be removed in "
"the near future.",
):
self.assertTrue(orig_inline_att.is_inline_attachment())
def test_construct_rfc822_attachment_from_data(self):
# constructed message/rfc822 attachment should end up as parsed message
# (same as if attachment was parsed from raw mime, as in previous test)