Merge ~ilasc/launchpad:bug-1910403 into launchpad:master

Proposed by Ioana Lasc
Status: Merged
Approved by: Ioana Lasc
Approved revision: 427ab4b2ff7f1699788da083dfb6cbf1c7e9fb29
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ilasc/launchpad:bug-1910403
Merge into: launchpad:master
Diff against target: 108 lines (+29/-24)
2 files modified
lib/lp/archiveuploader/tests/test_utils.py (+25/-10)
lib/lp/archiveuploader/utils.py (+4/-14)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+396505@code.launchpad.net

Commit message

UserUploadHandler fails without Maintainer email

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

I'm expecting the added test to fail with the stack in the bug 1910403 but it fails with the following stack:

getVerifiedSignature, handler.py:253
getVerifiedSignatureResilient, handler.py:147
_verifySignature, dscfile.py:190
parse, dscfile.py:153
parseChanges, changesfile.py:109
run_and_collect_errors, nascentupload.py:434
run_and_reject_on_error, nascentupload.py:445
process, nascentupload.py:145
_processUpload, uploadprocessor.py:538
processChangesFile, uploadprocessor.py:368
processUpload, test_uploadprocessor.py:337
testUploadWithoutMaintainerEmail, test_uploadprocessor.py:553
_run_test_method, testcase.py:723
_run_user, runtest.py:191
_run_core, runtest.py:144
_run_prepared_result, runtest.py:108
_run_one, runtest.py:94
run, runtest.py:80
run, testcase.py:675
__call__, case.py:673
run_tests, runner.py:396
run_layer, runner.py:474
run_tests, runner.py:293
run, runner.py:191
main, test.py:275
<module>, test:8

And the final exception (lp.services.gpg.interfaces.GPGVerificationError) is visible to the "user/developer/test runner" as :
[u"GPG verification of /tmp/tmpX1xckK/incoming/bar_1.0-1/bar_1.0-1_source.changes failed: (7, 8, u'Bad signature')"]

Not sure how we get from here to the exception stack in the Bug assessment.

Revision history for this message
Ioana Lasc (ilasc) wrote :

If however we do have the Maintainer email address in the changes file and add a non ASCII character in the email template params, we see this failure stack, again associated with failing to verify the signature:

processChangesFile, uploadprocessor.py:409
processUpload, test_uploadprocessor.py:337
testUploadWithoutMaintainerEmail, test_uploadprocessor.py:553
_run_test_method, testcase.py:723
_run_user, runtest.py:191
_run_core, runtest.py:144
_run_prepared_result, runtest.py:108
_run_one, runtest.py:94
run, runtest.py:80
run, testcase.py:675
__call__, case.py:673
run_tests, runner.py:396
run_layer, runner.py:474
run_tests, runner.py:293
run, runner.py:191
main, test.py:275
<module>, test:8

Revision history for this message
Ioana Lasc (ilasc) wrote :

OK, now reproduced the exact same stack trace from the OOPS through unit test "testParseMaintainerNonASCII" and for the same reason captured by bug 1910403: non ASCII characters in the Maintainer field which will ultimately be a part of the email template parameters along with other fields in lp.services.mail.basemailer, line 185.

I would think we would see the same in the other test "testUploadWithoutNonASCII" -> this however is failing signature verification in the same points pasted in stack traces in my previous comments. I don't understand why it's failing differently there, but I also don't understand that area of the code enough yet and I'm probably not setting up the test correctly to begin with,

Revision history for this message
Ioana Lasc (ilasc) wrote :

MP is now ready for first review.

Revision history for this message
Colin Watson (cjwatson) wrote :

You can reduce the original OOPS to whether an exception e from `parse_maintainer_bytes` can be rendered as text using `six.text_type(e)`; that's not literally what the email layer does, but it's close enough. This patch doesn't fix that. (In fact, while the cleanup in `ParseMaintError` looks reasonable, I don't think it fixes anything, as the new test you've added passes with or without that cleanup.)

>>> import six
>>> from lp.archiveuploader.utils import parse_maintainer_bytes
>>> try:
... parse_maintainer_bytes(b'No\xc3\xa8l K\xc3\xb6the', 'Maintainer')
... except Exception as e:
... pass
>>> six.text_type(e)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 2: ordinal not in range(128)

Consider instead just constructing the `ParseMaintError` exceptions raised by `parse_maintainer` as Unicode rather than encoding text to UTF-8 there. I think that should work better.

