def message(self): msg_root = MIMEMultipart('mixed') msg_root['Subject'] = forbid_multi_line_headers( 'Subject', self.subject, 'utf-8')[1] msg_root['From'] = forbid_multi_line_headers('From', self.from_email, 'utf-8')[1] msg_root['To'] = forbid_multi_line_headers('To', ', '.join(self.to), 'utf-8')[1] msg_root['Date'] = formatdate(localtime=settings.EMAIL_USE_LOCALTIME) msg_root['Message-ID'] = make_msgid(domain=DNS_NAME) msg_content = MIMEMultipart('related') msg_root.attach(msg_content) msg_text = MIMEText(self.content, 'html', 'utf-8') msg_content.attach(msg_text) if self.images: for image in self.images: msg_content.attach(self.create_inline_image(*image)) if self.attachments: for attachment in self.attachments: msg_root.attach(self.create_attachment(*attachment)) self.patch_message(msg_root) return msg_root
def test_long_course_display_name(self): """ This test tests that courses with exorbitantly large display names can still send emails, since it appears that 320 appears to be the character length limit of from emails for Amazon SES. """ test_email = { 'action': 'Send email', 'send_to': '["myself", "staff", "learners"]', 'subject': 'test subject for self', 'message': 'test message for self' } # make display_name that's longer than 320 characters when encoded # to ascii and escaped, but shorter than 320 unicode characters long_name = u"Финансовое программирование и политика, часть 1: макроэкономические счета и анализ" course = CourseFactory.create( display_name=long_name, org="IMF", number="FPP.1x", run="2016", ) instructor = InstructorFactory(course_key=course.id) unexpected_from_addr = _get_source_address( course.id, course.display_name, course_language=None, truncate=False ) __, encoded_unexpected_from_addr = forbid_multi_line_headers( "from", unexpected_from_addr, 'utf-8' ) escaped_encoded_unexpected_from_addr = escape(encoded_unexpected_from_addr) # it's shorter than 320 characters when just encoded self.assertEqual(len(encoded_unexpected_from_addr), 318) # escaping it brings it over that limit self.assertEqual(len(escaped_encoded_unexpected_from_addr), 324) # when not escaped or encoded, it's well below 320 characters self.assertEqual(len(unexpected_from_addr), 137) 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) self.assertTrue(json.loads(response.content)['success']) self.assertEqual(len(mail.outbox), 1) from_email = mail.outbox[0].from_email expected_from_addr = ( u'"{course_name}" Course Staff <{course_name}[email protected]>' ).format(course_name=course.id.course) self.assertEqual( from_email, expected_from_addr ) self.assertEqual(len(from_email), 61)
def test_long_course_display_name(self): """ This test tests that courses with exorbitantly large display names can still send emails, since it appears that 320 appears to be the character length limit of from emails for Amazon SES. """ test_email = { 'action': 'Send email', 'send_to': '["myself", "staff", "learners"]', 'subject': 'test subject for self', 'message': 'test message for self' } # make display_name that's longer than 320 characters when encoded # to ascii and escaped, but shorter than 320 unicode characters long_name = u"Финансовое программирование и политика, часть 1: макроэкономические счета и анализ" course = CourseFactory.create( display_name=long_name, org="IMF", number="FPP.1x", run="2016", ) instructor = InstructorFactory(course_key=course.id) unexpected_from_addr = _get_source_address( course.id, course.display_name, course_language=None, truncate=False ) __, encoded_unexpected_from_addr = forbid_multi_line_headers( "from", unexpected_from_addr, 'utf-8' ) escaped_encoded_unexpected_from_addr = escape(encoded_unexpected_from_addr) # it's shorter than 320 characters when just encoded self.assertEqual(len(encoded_unexpected_from_addr), 318) # escaping it brings it over that limit self.assertEqual(len(escaped_encoded_unexpected_from_addr), 324) # when not escaped or encoded, it's well below 320 characters self.assertEqual(len(unexpected_from_addr), 137) self.login_as_user(instructor) send_mail_url = reverse('send_email', kwargs={'course_id': six.text_type(course.id)}) response = self.client.post(send_mail_url, test_email) self.assertTrue(json.loads(response.content.decode('utf-8'))['success']) self.assertEqual(len(mail.outbox), 1) from_email = mail.outbox[0].from_email expected_from_addr = ( u'"{course_name}" Course Staff <{course_name}[email protected]>' ).format(course_name=course.id.course) self.assertEqual( from_email, expected_from_addr ) self.assertEqual(len(from_email), 61)
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 <*****@*****.**> 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) # For the email address, get the course. Then make sure that it can be used # in an email address, by substituting a '_' anywhere a non-(ascii, period, or dash) # character appears. course_name = re.sub(r"[^\w.-]", '_', course_id.course) from_addr_format = u'"{course_title}" Course Staff <{course_name}-{from_email}>' def format_address(course_title_no_quotes): """ Partial function for formatting the from_addr. Since `course_title_no_quotes` may be truncated to make sure the returned string has fewer than 320 characters, we define this function to make it easy to determine quickly what the max length is for `course_title_no_quotes`. """ return from_addr_format.format( course_title=course_title_no_quotes, course_name=course_name, from_email=theming_helpers.get_value( 'bulk_email_default_from_email', settings.BULK_EMAIL_DEFAULT_FROM_EMAIL ) ) from_addr = format_address(course_title_no_quotes) # 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') # It seems that this value is also escaped when set out to amazon, judging # from our logs escaped_encoded_from_addr = escape(encoded_from_addr) if len(escaped_encoded_from_addr) >= 320 and truncate: from_addr = format_address(course_name) return from_addr
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 <*****@*****.**> 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) # For the email address, get the course. Then make sure that it can be used # in an email address, by substituting a '_' anywhere a non-(ascii, period, or dash) # character appears. course_name = re.sub(r"[^\w.-]", '_', course_id.course) from_addr_format = u'"{course_title}" Course Staff <{course_name}-{from_email}>' def format_address(course_title_no_quotes): """ Partial function for formatting the from_addr. Since `course_title_no_quotes` may be truncated to make sure the returned string has fewer than 320 characters, we define this function to make it easy to determine quickly what the max length is for `course_title_no_quotes`. """ return from_addr_format.format( course_title=course_title_no_quotes, course_name=course_name, from_email=theming_helpers.get_value( 'email_from_address', settings.BULK_EMAIL_DEFAULT_FROM_EMAIL ) ) from_addr = format_address(course_title_no_quotes) # 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') # It seems that this value is also escaped when set out to amazon, judging # from our logs escaped_encoded_from_addr = escape(encoded_from_addr) if len(escaped_encoded_from_addr) >= 320 and truncate: from_addr = format_address(course_name) return from_addr
def test_long_course_display_name(self): """ This test tests that courses with exorbitantly large display names can still send emails, since it appears that 320 appears to be the character length limit of from emails for Amazon SES. """ test_email = { 'action': 'Send email', 'send_to': 'myself', 'subject': 'test subject for self', 'message': 'test message for self' } # 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) self.assertTrue(json.loads(response.content)['success']) self.assertEqual(len(mail.outbox), 1) from_email = mail.outbox[0].from_email expected_from_addr = ( u'"{course_name}" Course Staff <{course_name}[email protected]>' ).format(course_name=course.id.course) self.assertEqual(from_email, expected_from_addr) self.assertEqual(len(from_email), 83)
def _get_source_address(course_id, course_title, course_language, 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 <*****@*****.**> 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) # For the email address, get the course. Then make sure that it can be used # in an email address, by substituting a '_' anywhere a non-(ascii, period, or dash) # character appears. course_name = re.sub(r"[^\w.-]", '_', course_id.course) # Use course.language if present language = course_language if course_language else settings.LANGUAGE_CODE with override_language(language): # RFC2821 requires the byte order of the email address to be the name then email # e.g. "John Doe <*****@*****.**>" # Although the display will be flipped in RTL languages, the byte order is still the same. from_addr_format = '{name} {email}'.format( # Translators: Bulk email from address e.g. ("Physics 101" Course Staff) name=_('"{course_title}" Course Staff'), email='<{course_name}-{from_email}>', # xss-lint: disable=python-wrap-html ) def format_address(course_title_no_quotes): """ Partial function for formatting the from_addr. Since `course_title_no_quotes` may be truncated to make sure the returned string has fewer than 320 characters, we define this function to make it easy to determine quickly what the max length is for `course_title_no_quotes`. """ return from_addr_format.format( course_title=course_title_no_quotes, course_name=course_name, from_email=configuration_helpers.get_value( 'email_from_address', settings.BULK_EMAIL_DEFAULT_FROM_EMAIL ) ) from_addr = format_address(course_title_no_quotes) # 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') # It seems that this value is also escaped when set out to amazon, judging # from our logs escaped_encoded_from_addr = escape(encoded_from_addr) if len(escaped_encoded_from_addr) >= 320 and truncate: from_addr = format_address(course_name) return from_addr
def _get_source_address(course_id, course_title, course_language, 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 <*****@*****.**> 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) # For the email address, get the course. Then make sure that it can be used # in an email address, by substituting a '_' anywhere a non-(ascii, period, or dash) # character appears. course_name = re.sub(r"[^\w.-]", '_', course_id.course) # Use course.language if present language = course_language if course_language else settings.LANGUAGE_CODE with override_language(language): # RFC2821 requires the byte order of the email address to be the name then email # e.g. "John Doe <*****@*****.**>" # Although the display will be flipped in RTL languages, the byte order is still the same. from_addr_format = u'{name} {email}'.format( # Translators: Bulk email from address e.g. ("Physics 101" Course Staff) name=_('"{course_title}" Course Staff'), email=u'<{course_name}-{from_email}>', ) def format_address(course_title_no_quotes): """ Partial function for formatting the from_addr. Since `course_title_no_quotes` may be truncated to make sure the returned string has fewer than 320 characters, we define this function to make it easy to determine quickly what the max length is for `course_title_no_quotes`. """ return from_addr_format.format( course_title=course_title_no_quotes, course_name=course_name, from_email=configuration_helpers.get_value( 'email_from_address', settings.BULK_EMAIL_DEFAULT_FROM_EMAIL ) ) from_addr = format_address(course_title_no_quotes) # 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') # It seems that this value is also escaped when set out to amazon, judging # from our logs escaped_encoded_from_addr = escape(encoded_from_addr) if len(escaped_encoded_from_addr) >= 320 and truncate: from_addr = format_address(course_name) return from_addr