|
@@ -13,7 +13,7 @@ from email.mime.base import MIMEBase
|
|
|
from email.mime.message import MIMEMessage
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
from email.mime.text import MIMEText
|
|
|
-from email.utils import formataddr, formatdate, getaddresses, parseaddr
|
|
|
+from email.utils import formatdate, getaddresses, parseaddr
|
|
|
from io import BytesIO
|
|
|
|
|
|
from django.conf import settings
|
|
@@ -103,22 +103,66 @@ def forbid_multi_line_headers(name, val, encoding):
|
|
|
return str(name), val
|
|
|
|
|
|
|
|
|
+def split_addr(addr, encoding):
|
|
|
+ """
|
|
|
+ Split the address into local part and domain, properly encoded.
|
|
|
+
|
|
|
+ When non-ascii characters are present in the local part, it must be
|
|
|
+ MIME-word encoded. The domain name must be idna-encoded if it contains
|
|
|
+ non-ascii characters.
|
|
|
+ """
|
|
|
+ if '@' in addr:
|
|
|
+ localpart, domain = addr.split('@', 1)
|
|
|
+ # Try to get the simplest encoding - ascii if possible so that
|
|
|
+ # to@example.com doesn't become =?utf-8?q?to?=@example.com. This
|
|
|
+ # makes unit testing a bit easier and more readable.
|
|
|
+ try:
|
|
|
+ localpart.encode('ascii')
|
|
|
+ except UnicodeEncodeError:
|
|
|
+ localpart = Header(localpart, encoding).encode()
|
|
|
+ domain = domain.encode('idna').decode('ascii')
|
|
|
+ else:
|
|
|
+ localpart = Header(addr, encoding).encode()
|
|
|
+ domain = ''
|
|
|
+ return (localpart, domain)
|
|
|
+
|
|
|
+
|
|
|
def sanitize_address(addr, encoding):
|
|
|
+ """
|
|
|
+ Format a pair of (name, address) or an email address string.
|
|
|
+ """
|
|
|
if not isinstance(addr, tuple):
|
|
|
addr = parseaddr(force_text(addr))
|
|
|
nm, addr = addr
|
|
|
+ localpart, domain = None, None
|
|
|
nm = Header(nm, encoding).encode()
|
|
|
try:
|
|
|
addr.encode('ascii')
|
|
|
- except UnicodeEncodeError: # IDN
|
|
|
- if '@' in addr:
|
|
|
- localpart, domain = addr.split('@', 1)
|
|
|
- localpart = str(Header(localpart, encoding))
|
|
|
- domain = domain.encode('idna').decode('ascii')
|
|
|
+ except UnicodeEncodeError: # IDN or non-ascii in the local part
|
|
|
+ localpart, domain = split_addr(addr, encoding)
|
|
|
+
|
|
|
+ if six.PY2:
|
|
|
+ # On Python 2, use the stdlib since `email.headerregistry` doesn't exist.
|
|
|
+ from email.utils import formataddr
|
|
|
+ if localpart and domain:
|
|
|
addr = '@'.join([localpart, domain])
|
|
|
- else:
|
|
|
- addr = Header(addr, encoding).encode()
|
|
|
- return formataddr((nm, addr))
|
|
|
+ return formataddr((nm, addr))
|
|
|
+
|
|
|
+ # On Python 3, an `email.headerregistry.Address` object is used since
|
|
|
+ # email.utils.formataddr() naively encodes the name as ascii (see #25986).
|
|
|
+ from email.headerregistry import Address
|
|
|
+ from email.errors import InvalidHeaderDefect, NonASCIILocalPartDefect
|
|
|
+
|
|
|
+ if localpart and domain:
|
|
|
+ address = Address(nm, username=localpart, domain=domain)
|
|
|
+ return str(address)
|
|
|
+
|
|
|
+ try:
|
|
|
+ address = Address(nm, addr_spec=addr)
|
|
|
+ except (InvalidHeaderDefect, NonASCIILocalPartDefect):
|
|
|
+ localpart, domain = split_addr(addr, encoding)
|
|
|
+ address = Address(nm, username=localpart, domain=domain)
|
|
|
+ return str(address)
|
|
|
|
|
|
|
|
|
class MIMEMixin():
|