Avoid double-encode address headers. Fixes #40

This commit is contained in:
Sergey Lavrinenko 2015-03-23 14:45:44 +03:00
parent 20249b6e76
commit 5583523d98
4 changed files with 48 additions and 27 deletions

View File

@ -113,6 +113,8 @@ if is_py2:
return x.encode(charset, errors)
raise TypeError('Expected bytes')
from email.utils import formataddr
elif is_py3:
import urllib.parse as urlparse
@ -142,6 +144,25 @@ elif is_py3:
return x.encode(charset, errors)
raise TypeError('Expected bytes')
from email.utils import escapesre, specialsre
def formataddr(pair):
"""
This code is copy of python2 email.utils.formataddr.
Takes a 2-tuple of the form (realname, email_address) and returns RFC2822-like string.
Does not encode non-ascii realname.
Python3 email.utils.formataddr do encode realname.
"""
name, address = pair
if name:
quotes = ''
if specialsre.search(name):
quotes = '"'
name = escapesre.sub(r'\\\g<0>', name)
return '%s%s%s <%s>' % (quotes, name, quotes, address)
return address
def to_unicode(x, charset=sys.getdefaultencoding(), errors='strict',
allow_none_charset=False):

View File

@ -10,7 +10,8 @@ import errno
from zipfile import ZipFile
import email
from ..compat import to_unicode, string_types, to_native
from ..compat import to_unicode, string_types, to_native, formataddr as compat_formataddr
from ..loader.helpers import decode_text
from ..message import Message
from ..utils import decode_header
@ -207,22 +208,6 @@ class ZipLoader(BaseLoader):
return sorted(self._filenames)
def _human_formataddr(pair):
"""Takes a 2-tuple of the form (realname, email_address) and returns RFC2822-like string.
Does not encode non-ascii realname.
The same code in python2 email.utils.formataddr
"""
from email.utils import escapesre, specialsre
name, address = pair
if name:
quotes = ''
if specialsre.search(name):
quotes = '"'
name = escapesre.sub(r'\\\g<0>', name)
return '%s%s%s <%s>' % (quotes, name, quotes, address)
return address
class MsgLoader(BaseLoader):
"""
Load files from email.Message
@ -368,7 +353,7 @@ class MsgLoader(BaseLoader):
if not skip_invalid:
r.append(decode_header(email))
else:
r.append(_human_formataddr([decode_header(name), email]))
r.append(compat_formataddr([decode_header(name), email]))
return r

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from email.utils import getaddresses, formataddr
from .compat import (string_types, is_callable, to_bytes)
from .compat import (string_types, is_callable, to_bytes, formataddr as compat_formataddr)
from .utils import (SafeMIMEText, SafeMIMEMultipart, sanitize_address,
parse_name_and_email, load_email_charsets,
encode_header as encode_header_,
@ -175,10 +175,13 @@ class MessageBuildMixin(object):
else:
return value
def encode_address_header(self, name, email):
return formataddr((self.encode_header(name or ''), email))
def encode_address_header(self, pair):
if not pair:
return None
name, email = pair
return compat_formataddr((name or '', email))
encode_name_header = encode_address_header
encode_name_header = encode_address_header # legacy name
def set_header(self, msg, key, value, encode=True):
@ -215,11 +218,8 @@ class MessageBuildMixin(object):
if subject is not None:
self.set_header(msg, 'Subject', subject)
mail_from = self._mail_from and self.encode_address_header(*self._mail_from) or None
self.set_header(msg, 'From', mail_from, encode=False)
mail_to = self._mail_to and self.encode_address_header(*self._mail_to[0]) or None
self.set_header(msg, 'To', mail_to, encode=False)
self.set_header(msg, 'From', self.encode_address_header(self._mail_from), encode=False)
self.set_header(msg, 'To', self._mail_to and self.encode_address_header(self._mail_to[0]) or None, encode=False)
return msg

View File

@ -1,12 +1,16 @@
# coding: utf-8
from __future__ import unicode_literals, print_function
import datetime
from email.utils import parseaddr, formataddr
from dateutil.parser import parse as dateutil_parse
import pytest
import emails
from emails import Message
import emails.exc
from emails.compat import to_unicode, StringIO, is_py2, is_py34_plus
from emails.utils import decode_header, sanitize_address
from .helpers import common_email_data
@ -103,6 +107,17 @@ def test_sanitize_header():
emails.Message(html='...', **{header: value}).as_message()
def test_address_header_not_double_encoded():
msg = {}
m = Message()
TEXT = 'Пушкин А.С.'
m.mail_from = (TEXT, 'a@b.c')
m.html = '...'
msg = m.as_message()
assert decode_header(parseaddr(msg['From'])[0]) == TEXT
def test_message_policy():
if is_py34_plus: