From bb5874d5a1a4b3825be44efeef32a1e0ed668bb8 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 20 Apr 2016 08:43:26 -0400 Subject: [PATCH] truncate email from addresses if >320 chars when encoded (TNL-4264) (#12171) * truncate email from addresses if >320 chars when encoded (TNL-4264) * use exact lengths --- lms/djangoapps/bulk_email/tasks.py | 21 +++++++++++----- lms/djangoapps/bulk_email/tests/test_email.py | 25 +++++++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index 1c894be49c..4ec53f3778 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -32,6 +32,7 @@ from celery.exceptions import RetryTaskError # pylint: disable=no-name-in-modul from django.conf import settings from django.contrib.auth.models import User from django.core.mail import EmailMultiAlternatives, get_connection +from django.core.mail.message import forbid_multi_line_headers from django.core.urlresolvers import reverse from bulk_email.models import ( @@ -384,13 +385,20 @@ def _filter_optouts_from_recipients(to_list, course_id): return to_list, num_optout -def _get_source_address(course_id, course_title): +def _get_source_address(course_id, course_title, truncate=True): """ Calculates an email address to be used as the 'from-address' for sent emails. Makes a unique from name and address for each course, e.g. - "COURSE_TITLE" Course Staff + "COURSE_TITLE" Course Staff + + If, when decoded to ascii, this from_addr is longer than 320 characters, + use the course_name rather than the course title, e.g. + + "course_name" Course Staff + + The "truncate" kwarg is only used for tests. """ course_title_no_quotes = re.sub(r'"', '', course_title) @@ -418,10 +426,11 @@ def _get_source_address(course_id, course_title): from_addr = format_address(course_title_no_quotes) - # If it's longer than 320 characters, reformat, but with the course name - # rather than course title. Amazon SES's from address field appears to have a maximum - # length of 320. - if len(from_addr) >= 320: + # If the encoded from_addr is longer than 320 characters, reformat, + # but with the course name rather than course title. + # Amazon SES's from address field appears to have a maximum length of 320. + __, encoded_from_addr = forbid_multi_line_headers('from', from_addr, 'utf-8') + if len(encoded_from_addr) >= 320 and truncate: from_addr = format_address(course_name) return from_addr diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py index d1a32e641d..e772dccad2 100644 --- a/lms/djangoapps/bulk_email/tests/test_email.py +++ b/lms/djangoapps/bulk_email/tests/test_email.py @@ -10,11 +10,13 @@ from unittest import skipIf from django.conf import settings from django.core import mail +from django.core.mail.message import forbid_multi_line_headers from django.core.urlresolvers import reverse from django.core.management import call_command from django.test.utils import override_settings from bulk_email.models import Optout +from bulk_email.tasks import _get_source_address from courseware.tests.factories import StaffFactory, InstructorFactory from instructor_task.subtasks import update_subtask_status from student.roles import CourseStaffRole @@ -322,13 +324,24 @@ class TestEmailSendFromDashboardMockedHtmlToText(EmailSendFromDashboardTestCase) 'message': 'test message for self' } - # make very long display_name for course - long_name = u"x" * 321 + # make display_name that's longer than 320 characters when encoded + # to ascii, but shorter than 320 unicode characters + long_name = u"é" * 200 + course = CourseFactory.create( display_name=long_name, number="bulk_email_course_name" ) instructor = InstructorFactory(course_key=course.id) + unexpected_from_addr = _get_source_address( + course.id, course.display_name, truncate=False + ) + __, encoded_unexpected_from_addr = forbid_multi_line_headers( + "from", unexpected_from_addr, 'utf-8' + ) + self.assertEqual(len(encoded_unexpected_from_addr), 748) + self.assertEqual(len(unexpected_from_addr), 261) + self.login_as_user(instructor) send_mail_url = reverse('send_email', kwargs={'course_id': unicode(course.id)}) response = self.client.post(send_mail_url, test_email) @@ -337,11 +350,13 @@ class TestEmailSendFromDashboardMockedHtmlToText(EmailSendFromDashboardTestCase) self.assertEqual(len(mail.outbox), 1) from_email = mail.outbox[0].from_email + expected_from_addr = ( + u'"{course_name}" Course Staff <{course_name}-no-reply@example.com>' + ).format(course_name=course.id.course) + self.assertEqual( from_email, - u'"{course_name}" Course Staff <{course_name}-no-reply@example.com>'.format( - course_name=course.id.course - ) + expected_from_addr ) self.assertEqual(len(from_email), 83)