← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/maintainably-fix-safe-fix-maintainer into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/maintainably-fix-safe-fix-maintainer into lp:launchpad.

Commit message:
Split fix_maintainer into parse_maintainer and rfc(822|2047)_encode_address. Fixes Unicode handling in some callsites like SignableTagFile.parseAddress's creation case.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1479160 in Launchpad itself: "SignableTagFile.parseAddress fails to create a person with a non-ASCII name"
  https://bugs.launchpad.net/launchpad/+bug/1479160

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/maintainably-fix-safe-fix-maintainer/+merge/266185

Split fix_maintainer up into parse_maintainer and rfc(822|2047)_encode_address. parse_maintainer now returns Unicode, fixing SignableTagFile.parseAddress when it attempts to create a person with a non-ASCII displayname.

The RFC2047-encoded address no longer respects the address's original encoding, but that actually didn't happen anyway, since everything used safe_fix_maintainer which converts everything to UTF-8 first.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/maintainably-fix-safe-fix-maintainer into lp:launchpad.
=== modified file 'lib/lp/archiveuploader/changesfile.py'
--- lib/lp/archiveuploader/changesfile.py	2015-04-21 10:03:15 +0000
+++ lib/lp/archiveuploader/changesfile.py	2015-07-29 07:00:05 +0000
@@ -37,6 +37,7 @@
     re_changes_file_name,
     re_isadeb,
     re_issource,
+    rfc822_encode_address,
     UploadError,
     UploadWarning,
     )
@@ -355,10 +356,10 @@
          -- <CHANGED-BY>  <DATE>
         }}}
         """
-        changes_author = (
-            '\n\n -- %s  %s' %
-            (self.changed_by['rfc822'], self.date))
-        return self.changes_comment + changes_author
+        changes_author = rfc822_encode_address(
+            self.changed_by['name'], self.changed_by['email'])
+        return '%s\n\n -- %s  %s' % (
+            self.changes_comment, changes_author.encode('utf-8'), self.date)
 
 
 def determine_file_class_and_name(filename):

=== modified file 'lib/lp/archiveuploader/dscfile.py'
--- lib/lp/archiveuploader/dscfile.py	2014-07-23 05:53:56 +0000
+++ lib/lp/archiveuploader/dscfile.py	2015-07-29 07:00:05 +0000
@@ -44,12 +44,12 @@
     extract_dpkg_source,
     get_source_file_extension,
     parse_and_merge_file_lists,
+    parse_maintainer_bytes,
     ParseMaintError,
     re_is_component_orig_tar_ext,
     re_issource,
     re_valid_pkg_name,
     re_valid_version,
-    safe_fix_maintainer,
     UploadError,
     UploadWarning,
     )
@@ -189,13 +189,11 @@
         for any reason, or if the email address then cannot be found within
         the launchpad database.
 
-        Return a dict containing the rfc822 and rfc2047 formatted forms of
-        the address, the person's name, email address and person record within
-        the launchpad database.
+        Return a dict containing the person's name, email address and
+        person record within the launchpad database.
         """
         try:
-            (rfc822, rfc2047, name, email) = safe_fix_maintainer(
-                addr, fieldname)
+            (name, email) = parse_maintainer_bytes(addr, fieldname)
         except ParseMaintError as error:
             raise UploadError(str(error))
 
@@ -226,8 +224,6 @@
                               % (name, email))
 
         return {
-            "rfc822": rfc822,
-            "rfc2047": rfc2047,
             "name": name,
             "email": email,
             "person": person,

=== modified file 'lib/lp/archiveuploader/tests/nascentuploadfile.txt'
--- lib/lp/archiveuploader/tests/nascentuploadfile.txt	2015-01-28 17:54:06 +0000
+++ lib/lp/archiveuploader/tests/nascentuploadfile.txt	2015-07-29 07:00:05 +0000
@@ -298,16 +298,14 @@
 The built address_structure contains values that will be used during
 the upload processing:
 
-    >>> ed_binary_changes.maintainer['rfc822']
-    'James Troup <james@xxxxxxxxxx>'
-    >>> ed_binary_changes.maintainer['rfc2047']
-    'James Troup <james@xxxxxxxxxx>'
     >>> ed_binary_changes.maintainer['name']
-    'James Troup'
+    u'James Troup'
     >>> ed_binary_changes.maintainer['email']
-    'james@xxxxxxxxxx'
+    u'james@xxxxxxxxxx'
     >>> ed_binary_changes.maintainer['person']
     <Person ...>
+    >>> ed_binary_changes.maintainer['person'].displayname
+    u'James Troup'
 
 
 Signature Traces
@@ -372,8 +370,8 @@
 the author. The DSCFile class implements the same address parsing
 methods found in ChangesFile:
 
-    >>> ed_source_dsc.maintainer['rfc822']
-    'James Troup <james@xxxxxxxxxx>'
+    >>> ed_source_dsc.maintainer['person'].displayname
+    u'James Troup'
 
 The DSC signer IPerson:
 

=== removed file 'lib/lp/archiveuploader/tests/safe_fix_maintainer.txt'
--- lib/lp/archiveuploader/tests/safe_fix_maintainer.txt	2015-07-07 12:48:40 +0000
+++ lib/lp/archiveuploader/tests/safe_fix_maintainer.txt	1970-01-01 00:00:00 +0000
@@ -1,43 +0,0 @@
-Test some utils method inherited from DAK:
-
-safe_fix_maintainer() is a function used to sanitise the
-identification fields coming from the Debian control files (changes
-and dsc). It allows safe unicode and non-unicode inputs.
-
-    >>> from lp.archiveuploader.utils import safe_fix_maintainer
-
-    >>> maintainer_field = 'maintainer'
-    >>> changer_field = 'changed-by'
-
-Pure ASCII content using the two available fieldname (pretty much the same)
-
-    >>> content = 'Hello World <hello@xxxxxxxxx>'
-    >>> safe_fix_maintainer(content, maintainer_field)
-    ('Hello World <hello@xxxxxxxxx>', 'Hello World <hello@xxxxxxxxx>',
-    'Hello World', 'hello@xxxxxxxxx')
-
-    >>> content = 'Hello World <hello@xxxxxxxxx>'
-    >>> safe_fix_maintainer(content, changer_field)
-    ('Hello World <hello@xxxxxxxxx>', 'Hello World <hello@xxxxxxxxx>',
-    'Hello World', 'hello@xxxxxxxxx')
-
-
-Passing Unicode:
-
-    # XXX cprov 2006-02-20 bug=32148: Not sure if it is working properly,
-    # at least doesn't raise any exception like in bug #32148.
-
-    >>> content = u'Rapha\xc3l Pinson <raphink@xxxxxxxxxx>'
-    >>> safe_fix_maintainer(content, maintainer_field)
-    ('Rapha\xc3\x83l Pinson <raphink@xxxxxxxxxx>',
-    '=?utf-8?q?Rapha=C3=83l_Pinson?= <raphink@xxxxxxxxxx>',
-    'Rapha\xc3\x83l Pinson', 'raphink@xxxxxxxxxx')
-
-
-Passing latin encoded string:
-
-    >>> content = 'Rapha\xebl Pinson <raphink@xxxxxxxxxx>'
-    >>> safe_fix_maintainer(content, maintainer_field)
-    ('Rapha\xc3\xabl Pinson <raphink@xxxxxxxxxx>',
-    '=?utf-8?q?Rapha=C3=ABl_Pinson?= <raphink@xxxxxxxxxx>',
-    'Rapha\xc3\xabl Pinson', 'raphink@xxxxxxxxxx')

=== modified file 'lib/lp/archiveuploader/tests/test_changesfile.py'
--- lib/lp/archiveuploader/tests/test_changesfile.py	2015-04-21 10:03:15 +0000
+++ lib/lp/archiveuploader/tests/test_changesfile.py	2015-07-29 07:00:05 +0000
@@ -8,7 +8,11 @@
 import os
 
 from debian.deb822 import Changes
-from testtools.matchers import MatchesStructure
+from testtools.matchers import (
+    Equals,
+    MatchesDict,
+    MatchesStructure,
+    )
 from zope.component import getUtility
 
 from lp.archiveuploader.changesfile import (
@@ -252,8 +256,13 @@
         self.assertEquals(None, changes.changed_by)
         errors = list(changes.processAddresses())
         self.assertEquals(0, len(errors), "Errors: %r" % errors)
-        self.assertEquals(
-            "Somebody <somebody@xxxxxxxxxx>", changes.changed_by['rfc822'])
+        self.assertThat(
+            changes.changed_by,
+            MatchesDict({
+                "name": Equals(u"Somebody"),
+                "email": Equals(u"somebody@xxxxxxxxxx"),
+                "person": MatchesStructure.byEquality(displayname=u"Somebody"),
+                }))
 
     def test_simulated_changelog(self):
         # The simulated_changelog property returns a changelog entry based on

=== modified file 'lib/lp/archiveuploader/tests/test_dscfile.py'
--- lib/lp/archiveuploader/tests/test_dscfile.py	2014-06-11 08:29:09 +0000
+++ lib/lp/archiveuploader/tests/test_dscfile.py	2015-07-29 07:00:05 +0000
@@ -187,6 +187,16 @@
             UploadError,
             self.makeSignableTagFile().parseAddress, "invalid@bad-address")
 
+    def test_parseAddress_decodes_utf8(self):
+        name = u'B\u0105r'
+        email = u'bar@xxxxxxxxxxx'
+        results = self.makeSignableTagFile().parseAddress(
+            '%s <%s>' % (name.encode('utf-8'), email.encode('utf-8')))
+        self.assertEqual(email, results['email'])
+        self.assertEqual(name, results['name'])
+        self.assertEqual(name, results['person'].displayname)
+        self.assertEqual(email, results['person'].guessedemails[0].email)
+
 
 class TestDscFileLibrarian(TestCaseWithFactory):
     """Tests for DscFile that may use the Librarian."""

=== modified file 'lib/lp/archiveuploader/tests/test_utils.py'
--- lib/lp/archiveuploader/tests/test_utils.py	2011-03-21 12:55:50 +0000
+++ lib/lp/archiveuploader/tests/test_utils.py	2015-07-29 07:00:05 +0000
@@ -6,6 +6,9 @@
 # arch-tag: 90e6eb79-83a2-47e8-9f8b-3c687079c923
 
 import os
+import re
+
+from testtools.testcase import ExpectedException
 
 from lp.archiveuploader.tests import datadir
 from lp.archiveuploader.utils import (
@@ -139,94 +142,99 @@
         self.assertEquals(sect, "libs")
         self.assertEquals(comp, "restricted")
 
-    def testFixMaintainerOkay(self):
-        """lp.archiveuploader.utils.fix_maintainer should parse correct values
+    def testParseMaintainerOkay(self):
+        """lp.archiveuploader.utils.parse_maintainer should parse correctly
         """
-        from lp.archiveuploader.utils import fix_maintainer
+        from lp.archiveuploader.utils import (
+            parse_maintainer_bytes,
+            rfc2047_encode_address,
+            rfc822_encode_address,
+            )
         cases = (
             ("No\xc3\xa8l K\xc3\xb6the <noel@xxxxxxxxxx>",
-             "No\xc3\xa8l K\xc3\xb6the <noel@xxxxxxxxxx>",
-             "=?utf-8?b?Tm/DqGwgS8O2dGhl?= <noel@xxxxxxxxxx>",
-             "No\xc3\xa8l K\xc3\xb6the",
-             "noel@xxxxxxxxxx"),
+             u"No\xe8l K\xf6the <noel@xxxxxxxxxx>",
+             u"=?utf-8?b?Tm/DqGwgS8O2dGhl?= <noel@xxxxxxxxxx>",
+             u"No\xe8l K\xf6the",
+             u"noel@xxxxxxxxxx"),
 
             ("No\xe8l K\xf6the <noel@xxxxxxxxxx>",
-             "No\xc3\xa8l K\xc3\xb6the <noel@xxxxxxxxxx>",
-             "=?iso-8859-1?q?No=E8l_K=F6the?= <noel@xxxxxxxxxx>",
-             "No\xc3\xa8l K\xc3\xb6the",
-             "noel@xxxxxxxxxx"),
+             u"No\xe8l K\xf6the <noel@xxxxxxxxxx>",
+             u"=?utf-8?b?Tm/DqGwgS8O2dGhl?= <noel@xxxxxxxxxx>",
+             u"No\xe8l K\xf6the",
+             u"noel@xxxxxxxxxx"),
 
             ("James Troup <james@xxxxxxxxxx>",
-             "James Troup <james@xxxxxxxxxx>",
-             "James Troup <james@xxxxxxxxxx>",
-             "James Troup",
-             "james@xxxxxxxxxx"),
+             u"James Troup <james@xxxxxxxxxx>",
+             u"James Troup <james@xxxxxxxxxx>",
+             u"James Troup",
+             u"james@xxxxxxxxxx"),
 
             ("James J. Troup <james@xxxxxxxxxx>",
-             "james@xxxxxxxxxx (James J. Troup)",
-             "james@xxxxxxxxxx (James J. Troup)",
-             "James J. Troup",
-             "james@xxxxxxxxxx"),
+             u"james@xxxxxxxxxx (James J. Troup)",
+             u"james@xxxxxxxxxx (James J. Troup)",
+             u"James J. Troup",
+             u"james@xxxxxxxxxx"),
 
             ("James J, Troup <james@xxxxxxxxxx>",
-             "james@xxxxxxxxxx (James J, Troup)",
-             "james@xxxxxxxxxx (James J, Troup)",
-             "James J, Troup",
-             "james@xxxxxxxxxx"),
+             u"james@xxxxxxxxxx (James J, Troup)",
+             u"james@xxxxxxxxxx (James J, Troup)",
+             u"James J, Troup",
+             u"james@xxxxxxxxxx"),
 
             ("james@xxxxxxxxxx",
-             " <james@xxxxxxxxxx>",
-             " <james@xxxxxxxxxx>",
-             "",
-             "james@xxxxxxxxxx"),
+             u" <james@xxxxxxxxxx>",
+             u" <james@xxxxxxxxxx>",
+             u"",
+             u"james@xxxxxxxxxx"),
 
             ("<james@xxxxxxxxxx>",
-             " <james@xxxxxxxxxx>",
-             " <james@xxxxxxxxxx>",
-             "",
-             "james@xxxxxxxxxx"),
+             u" <james@xxxxxxxxxx>",
+             u" <james@xxxxxxxxxx>",
+             u"",
+             u"james@xxxxxxxxxx"),
 
             ("Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>",
-             "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>",
-             "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>",
-             "Cris van Pelt",
-             "\"Cris van Pelt\"@tribe.eu.org"),
+             u"Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>",
+             u"Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>",
+             u"Cris van Pelt",
+             u"\"Cris van Pelt\"@tribe.eu.org"),
 
             ("Zak B. Elep <zakame@xxxxxxxxxx>",
-             "zakame@xxxxxxxxxx (Zak B. Elep)",
-             "zakame@xxxxxxxxxx (Zak B. Elep)",
-             "Zak B. Elep",
-             "zakame@xxxxxxxxxx"),
+             u"zakame@xxxxxxxxxx (Zak B. Elep)",
+             u"zakame@xxxxxxxxxx (Zak B. Elep)",
+             u"Zak B. Elep",
+             u"zakame@xxxxxxxxxx"),
 
             ("zakame@xxxxxxxxxx (Zak B. Elep)",
-             " <zakame@xxxxxxxxxx (Zak B. Elep)>",
-             " <zakame@xxxxxxxxxx (Zak B. Elep)>",
-             "",
-             "zakame@xxxxxxxxxx (Zak B. Elep)"),
+             u" <zakame@xxxxxxxxxx (Zak B. Elep)>",
+             u" <zakame@xxxxxxxxxx (Zak B. Elep)>",
+             u"",
+             u"zakame@xxxxxxxxxx (Zak B. Elep)"),
              )
 
         for case in cases:
-            (a, b, c, d) = fix_maintainer(case[0])
-            self.assertEquals(case[1], a)
-            self.assertEquals(case[2], b)
-            self.assertEquals(case[3], c)
-            self.assertEquals(case[4], d)
+            (name, email) = parse_maintainer_bytes(case[0], 'Maintainer')
+            self.assertEquals(case[3], name)
+            self.assertEquals(case[4], email)
+            self.assertEquals(case[1], rfc822_encode_address(name, email))
+            self.assertEquals(case[2], rfc2047_encode_address(name, email))
 
-    def testFixMaintainerRaises(self):
-        """lp.archiveuploader.utils.fix_maintainer should raise on incorrect
+    def testParseMaintainerRaises(self):
+        """lp.archiveuploader.utils.parse_maintainer should raise on incorrect
            values
         """
-        from lp.archiveuploader.utils import fix_maintainer, ParseMaintError
+        from lp.archiveuploader.utils import (
+            parse_maintainer_bytes,
+            ParseMaintError,
+            )
         cases = (
             "James Troup",
             "James Troup <james>",
-            "James Troup <james@xxxxxxxxxx")
+            "James Troup <james@xxxxxxxxxx",
+            "No\xc3\xa8l K\xc3\xb6the")
         for case in cases:
-            try:
-                fix_maintainer(case)
-                self.assertNotReached()
-            except ParseMaintError:
-                pass
+            with ExpectedException(ParseMaintError, '^%s: ' % re.escape(case)):
+                parse_maintainer_bytes(case, 'Maintainer')
 
 
 class TestFilenameRegularExpressions(TestCase):

=== modified file 'lib/lp/archiveuploader/utils.py'
--- lib/lp/archiveuploader/utils.py	2015-07-07 12:46:26 +0000
+++ lib/lp/archiveuploader/utils.py	2015-07-29 07:00:05 +0000
@@ -12,6 +12,7 @@
     'extract_dpkg_source',
     'get_source_file_extension',
     'parse_and_merge_file_lists',
+    'parse_maintainer_bytes',
     'ParseMaintError',
     'prefix_multi_line_string',
     're_taint_free',
@@ -24,14 +25,15 @@
     're_valid_pkg_name',
     're_changes_file_name',
     're_extract_src_version',
-    'safe_fix_maintainer',
+    'rfc2047_encode_address',
+    'rfc822_encode_address',
     'UploadError',
     'UploadWarning',
     ]
 
 
 from collections import defaultdict
-import email.header
+from email.header import Header
 import os
 import re
 import signal
@@ -157,42 +159,6 @@
     return (section, component)
 
 
-def force_to_utf8(s):
-    """Forces a string to UTF-8.
-
-    If the string isn't already UTF-8, it's assumed to be ISO-8859-1.
-    """
-    try:
-        unicode(s, 'utf-8')
-        return s
-    except UnicodeError:
-        latin1_s = unicode(s, 'iso8859-1')
-        return latin1_s.encode('utf-8')
-
-
-def rfc2047_encode(s):
-    """Encodes a (header) string per RFC2047 if necessary.
-
-    If the string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1.
-    """
-    if not s:
-        return ''
-    try:
-        s.decode('us-ascii')
-        #encodings.ascii.Codec().decode(s)
-        return s
-    except UnicodeError:
-        pass
-    try:
-        s.decode('utf8')
-        #encodings.utf_8.Codec().decode(s)
-        h = email.header.Header(s, 'utf-8', 998)
-        return str(h)
-    except UnicodeError:
-        h = email.header.Header(s, 'iso-8859-1', 998)
-        return str(h)
-
-
 class ParseMaintError(Exception):
     """Exception raised for errors in parsing a maintainer field.
 
@@ -206,73 +172,85 @@
         self.message = message
 
 
-def fix_maintainer(maintainer, field_name="Maintainer"):
-    """Parses a Maintainer or Changed-By field and returns:
-
-    (1) an RFC822 compatible version,
-    (2) an RFC2047 compatible version,
-    (3) the name
-    (4) the email
-
-    The name is forced to UTF-8 for both (1) and (3).  If the name field
-    contains '.' or ',', (1) and (2) are switched to 'email (name)' format.
+def parse_maintainer(maintainer, field_name="Maintainer"):
+    """Parses a Maintainer or Changed-By field into the name and address.
+
+    maintainer, name and address are all Unicode.
     """
     maintainer = maintainer.strip()
     if not maintainer:
