From efc4e6df11155eee2a3368274bab31833c1bd6fd Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Tue, 31 Mar 2026 20:18:48 +0300 Subject: [PATCH 1/2] Skip data URIs in transformer to prevent broken attachments Closes #62 --- .../testsuite/transformer/test_transformer.py | 20 +++++++++++++++++++ emails/transformer.py | 3 +++ 2 files changed, 23 insertions(+) diff --git a/emails/testsuite/transformer/test_transformer.py b/emails/testsuite/transformer/test_transformer.py index 5e82202..eb93d0d 100644 --- a/emails/testsuite/transformer/test_transformer.py +++ b/emails/testsuite/transformer/test_transformer.py @@ -66,6 +66,26 @@ def test_tag_attribute(): assert m3.attachments['1.jpg'].content_disposition == "inline" +def test_data_uri_preserved(): + DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + + # data URIs in img src should be left untouched + html = '' % DATA_URI + m = emails.loader.from_string(html=html) + assert len(m.attachments.keys()) == 0 + assert DATA_URI in m.html + + # data URIs in css url() should be left untouched + html = '
x
' % DATA_URI + m = emails.loader.from_string(html=html) + assert len(m.attachments.keys()) == 0 + + # data URIs in background attribute should be left untouched + html = '' % DATA_URI + m = emails.loader.from_string(html=html) + assert len(m.attachments.keys()) == 0 + + ROOT = os.path.dirname(__file__) def test_local_premailer(): diff --git a/emails/transformer.py b/emails/transformer.py index 3e1c299..b844e9b 100644 --- a/emails/transformer.py +++ b/emails/transformer.py @@ -242,6 +242,9 @@ def _load_attachment_func(self, uri, element=None, callback=None, **kw): # Return local uri # + if uri.startswith('data:'): + return uri + if callback is None: # Default callback: skip images with data-emails="ignore" attribute callback = lambda _, hints: hints['attrib'] != 'ignore' From f71c0dc959c9ba841045274c67c59c5a88e79964 Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Tue, 31 Mar 2026 20:24:45 +0300 Subject: [PATCH 2/2] Handle mixed-case data URI schemes --- emails/testsuite/transformer/test_transformer.py | 6 ++++++ emails/transformer.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/emails/testsuite/transformer/test_transformer.py b/emails/testsuite/transformer/test_transformer.py index eb93d0d..be3c4dd 100644 --- a/emails/testsuite/transformer/test_transformer.py +++ b/emails/testsuite/transformer/test_transformer.py @@ -85,6 +85,12 @@ def test_data_uri_preserved(): m = emails.loader.from_string(html=html) assert len(m.attachments.keys()) == 0 + # mixed-case scheme should also be preserved + mixed = DATA_URI.replace('data:', 'Data:') + html = '' % mixed + m = emails.loader.from_string(html=html) + assert len(m.attachments.keys()) == 0 + ROOT = os.path.dirname(__file__) diff --git a/emails/transformer.py b/emails/transformer.py index b844e9b..21d6e20 100644 --- a/emails/transformer.py +++ b/emails/transformer.py @@ -242,7 +242,7 @@ def _load_attachment_func(self, uri, element=None, callback=None, **kw): # Return local uri # - if uri.startswith('data:'): + if uri[:5].lower() == 'data:': return uri if callback is None: