Raise error for invalidly-formatted email addresses.

A message's `from_email` and each address in its `to`, `cc`, and `bcc` lists must contain exactly one email address. Previous code would silently ignore additional addresses, leading to unusual behavior. Now, raises new `AnymailInvalidAddress` exception.

Example: `from_email='Widgets, Inc. <widgets@example.com>'` is invalid: it needs double-quotes around the "Widgets, Inc." display-name portion. In earlier versions, this probably would have sent the message from something like "From: Widgets <@localhost>". Now, it will raise an exception.

**Potentially-breaking change:** If your code is using an unquoted display-name containing a comma in an email address, it will now raise an error. In earlier versions, this may have appeared to succeed, but was almost certainly not doing what you intended.

Fixes #44.
This commit is contained in:
medmunds
2016-12-15 13:57:49 -08:00
parent 4ca39a976f
commit d0596d100b
4 changed files with 116 additions and 20 deletions

63
tests/test_utils.py Normal file
View File

@@ -0,0 +1,63 @@
# Tests for the anymail/utils.py module
# (not to be confused with utilities for testing found in in tests/utils.py)
from django.test import SimpleTestCase
from anymail.exceptions import AnymailInvalidAddress
from anymail.utils import ParsedEmail
class ParsedEmailTests(SimpleTestCase):
"""Test utils.ParsedEmail"""
# Anymail (and Djrill) have always used EmailMessage.encoding, which defaults to None.
# (Django substitutes settings.DEFAULT_ENCODING='utf-8' when converting to a mime message,
# but Anymail has never used that code.)
ADDRESS_ENCODING = None
def test_simple_email(self):
parsed = ParsedEmail("test@example.com", self.ADDRESS_ENCODING)
self.assertEqual(parsed.email, "test@example.com")
self.assertEqual(parsed.name, "")
self.assertEqual(parsed.address, "test@example.com")
def test_display_name(self):
parsed = ParsedEmail('"Display Name, Inc." <test@example.com>', self.ADDRESS_ENCODING)
self.assertEqual(parsed.email, "test@example.com")
self.assertEqual(parsed.name, "Display Name, Inc.")
self.assertEqual(parsed.address, '"Display Name, Inc." <test@example.com>')
def test_obsolete_display_name(self):
# you can get away without the quotes if there are no commas or parens
# (but it's not recommended)
parsed = ParsedEmail('Display Name <test@example.com>', self.ADDRESS_ENCODING)
self.assertEqual(parsed.email, "test@example.com")
self.assertEqual(parsed.name, "Display Name")
self.assertEqual(parsed.address, 'Display Name <test@example.com>')
def test_unicode_display_name(self):
parsed = ParsedEmail(u'"Unicode \N{HEAVY BLACK HEART}" <test@example.com>', self.ADDRESS_ENCODING)
self.assertEqual(parsed.email, "test@example.com")
self.assertEqual(parsed.name, u"Unicode \N{HEAVY BLACK HEART}")
# display-name automatically shifts to quoted-printable/base64 for non-ascii chars:
self.assertEqual(parsed.address, '=?utf-8?b?VW5pY29kZSDinaQ=?= <test@example.com>')
def test_invalid_display_name(self):
with self.assertRaises(AnymailInvalidAddress):
# this parses as multiple email addresses, because of the comma:
ParsedEmail('Display Name, Inc. <test@example.com>', self.ADDRESS_ENCODING)
def test_none_address(self):
# used for, e.g., telling Mandrill to use template default from_email
parsed = ParsedEmail(None, self.ADDRESS_ENCODING)
self.assertEqual(parsed.email, None)
self.assertEqual(parsed.name, None)
self.assertEqual(parsed.address, None)
def test_empty_address(self):
with self.assertRaises(AnymailInvalidAddress):
ParsedEmail('', self.ADDRESS_ENCODING)
def test_whitespace_only_address(self):
with self.assertRaises(AnymailInvalidAddress):
ParsedEmail(' ', self.ADDRESS_ENCODING)