Merge pull request #1080 from django-helpdesk/support_for_email_with_attached_multipart

Support for email with attached multipart
This commit is contained in:
Christopher Broderick 2023-03-25 23:02:21 +00:00 committed by GitHub
commit 40175a03d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 31 additions and 7 deletions

View File

@ -687,8 +687,8 @@ def extract_part_data(
name = f"part-{counter}{ext}" name = f"part-{counter}{ext}"
else: else:
name = f"part-{counter}_{name}" name = f"part-{counter}_{name}"
payload = part.as_string() if part.is_multipart() else part.get_payload(decode=True)
files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0])) files.append(SimpleUploadedFile(name, payload, mimetypes.guess_type(name)[0]))
logger.debug("Found MIME attachment %s", name) logger.debug("Found MIME attachment %s", name)
return part_body, part_full_body return part_body, part_full_body

View File

@ -12,7 +12,7 @@ import helpdesk.email
from helpdesk.email import extract_part_data, object_from_message from helpdesk.email import extract_part_data, object_from_message
from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException
from helpdesk.management.commands.get_email import Command from helpdesk.management.commands.get_email import Command
from helpdesk.models import FollowUp, FollowUpAttachment, IgnoreEmail, Queue, Ticket, TicketCC from helpdesk.models import Attachment, FollowUp, FollowUpAttachment, IgnoreEmail, Queue, Ticket, TicketCC
from helpdesk.tests import utils from helpdesk.tests import utils
import itertools import itertools
import logging import logging
@ -231,6 +231,29 @@ class GetEmailCommonTests(TestCase):
followup = ticket.followup_set.get() followup = ticket.followup_set.get()
self.assertEqual(1, followup.followupattachment_set.count()) self.assertEqual(1, followup.followupattachment_set.count())
def test_email_with_multipart_as_attachment(self):
"""
Is a multipart attachment to an email correctly saved as an attachment
"""
att_filename = 'email_attachment.eml'
message, _, _ = utils.generate_multipart_email(type_list=['plain', 'html'])
email_attachment, _, _ = utils.generate_multipart_email(type_list=['plain', 'html'])
att_content = email_attachment.as_string()
message.attach(utils.generate_file_mime_part(filename=att_filename, content=att_content))
object_from_message(message.as_string(), self.queue_public, self.logger)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(f'[test-1] {message.get("subject")} (Opened)', mail.outbox[0].subject)
ticket = Ticket.objects.get()
followup = ticket.followup_set.get()
att_retrieved: Attachment = followup.followupattachment_set.get()
self.assertTrue(att_retrieved.filename.endswith(att_filename), "Filename of attached multipart not detected: %s" % (att_retrieved.filename))
with att_retrieved.file.open('r') as f:
retrieved_content = f.read()
self.assertEquals(att_content, retrieved_content, "Retrieved attachment content different to original :\n\n%s\n\n%s" % (att_content, retrieved_content))
class GetEmailParametricTemplate(object): class GetEmailParametricTemplate(object):
"""TestCase that checks basic email functionality across methods and socks configs.""" """TestCase that checks basic email functionality across methods and socks configs."""

View File

@ -141,14 +141,15 @@ def generate_email_address(
# format email address for RFC 2822 and return # format email address for RFC 2822 and return
return email.utils.formataddr((real_name, email_address)), email_address, first_name, last_name return email.utils.formataddr((real_name, email_address)), email_address, first_name, last_name
def generate_file_mime_part(locale: str="en_US",filename: str = None) -> Message: def generate_file_mime_part(locale: str="en_US",filename: str = None, content: str = None) -> Message:
""" """
:param locale: change this to generate locale specific file name and attachment content :param locale: change this to generate locale specific file name and attachment content
:param filename: pass a file name if you want to specify a specific name otherwise a random name will be generated :param filename: pass a file name if you want to specify a specific name otherwise a random name will be generated
:param content: pass a string value if you want have specific content otherwise a random string will be generated
""" """
part = MIMEBase('application', 'octet-stream') part = MIMEBase('application', 'octet-stream')
part.set_payload(get_fake("text", locale=locale, min_length=1024)) part.set_payload(get_fake("text", locale=locale, min_length=1024) if content is None else content)
encoders.encode_base64(part) encoders.encode_base64(part)
if not filename: if not filename:
filename = get_fake("word", locale=locale, min_length=8) + ".txt" filename = get_fake("word", locale=locale, min_length=8) + ".txt"
@ -227,7 +228,7 @@ def generate_mime_part(locale: str="en_US",
return msg return msg
def generate_multipart_email(locale: str="en_US", def generate_multipart_email(locale: str="en_US",
type_list: typing.List[str]=["plain", "html", "attachment"], type_list: typing.List[str]=["plain", "html", "image"],
use_short_email: bool=False use_short_email: bool=False
) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]: ) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]:
""" """

View File

@ -10,7 +10,7 @@ from ..lib import format_time_spent
from ..templated_email import send_templated_mail from ..templated_email import send_templated_mail
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from datetime import date, datetime, timedelta from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test