You may also want to fold the test for this into a simple modification to `testParseMaintainerRaises`, rather than adding a new test case. It looks as though a change to that test will be needed for a correct fix anyway.

review: Needs Fixing
Revision history for this message
Ioana Lasc (ilasc) wrote :

Colin thank you for the details!
Incorporated all suggested changes, MP now ready for another look.

Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/archiveuploader/tests/test_utils.py b/lib/lp/archiveuploader/tests/test_utils.py
2index e4d11ef..52caef5 100755
3--- a/lib/lp/archiveuploader/tests/test_utils.py
4+++ b/lib/lp/archiveuploader/tests/test_utils.py
5@@ -6,9 +6,8 @@
6 from __future__ import absolute_import, print_function, unicode_literals
7
8 import os
9-import re
10
11-from testtools.testcase import ExpectedException
12+import six
13
14 from lp.archiveuploader.tests import datadir
15 from lp.archiveuploader.utils import (
16@@ -16,6 +15,7 @@ from lp.archiveuploader.utils import (
17 determine_source_file_type,
18 DpkgSourceError,
19 extract_dpkg_source,
20+ ParseMaintError,
21 re_isadeb,
22 re_issource,
23 )
24@@ -234,17 +234,32 @@ class TestUtilities(TestCase):
25 """
26 from lp.archiveuploader.utils import (
27 parse_maintainer_bytes,
28- ParseMaintError,
29 )
30+
31 cases = (
32- "James Troup",
33- "James Troup <james>",
34- "James Troup <james@nocrew.org",
35- b"No\xc3\xa8l K\xc3\xb6the")
36+ ("James Troup",
37+ 'James Troup: no @ found in email address part.'),
38+
39+ ("James Troup <james>",
40+ 'James Troup <james>: no @ found in email address part.'),
41+
42+ ("James Troup <james@nocrew.org",
43+ ("James Troup <james@nocrew.org: "
44+ "doesn't parse as a valid Maintainer field.")),
45+
46+ (b"No\xc3\xa8l K\xc3\xb6the",
47+ (b'No\xc3\xa8l K\xc3\xb6the: '
48+ b'no @ found in email address '
49+ b'part.').decode('utf-8')),
50+ )
51+
52 for case in cases:
53- with ExpectedException(
54- ParseMaintError, b'^%s: ' % re.escape(case)):
55- parse_maintainer_bytes(case, 'Maintainer')
56+ try:
57+ parse_maintainer_bytes(case[0], 'Maintainer')
58+ except ParseMaintError as e:
59+ self.assertEqual(case[1], six.text_type(e))
60+ else:
61+ self.fail('ParseMaintError not raised')
62
63
64 class TestFilenameRegularExpressions(TestCase):
65diff --git a/lib/lp/archiveuploader/utils.py b/lib/lp/archiveuploader/utils.py
66index 8b4cf73..4168d96 100644
67--- a/lib/lp/archiveuploader/utils.py
68+++ b/lib/lp/archiveuploader/utils.py
69@@ -171,16 +171,8 @@ def extract_component_from_section(section, default_component="main"):
70
71 class ParseMaintError(Exception):
72 """Exception raised for errors in parsing a maintainer field.
73-
74- Attributes:
75- message -- explanation of the error
76 """
77
78- def __init__(self, message):
79- Exception.__init__(self)
80- self.args = (message, )
81- self.message = message
82-
83
84 def parse_maintainer(maintainer, field_name="Maintainer"):
85 """Parses a Maintainer or Changed-By field into the name and address.
86@@ -200,9 +192,8 @@ def parse_maintainer(maintainer, field_name="Maintainer"):
87 else:
88 m = re_parse_maintainer.match(maintainer)
89 if not m:
90- raise ParseMaintError(
91- "%s: doesn't parse as a valid %s field."
92- % (maintainer.encode("utf-8"), field_name))
93+ raise ParseMaintError("%s: doesn't parse as a valid %s field."
94+ % (maintainer, field_name))
95 name = m.group(1)
96 email = m.group(2)
97 # Just in case the maintainer ended up with nested angles; check...
98@@ -210,9 +201,8 @@ def parse_maintainer(maintainer, field_name="Maintainer"):
99 email = email[1:]
100
101 if email.find(u"@") == -1 and email.find(u"buildd_") != 0:
102- raise ParseMaintError(
103- "%s: no @ found in email address part." %
104- maintainer.encode("utf-8"))
105+ raise ParseMaintError("%s: no @ found in email address part."
106+ % maintainer)
107
108 return (name, email)
109

Subscribers

People subscribed via source and target branches

to status/vote changes: