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

@@ -267,7 +267,7 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
self.assertEqual(sent_message.html, html_content)
inlines = sent_message.inline_attachments
inlines = sent_message.content_id_map
self.assertEqual(len(inlines), 1)
self.assertEqual(inlines[cid].get_content_type(), "image/png")
self.assertEqual(inlines[cid].get_filename(), image_filename)

View File

@@ -274,7 +274,7 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
self.assertEqual(sent_message.html, html_content)
inlines = sent_message.inline_attachments
inlines = sent_message.content_id_map
self.assertEqual(len(inlines), 1)
self.assertEqual(inlines[cid].get_content_type(), "image/png")
self.assertEqual(inlines[cid].get_filename(), image_filename)

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)

View File

@@ -323,7 +323,7 @@ class MailerSendInboundTestCase(MailerSendWebhookTestCase):
],
)
inlines = message.inline_attachments
inlines = message.content_id_map
self.assertEqual(len(inlines), 1)
inline = inlines["ii_letc8ro50"]
self.assertEqual(inline.get_filename(), "sample_image.png")

View File

@@ -182,7 +182,7 @@ class MailgunInboundTestCase(WebhookTestCase):
attachments[1].get_content_bytes(), email_content
)
inlines = message.inline_attachments
inlines = message.content_id_map
self.assertEqual(len(inlines), 1)
inline = inlines["abc123"]
self.assertEqual(inline.get_filename(), "image.png")
@@ -266,7 +266,7 @@ class MailgunInboundTestCase(WebhookTestCase):
event = kwargs["event"]
message = event.message
self.assertEqual(len(message.attachments), 0) # all inlines
inlines = [part for part in message.walk() if part.is_inline_attachment()]
inlines = [part for part in message.walk() if part.is_inline()]
self.assertEqual(len(inlines), 4)
self.assertEqual(inlines[0]["Content-ID"], "")
self.assertEqual(inlines[1]["Content-ID"], "")

View File

@@ -205,7 +205,7 @@ class MailjetInboundTestCase(WebhookTestCase):
attachments[1].get_content_bytes(), email_content
)
inlines = message.inline_attachments
inlines = message.content_id_map
self.assertEqual(len(inlines), 1)
inline = inlines["abc123"]
self.assertEqual(inline.get_filename(), "image.png")

View File

@@ -204,7 +204,7 @@ class PostalInboundTestCase(WebhookTestCase):
attachments[1].get_content_bytes(), email_content
)
inlines = message.inline_attachments
inlines = message.content_id_map
self.assertEqual(len(inlines), 1)
inline = inlines["abc123"]
self.assertEqual(inline.get_filename(), "image.png")

View File

@@ -191,7 +191,7 @@ class PostmarkInboundTestCase(WebhookTestCase):
attachments[1].get_content_bytes(), email_content
)
inlines = message.inline_attachments
inlines = message.content_id_map
self.assertEqual(len(inlines), 1)
inline = inlines["abc123"]
self.assertEqual(inline.get_filename(), "image.png")

View File

@@ -170,7 +170,7 @@ class SendgridInboundTestCase(WebhookTestCase):
attachments[1].get_content_bytes(), email_content
)
inlines = message.inline_attachments
inlines = message.content_id_map
self.assertEqual(len(inlines), 1)
inline = inlines["abc123"]
self.assertEqual(inline.get_filename(), "image.png")

View File

@@ -205,7 +205,7 @@ class SparkpostInboundTestCase(WebhookTestCase):
self.assertEqual(att_message["Subject"], "Test email")
self.assertEqual(att_message.text, "Hi Bob, This is a message. Thanks!\n")
inlines = message.inline_attachments
inlines = message.content_id_map
self.assertEqual(len(inlines), 1)
inline = inlines["abc123"]
self.assertEqual(inline.get_filename(), "image.png")