diff --git a/helpdesk/email.py b/helpdesk/email.py index f27dc969..15e27f54 100644 --- a/helpdesk/email.py +++ b/helpdesk/email.py @@ -687,8 +687,8 @@ def extract_part_data( name = f"part-{counter}{ext}" else: name = f"part-{counter}_{name}" - - files.append(SimpleUploadedFile(name, part.get_payload(decode=True), mimetypes.guess_type(name)[0])) + payload = part.as_string() if part.is_multipart() else part.get_payload(decode=True) + files.append(SimpleUploadedFile(name, payload, mimetypes.guess_type(name)[0])) logger.debug("Found MIME attachment %s", name) return part_body, part_full_body diff --git a/helpdesk/tests/test_get_email.py b/helpdesk/tests/test_get_email.py index 3a5bccc7..be1860ae 100644 --- a/helpdesk/tests/test_get_email.py +++ b/helpdesk/tests/test_get_email.py @@ -12,7 +12,7 @@ import helpdesk.email from helpdesk.email import extract_part_data, object_from_message from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException 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 import itertools import logging @@ -230,6 +230,29 @@ class GetEmailCommonTests(TestCase): followup = ticket.followup_set.get() 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): """TestCase that checks basic email functionality across methods and socks configs.""" diff --git a/helpdesk/tests/utils.py b/helpdesk/tests/utils.py index f2822732..f93d715a 100644 --- a/helpdesk/tests/utils.py +++ b/helpdesk/tests/utils.py @@ -141,14 +141,15 @@ def generate_email_address( # format email address for RFC 2822 and return 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 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.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) if not filename: filename = get_fake("word", locale=locale, min_length=8) + ".txt" @@ -227,7 +228,7 @@ def generate_mime_part(locale: str="en_US", return msg 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 ) -> typing.Tuple[Message, typing.Tuple[str, str], typing.Tuple[str, str]]: """ diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index 7d33f6a9..8509f487 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -10,7 +10,7 @@ from ..lib import format_time_spent from ..templated_email import send_templated_mail from collections import defaultdict from copy import deepcopy -from datetime import date, datetime, timedelta +from datetime import datetime, timedelta from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.decorators import user_passes_test @@ -24,7 +24,7 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonRespons from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse, reverse_lazy from django.utils import timezone -from django.utils.dateparse import parse_datetime +from django.utils.dateparse import parse_date, parse_datetime from django.utils.html import escape from django.utils.timezone import make_aware from django.utils.translation import gettext as _ @@ -521,7 +521,8 @@ def get_due_date_from_request_or_ticket( due_date = request.POST.get('due_date', None) or None if due_date is not None: - due_date = make_aware(parse_datetime(due_date)) + parsed_date = parse_datetime(due_date) or datetime.combine(parse_date(due_date), time()) + due_date = make_aware(parsed_date) else: due_date_year = int(request.POST.get('due_date_year', 0)) due_date_month = int(request.POST.get('due_date_month', 0))