Merge lp:~sjakthol/software-properties/fix-1111291 into lp:software-properties

Proposed by Sami Jaktholm
Status: Merged
Merged at revision: 851
Proposed branch: lp:~sjakthol/software-properties/fix-1111291
Merge into: lp:software-properties
Diff against target: 86 lines (+40/-28)
1 file modified
softwareproperties/ppa.py (+40/-28)
To merge this branch: bzr merge lp:~sjakthol/software-properties/fix-1111291
Reviewer Review Type Date Requested Status
Michael Vogt (community) Approve
Review via email: mp+157514@code.launchpad.net

Commit message

* softwareproperties/ppa.py
  - Use PycURL only with python2, not if urllib raises an unexpected exception
    in python3. Fixes LP: #1111291.
  - Catch http.client.HTTPExceptions from urlopen and raise a PPAException to
    show a formatted error message instead of traceback.

Description of the change

This is hard to test as the code is only run if urllib fails. Luckily the changes are pretty straightforward.

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

On Sat, Apr 06, 2013 at 12:30:28PM -0000, Sami Jaktholm wrote:
> Sami Jaktholm has proposed merging lp:~sjakthol/software-properties/fix-1111291 into lp:software-properties.
>
> Commit message:
> * softwareproperties/ppa.py:
> - Convert bytes to string in CurlCallback.body_callback. Testcase added to
> tests/test_lp.py. Fixes LP: #1111291.

Hi Sami, thanks for your branch.

[..]
> This is hard to test as the code is only run if urllib fails. Luckily the changes are pretty straightforward.

The change itself it looks fine, but I'm curious why its needed? It
seems like the fallback code is only intended for python2 and the fix
is only needed when the pycurl code is run with py3.

We should probably rewrite this function to make that clear, attached
is a patch that shows what I have in mind (not tested, I have no
network connection while writing this).

So it would be great to look a bit deeper and figure out what is
causing the fallback code to get activated in the first place. The
current code with the big try/except is hiding the real problem it
seems.

Cheers,
 Michael

1=== modified file 'softwareproperties/ppa.py'
2--- softwareproperties/ppa.py 2013-02-27 20:15:21 +0000
3+++ softwareproperties/ppa.py 2013-04-08 04:40:31 +0000
4@@ -35,7 +35,9 @@
5 import urllib.request
6 from urllib.error import URLError
7 import urllib.parse
8+ NEED_PYCURL = False
9 except ImportError:
10+ NEED_PYCURL = True
11 import pycurl
12
13 DEFAULT_KEYSERVER = "hkp://keyserver.ubuntu.com:80/"
14@@ -92,35 +94,41 @@
15
16 def get_ppa_info_from_lp(owner_name, ppa_name):
17 lp_url = LAUNCHPAD_PPA_API % (owner_name, ppa_name)
18- try:
19- try:
20- request = urllib.request.Request(str(lp_url), headers={"Accept":" application/json"})
21- lp_page = urllib.request.urlopen(request, cafile=LAUNCHPAD_PPA_CERT)
22- json_data = lp_page.read().decode("utf-8", "strict")
23- except URLError as e:
24- raise PPAException("Error reading %s: %s" % (lp_url, e.reason), e)
25- except PPAException:
26- raise
27- except:
28- import pycurl
29- try:
30- callback = CurlCallback()
31- curl = pycurl.Curl()
32- curl.setopt(pycurl.SSL_VERIFYPEER, 1)
33- curl.setopt(pycurl.SSL_VERIFYHOST, 2)
34- curl.setopt(pycurl.WRITEFUNCTION, callback.body_callback)
35- if LAUNCHPAD_PPA_CERT:
36- curl.setopt(pycurl.CAINFO, LAUNCHPAD_PPA_CERT)
37- curl.setopt(pycurl.URL, str(lp_url))
38- curl.setopt(pycurl.HTTPHEADER, ["Accept: application/json"])
39- curl.perform()
40- curl.close()
41- json_data = callback.contents
42- except pycurl.error as e:
43- raise PPAException("Error reading %s: %s" % (lp_url, e), e)
44+ if NEED_PYCURL:
45+ # python2 has no cert verification so we need pycurl
46+ return _get_https_content_py3(lp_url)
47+ else:
48+ # python3 has cert verification so we can use the buildin urllib
49+ return _get_https_content_pycurl(lp_url)
50+
51+def _get_https_content_py3(lp_url):
52+ try:
53+ request = urllib.request.Request(str(lp_url), headers={"Accept":" application/json"})
54+ lp_page = urllib.request.urlopen(request, cafile=LAUNCHPAD_PPA_CERT)
55+ json_data = lp_page.read().decode("utf-8", "strict")
56+ except URLError as e:
57+ raise PPAException("Error reading %s: %s" % (lp_url, e.reason), e)
58+ return json_data
59+
60+def _get_https_content_pycurl(lp_url):
61+ # this is the fallback code for python2
62+ try:
63+ callback = CurlCallback()
64+ curl = pycurl.Curl()
65+ curl.setopt(pycurl.SSL_VERIFYPEER, 1)
66+ curl.setopt(pycurl.SSL_VERIFYHOST, 2)
67+ curl.setopt(pycurl.WRITEFUNCTION, callback.body_callback)
68+ if LAUNCHPAD_PPA_CERT:
69+ curl.setopt(pycurl.CAINFO, LAUNCHPAD_PPA_CERT)
70+ curl.setopt(pycurl.URL, str(lp_url))
71+ curl.setopt(pycurl.HTTPHEADER, ["Accept: application/json"])
72+ curl.perform()
73+ curl.close()
74+ json_data = callback.contents
75+ except pycurl.error as e:
76+ raise PPAException("Error reading %s: %s" % (lp_url, e), e)
77 return json.loads(json_data)
78
79-
80 def verify_keyid_is_v4(signing_key_fingerprint):
81 """Verify that the keyid is a v4 fingerprint with at least 160bit"""
82 return len(signing_key_fingerprint) >= 160/8
844. By Sami Jaktholm

Don't use pycurl when running with python3.

Revision history for this message
Sami Jaktholm (sjakthol) wrote :

The related bug report shows that the fallback code is activated due to invalid response (http.client.BadStatusLine: ''). Looking at the urrlib.request code around the exception it only seems to convert socket.error to URLError but exceptions from httplib propagate all the way back to clients. These exceptions then trigger the pycurl code that fails in python3.

But anyways, I'll change this to fit your suggestion (it's way cleaner than the current try/except mess).

Revision history for this message
Michael Vogt (mvo) wrote :

On Mon, Apr 08, 2013 at 12:17:22PM -0000, Sami Jaktholm wrote:
> The related bug report shows that the fallback code is activated due to invalid response (http.client.BadStatusLine: ''). Looking at the urrlib.request code around the exception it only seems to convert socket.error to URLError but exceptions from httplib propagate all the way back to clients. These exceptions then trigger the pycurl code that fails in python3.
>
> But anyways, I'll change this to fit your suggestion (it's way cleaner than the current try/except mess).

Great! Thanks a lot and let me know once the merge proposal is updated :)

Revision history for this message
Michael Vogt (mvo) wrote :

On Mon, Apr 08, 2013 at 12:17:22PM -0000, Sami Jaktholm wrote:
> The related bug report shows that the fallback code is activated due to invalid response (http.client.BadStatusLine: ''). Looking at the urrlib.request code around the exception it only seems to convert socket.error to URLError but exceptions from httplib propagate all the way back to clients. These exceptions then trigger the pycurl code that fails in python3.
>
> But anyways, I'll change this to fit your suggestion (it's way cleaner than the current try/except mess).

Great! Thanks a lot and let me know once the merge proposal is updated :)

845. By Sami Jaktholm

Catch http.client.HTTPExceptions from urlopen and raise a PPAException to show a formatted error message instead of a traceback.

Revision history for this message
Sami Jaktholm (sjakthol) wrote :

Changed the python3 code to also catch exceptions from http.client as they are mostly caused by connection issues. Now a "check your connection" message is shown instead of a long traceback (this was the case where the pycurl code was triggered). Other exceptions are not caught.

Also updated the commit message so this should be ready to go.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks Sami! I like this change, +1 from me.

review: Approve
Revision history for this message
Sami Jaktholm (sjakthol) wrote :

Ping. Still waiting to be merged...

Revision history for this message
Brian Murray (brian-murray) wrote :

I've gone ahead and merged this, thanks!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'softwareproperties/ppa.py'
2--- softwareproperties/ppa.py 2013-02-27 20:15:21 +0000
3+++ softwareproperties/ppa.py 2013-04-09 07:26:20 +0000
4@@ -35,7 +35,10 @@
5 import urllib.request
6 from urllib.error import URLError
7 import urllib.parse
8+ from http.client import HTTPException
9+ NEED_PYCURL = False
10 except ImportError:
11+ NEED_PYCURL = True
12 import pycurl
13
14 DEFAULT_KEYSERVER = "hkp://keyserver.ubuntu.com:80/"
15@@ -92,34 +95,43 @@
16
17 def get_ppa_info_from_lp(owner_name, ppa_name):
18 lp_url = LAUNCHPAD_PPA_API % (owner_name, ppa_name)
19- try:
20- try:
21- request = urllib.request.Request(str(lp_url), headers={"Accept":" application/json"})
22- lp_page = urllib.request.urlopen(request, cafile=LAUNCHPAD_PPA_CERT)
23- json_data = lp_page.read().decode("utf-8", "strict")
24- except URLError as e:
25- raise PPAException("Error reading %s: %s" % (lp_url, e.reason), e)
26- except PPAException:
27- raise
28- except:
29- import pycurl
30- try:
31- callback = CurlCallback()
32- curl = pycurl.Curl()
33- curl.setopt(pycurl.SSL_VERIFYPEER, 1)
34- curl.setopt(pycurl.SSL_VERIFYHOST, 2)
35- curl.setopt(pycurl.WRITEFUNCTION, callback.body_callback)
36- if LAUNCHPAD_PPA_CERT:
37- curl.setopt(pycurl.CAINFO, LAUNCHPAD_PPA_CERT)
38- curl.setopt(pycurl.URL, str(lp_url))
39- curl.setopt(pycurl.HTTPHEADER, ["Accept: application/json"])
40- curl.perform()
41- curl.close()
42- json_data = callback.contents
43- except pycurl.error as e:
44- raise PPAException("Error reading %s: %s" % (lp_url, e), e)
45- return json.loads(json_data)
46-
47+ if NEED_PYCURL:
48+ # python2 has no cert verification so we need pycurl
49+ return _get_https_content_pycurl(lp_url)
50+ else:
51+ # python3 has cert verification so we can use the buildin urllib
52+ return _get_https_content_py3(lp_url)
53+
54+def _get_https_content_py3(lp_url):
55+ try:
56+ request = urllib.request.Request(str(lp_url), headers={"Accept":" application/json"})
57+ lp_page = urllib.request.urlopen(request, cafile=LAUNCHPAD_PPA_CERT)
58+ json_data = lp_page.read().decode("utf-8", "strict")
59+ except (URLError, HTTPException) as e:
60+ # HTTPException doesn't have a reason but might have a string
61+ # representation
62+ reason = hasattr(e, "reason") and e.reason or e
63+ raise PPAException("Error reading %s: %s" % (lp_url, reason), e)
64+ return json.loads(json_data)
65+
66+def _get_https_content_pycurl(lp_url):
67+ # this is the fallback code for python2
68+ try:
69+ callback = CurlCallback()
70+ curl = pycurl.Curl()
71+ curl.setopt(pycurl.SSL_VERIFYPEER, 1)
72+ curl.setopt(pycurl.SSL_VERIFYHOST, 2)
73+ curl.setopt(pycurl.WRITEFUNCTION, callback.body_callback)
74+ if LAUNCHPAD_PPA_CERT:
75+ curl.setopt(pycurl.CAINFO, LAUNCHPAD_PPA_CERT)
76+ curl.setopt(pycurl.URL, str(lp_url))
77+ curl.setopt(pycurl.HTTPHEADER, ["Accept: application/json"])
78+ curl.perform()
79+ curl.close()
80+ json_data = callback.contents
81+ except pycurl.error as e:
82+ raise PPAException("Error reading %s: %s" % (lp_url, e), e)
83+ return json.loads(json_data)
84
85 def verify_keyid_is_v4(signing_key_fingerprint):
86 """Verify that the keyid is a v4 fingerprint with at least 160bit"""

Subscribers

People subscribed via source and target branches

to status/vote changes: