Skip to content

Commit

Permalink
feature: base64 image convert to attachments
Browse files Browse the repository at this point in the history
fix: removed <>

fix: removing non alphanumeric characters

fix: use inline type for disposition
  • Loading branch information
jrbenriquez committed Feb 4, 2024
1 parent 83c65b5 commit 1cb98ef
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 1 deletion.
10 changes: 9 additions & 1 deletion django_email_verification/confirm.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from .errors import InvalidUserModel, NotAllFieldCompiled
from .token_utils import default_token_generator
from .utils import convert_base64_images

logger = logging.getLogger('django_email_verification')
DJANGO_EMAIL_VERIFICATION_MORE_VIEWS_ERROR = 'ERROR: more than one verify view found'
Expand Down Expand Up @@ -81,13 +82,20 @@ def has_decorator(k):
if not validators.url(context['link']):
logger.warning(f'{DJANGO_EMAIL_VERIFICATION_MALFORMED_URL} - {context["link"]}')

do_convert_base64_images = _get_validated_field(f'EMAIL_CONVERT_BASE64_IMAGES', default=False, use_default=True, default_type=bool)

attachments = []
if do_convert_base64_images:
# Look for inline base64 images and converts them to attachments for email providers that require them i.e. Gmail
mail_html, attachments = convert_base64_images(mail_html, attachments)

subject = Template(subject).render(Context(context))

text = render_to_string(mail_plain, context)

html = render_to_string(mail_html, context)

msg = EmailMultiAlternatives(subject, text, sender, [user.email])
msg = EmailMultiAlternatives(subject, text, sender, [user.email], attachments=attachments)

if debug:
msg.extra_headers['LINK'] = context['link']
Expand Down
51 changes: 51 additions & 0 deletions django_email_verification/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

import base64
import hashlib
import random
import re
import string

from email.mime.image import MIMEImage

def random_string(length, case="lowercase"):
return "".join(random.choices(getattr(string, f"ascii_{case}") + string.digits, k=length))

def convert_base64_images(body, attachments):

def repl(match):
# Capture subtype in case MIMEImage's use of imghdr.what bugs out in guesesing the file type
subtype = match.group("subtype")
key = hashlib.md5(base64.b64decode(match.group("data"))).hexdigest().replace("-", "")
if key not in base64_images:
base64_images[key] = {
"data": match.group("data"),
"subtype": subtype,
}
return ' src="cid:image-%s"' % key

# Compile pattern for base64 inline images
RE_BASE64_SRC = re.compile(
r' src="data:image/(?P<subtype>gif|png|jpeg|bmp|webp)(?:;charset=utf-8)?;base64,(?P<data>[A-Za-z0-9|+ /]+={0,2})"',
re.MULTILINE)

base64_images = {}

# Replace and add base64 data to base64_images via repl
body = re.sub(RE_BASE64_SRC, repl, body)
for key, image_data in base64_images.items():
try:
image = MIMEImage(base64.b64decode(image_data["data"]))
except TypeError:
# Check for subtype if checking fails
if image_data["subtype"]:
image = MIMEImage(
base64.b64decode(image_data["data"]),
_subtype=image_data["subtype"]
)
else:
raise
image.add_header('Content-ID', '<image-%s>' % key)
image.add_header('Content-Disposition', "inline; filename=%s" % f'image_{random_string(length=6)}')
attachments.append(image)

return body, attachments

0 comments on commit 1cb98ef

Please sign in to comment.