Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion qreu/address.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import absolute_import
# coding: utf-8
from __future__ import absolute_import, unicode_literals

from collections import namedtuple

import six

try:
from collections import UserList
except ImportError:
Expand Down Expand Up @@ -45,3 +49,28 @@ class AddressList(UserList):
@property
def addresses(self):
return [x[1] for x in getaddresses(self.data) if x[1]]


def normalize_display_address(addr_string):
"""
Ensures that the display name in an email address is properly quoted
if it contains special characters. If a display name is present before
a '<...>' address block and is not already quoted, it will be quoted.

:param addr_string: A string containing a full email address
(e.g. RAMOS ESCOLÀ, PEPITA <pepita@example.com>)
:return: A normalized email string with quoted display name if needed
"""
if isinstance(addr_string, six.binary_type):
addr_string = addr_string.decode('utf-8')
if '<' in addr_string and '>' in addr_string:
name_part, addr_part = addr_string.split('<', 1)
name = name_part.strip()
email = addr_part.strip('> ').strip()

if name and not (name.startswith('"') and name.endswith('"')):
name = '"{}"'.format(name)

return '{} <{}>'.format(name, email)
return addr_string # If no angle brackets found, return as-is

4 changes: 3 additions & 1 deletion qreu/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,9 @@ def from_(self):

:return: `address.Address`
"""
return address.parse(self.header('From', ''))
return address.parse(address.normalize_display_address(
self.header('From', ''))
)

@property
def to(self):
Expand Down
28 changes: 27 additions & 1 deletion spec/address_spec.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# coding=utf-8
from qreu.address import parse, parse_list, AddressList, Address
from qreu.address import parse, parse_list, AddressList, Address, normalize_display_address
from expects import *


Expand Down Expand Up @@ -38,3 +38,29 @@
expect((a1 + a2).addresses).to(contain_exactly(
'u@example.com', 'u2@example.com'
))

with context('normalizing address display name'):
with it('must quote display name if it contains commas'):
addr_str = 'RAMOS ESCOLÀ, PEPITA <pepita@example.com>'
normalized = normalize_display_address(addr_str)
expect(normalized).to(equal(u'"RAMOS ESCOLÀ, PEPITA" <pepita@example.com>'))

with it('must not quote display name if already quoted'):
addr_str = u'"RAMOS ESCOLÀ, PEPITA" <pepita@example.com>'
normalized = normalize_display_address(addr_str)
expect(normalized).to(equal(addr_str))

with it('must not alter address without display name'):
addr_str = 'pepita@example.com'
normalized = normalize_display_address(addr_str)
expect(normalized).to(equal(addr_str))

with it('must quote display name with semicolon'):
addr_str = 'Admin; Name <admin@example.com>'
normalized = normalize_display_address(addr_str)
expect(normalized).to(equal('"Admin; Name" <admin@example.com>'))

with it('must ignore address without angle brackets'):
addr_str = 'Admin Name admin@example.com'
normalized = normalize_display_address(addr_str)
expect(normalized).to(equal(addr_str))
3 changes: 3 additions & 0 deletions spec/fixtures/7.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
From: "RAMOS ESCOLÀ, PEPITA" <pepitaramos@example.com>
To: SAC <sac@example.com>
CC: "Bústia General" <general@example.com>
1 change: 1 addition & 0 deletions spec/fixtures/8.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
From: =?iso-8859-1?Q?RAMOS_ESCOL=C0=2C_PEPITA?= <pepita@example.com>
13 changes: 12 additions & 1 deletion spec/qreu_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
with description('Parsing an Email'):
with before.all:
self.raw_messages = []
for fixture in range(0, 7):
for fixture in range(0, 9):
with open('spec/fixtures/{0}.txt'.format(fixture)) as f:
self.raw_messages.append(f.read())

Expand Down Expand Up @@ -115,6 +115,17 @@
with it('should parse correctly address from weird header'):
c = Email.parse(self.raw_messages[5])
expect(c.from_).to(have_property('address', 'monica@example.com'))
expect(c.from_).to(have_property('display_name', u'Mónica de Test Example'))

c = Email.parse(self.raw_messages[8])
expect(c.from_).to(have_property('address', 'pepita@example.com'))
expect(c.from_).to(have_property('display_name', u'RAMOS ESCOLÀ, PEPITA'))

with it('should parse correctly address with accents'):
c = Email.parse(self.raw_messages[7])
expect(c.from_).to(have_property('display_name', u'RAMOS ESCOLÀ, PEPITA'))
expect(c.from_.address).to(equal('pepitaramos@example.com'))
expect(c.cc.addresses).to(contain_exactly(u'general@example.com'))

with description("Creating an Email"):
with context("empty"):
Expand Down