-        return ('', '', '', '')
+        return (u'', u'')
 
-    if maintainer.find("<") == -1:
+    if maintainer.find(u"<") == -1:
         email = maintainer
-        name = ""
-    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
+        name = u""
+    elif (maintainer[0] == u"<" and maintainer[-1:] == u">"):
         email = maintainer[1:-1]
-        name = ""
+        name = u""
     else:
         m = re_parse_maintainer.match(maintainer)
         if not m:
             raise ParseMaintError(
                 "%s: doesn't parse as a valid %s field."
-                % (maintainer, field_name))
+                % (maintainer.encode("utf-8"), field_name))
         name = m.group(1)
         email = m.group(2)
         # Just in case the maintainer ended up with nested angles; check...
-        while email.startswith("<"):
+        while email.startswith(u"<"):
             email = email[1:]
 
-    # Get an RFC2047 compliant version of the name
-    rfc2047_name = rfc2047_encode(name)
-
-    # Force the name to be UTF-8
-    name = force_to_utf8(name)
-
+    if email.find(u"@") == -1 and email.find(u"buildd_") != 0:
+        raise ParseMaintError(
+            "%s: no @ found in email address part." %
+            maintainer.encode("utf-8"))
+
+    return (name, email)
+
+
+def parse_maintainer_bytes(content, fieldname):
+    """Wrapper for parse_maintainer to handle both Unicode and bytestrings.
+
+    It verifies the content type and transforms it to a unicode with
+    guess().  Then we can safely call parse_maintainer().
+    """
+    if type(content) != unicode:
+        content = guess_encoding(content)
+    return parse_maintainer(content, fieldname)
+
+
+def rfc822_encode_address(name, email):
+    """Return a Unicode RFC822 encoding of a name and an email address.
+
+    name and email must be Unicode. If they contain non-ASCII
+    characters, the result is not RFC822-compliant and you should use
+    rfc2047_encode_address instead.
+
+    If the name field contains '.' or ',' the 'email (name)' format is used.
+    """
     # If the maintainer's name contains a full stop then the whole field will
     # not work directly as an email address due to a misfeature in the syntax
     # specified in RFC822; see Debian policy 5.6.2 (Maintainer field syntax)
     # for details.
-    if name.find(',') != -1 or name.find('.') != -1:
-        rfc822_maint = "%s (%s)" % (email, name)
-        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
+    if name.find(u',') != -1 or name.find(u'.') != -1:
+        return u"%s (%s)" % (email, name)
     else:
-        rfc822_maint = "%s <%s>" % (name, email)
-        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
-
-    if email.find("@") == -1 and email.find("buildd_") != 0:
-        raise ParseMaintError(
-            "%s: no @ found in email address part." % maintainer)
-
-    return (rfc822_maint, rfc2047_maint, name, email)
-
-
-def safe_fix_maintainer(content, fieldname):
-    """Wrapper for fix_maintainer() to handle unicode and string argument.
-
-    It verifies the content type and transforms it to a unicode with
-    guess().  Then we can safely call fix_maintainer().
+        return u"%s <%s>" % (name, email)
+
+
+def rfc2047_encode_address(name, email):
+    """Return an RFC2047 encoding of a name and an email address.
+
+    name and email must be Unicode strings, and email must be
+    ASCII-only.
+
+    If the name field contains '.' or ',' the 'email (name)' format is used.
     """
-    if type(content) != unicode:
-        content = guess_encoding(content)
-
-    return fix_maintainer(content.encode("utf-8"), fieldname)
+    try:
+        email.encode('ascii')
+    except UnicodeDecodeError:
+        raise AssertionError("Email addresses must be ASCII.")
+    return rfc822_encode_address(
+        Header(name, 'utf-8', 998).encode().decode('ascii'), email)
 
 
 def extract_dpkg_source(dsc_filepath, target, vendor=None):

=== modified file 'lib/lp/soyuz/adapters/notification.py'
--- lib/lp/soyuz/adapters/notification.py	2015-07-21 09:04:01 +0000
+++ lib/lp/soyuz/adapters/notification.py	2015-07-29 07:00:05 +0000
@@ -21,8 +21,10 @@
 from lp.archivepublisher.utils import get_ppa_reference
 from lp.archiveuploader.changesfile import ChangesFile
 from lp.archiveuploader.utils import (
+    parse_maintainer_bytes,
     ParseMaintError,
-    safe_fix_maintainer,
+    rfc2047_encode_address,
+    rfc822_encode_address,
     )
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -577,7 +579,7 @@
 def fix_email(fullemail, field_name):
     """Turn an email address from .changes into various useful forms.
 
-    The input address may be None, or anything that `fix_maintainer`
+    The input address may be None, or anything that `parse_maintainer_bytes`
     understands.
 
     :return: A tuple of (RFC2047-compatible address, Unicode
@@ -587,8 +589,11 @@
         return None, None, None
 
     try:
-        rfc822, rfc2047, _, email = safe_fix_maintainer(fullemail, field_name)
-        return rfc2047, rfc822.decode('utf-8'), email
+        name, email = parse_maintainer_bytes(fullemail, field_name)
+        return (
+            rfc2047_encode_address(name, email).encode('utf-8'),
+            rfc822_encode_address(name, email),
+            email.encode('ascii'))
     except ParseMaintError:
         return None, None, None
 


Follow ups