Merge lp:~cjwatson/launchpad/cveimport-requests-handles-gzip into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18683
Proposed branch: lp:~cjwatson/launchpad/cveimport-requests-handles-gzip
Merge into: lp:launchpad
Diff against target: 148 lines (+87/-24)
2 files modified
lib/lp/bugs/scripts/cveimport.py (+24/-24)
lib/lp/bugs/scripts/tests/test_cveimport.py (+63/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/cveimport-requests-handles-gzip
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+347465@code.launchpad.net

Commit message

Fix handling of CVE database URLs returning data with Content-Encoding: gzip.

Description of the change

I broke this in https://code.launchpad.net/~cjwatson/launchpad/cveimport-requests/+merge/347201. We have to be a bit cunning to arrange that the file:// URL test in cve-update.txt still works.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/scripts/cveimport.py'
2--- lib/lp/bugs/scripts/cveimport.py 2018-05-31 13:14:12 +0000
3+++ lib/lp/bugs/scripts/cveimport.py 2018-06-05 17:07:09 +0000
4@@ -8,7 +8,7 @@
5 __metaclass__ = type
6
7 import gzip
8-import StringIO
9+import io
10 import time
11 import xml.etree.cElementTree as cElementTree
12
13@@ -198,7 +198,7 @@
14 self.parser.add_option(
15 "-u", "--cveurl", dest="cveurl",
16 default=config.cveupdater.cve_db_url,
17- help="The URL for the gzipped XML CVE database.")
18+ help="The URL for the XML CVE database.")
19
20 def main(self):
21 self.logger.info('Initializing...')
22@@ -209,29 +209,8 @@
23 raise LaunchpadScriptFailure(
24 'Unable to open CVE database in %s'
25 % self.options.cvefile)
26-
27 elif self.options.cveurl is not None:
28- self.logger.info("Downloading CVE database from %s..." %
29- self.options.cveurl)
30- proxies = {}
31- if config.launchpad.http_proxy:
32- proxies['http'] = config.launchpad.http_proxy
33- proxies['https'] = config.launchpad.http_proxy
34- try:
35- with override_timeout(config.cveupdater.timeout):
36- # Command-line options are trusted, so allow file://
37- # URLs to ease testing.
38- response = urlfetch(
39- self.options.cveurl, proxies=proxies, allow_file=True)
40- except requests.RequestException:
41- raise LaunchpadScriptFailure(
42- 'Unable to connect for CVE database %s'
43- % self.options.cveurl)
44-
45- cve_db_gz = response.content
46- self.logger.info("%d bytes downloaded." % len(cve_db_gz))
47- cve_db = gzip.GzipFile(
48- fileobj=StringIO.StringIO(cve_db_gz)).read()
49+ cve_db = self.fetchCVEURL(self.options.cveurl)
50 else:
51 raise LaunchpadScriptFailure('No CVE database file or URL given.')
52
53@@ -243,6 +222,27 @@
54 self.logger.info('%d seconds to update database.'
55 % (finish_time - start_time))
56
57+ def fetchCVEURL(self, url):
58+ """Fetch CVE data from a URL, decompressing if necessary."""
59+ self.logger.info("Downloading CVE database from %s..." % url)
60+ try:
61+ with override_timeout(config.cveupdater.timeout):
62+ # Command-line options are trusted, so allow file://
63+ # URLs to ease testing.
64+ response = urlfetch(url, use_proxy=True, allow_file=True)
65+ except requests.RequestException:
66+ raise LaunchpadScriptFailure(
67+ 'Unable to connect for CVE database %s' % url)
68+
69+ cve_db = response.content
70+ self.logger.info("%d bytes downloaded." % len(cve_db))
71+ # requests will normally decompress this automatically, but that
72+ # might not be the case if we're given a file:// URL to a gzipped
73+ # file.
74+ if cve_db[:2] == b'\037\213': # gzip magic
75+ cve_db = gzip.GzipFile(fileobj=io.BytesIO(cve_db)).read()
76+ return cve_db
77+
78 def processCVEXML(self, cve_xml):
79 """Process the CVE XML file.
80
81
82=== added file 'lib/lp/bugs/scripts/tests/test_cveimport.py'
83--- lib/lp/bugs/scripts/tests/test_cveimport.py 1970-01-01 00:00:00 +0000
84+++ lib/lp/bugs/scripts/tests/test_cveimport.py 2018-06-05 17:07:09 +0000
85@@ -0,0 +1,63 @@
86+# Copyright 2018 Canonical Ltd. This software is licensed under the
87+# GNU Affero General Public License version 3 (see the file LICENSE).
88+
89+from __future__ import absolute_import, print_function, unicode_literals
90+
91+__metaclass__ = type
92+
93+import gzip
94+import io
95+
96+import responses
97+
98+from lp.bugs.scripts.cveimport import CVEUpdater
99+from lp.services.log.logger import DevNullLogger
100+from lp.testing import TestCase
101+
102+
103+class TestCVEUpdater(TestCase):
104+
105+ @responses.activate
106+ def test_fetch_uncompressed(self):
107+ # Fetching a URL returning uncompressed data works.
108+ url = 'http://cve.example.com/allitems.xml'
109+ body = b'<?xml version="1.0"?>'
110+ responses.add(
111+ 'GET', url, headers={'Content-Type': 'text/xml'}, body=body)
112+ cve_updater = CVEUpdater(
113+ 'cve-updater', test_args=[], logger=DevNullLogger())
114+ self.assertEqual(body, cve_updater.fetchCVEURL(url))
115+
116+ @responses.activate
117+ def test_fetch_content_encoding_gzip(self):
118+ # Fetching a URL returning Content-Encoding: gzip works.
119+ url = 'http://cve.example.com/allitems.xml.gz'
120+ body = b'<?xml version="1.0"?>'
121+ gzipped_body_file = io.BytesIO()
122+ with gzip.GzipFile(fileobj=gzipped_body_file, mode='wb') as f:
123+ f.write(body)
124+ responses.add(
125+ 'GET', url,
126+ headers={
127+ 'Content-Type': 'text/xml',
128+ 'Content-Encoding': 'gzip',
129+ },
130+ body=gzipped_body_file.getvalue())
131+ cve_updater = CVEUpdater(
132+ 'cve-updater', test_args=[], logger=DevNullLogger())
133+ self.assertEqual(body, cve_updater.fetchCVEURL(url))
134+
135+ @responses.activate
136+ def test_fetch_gzipped(self):
137+ # Fetching a URL returning gzipped data without Content-Encoding works.
138+ url = 'http://cve.example.com/allitems.xml.gz'
139+ body = b'<?xml version="1.0"?>'
140+ gzipped_body_file = io.BytesIO()
141+ with gzip.GzipFile(fileobj=gzipped_body_file, mode='wb') as f:
142+ f.write(body)
143+ responses.add(
144+ 'GET', url, headers={'Content-Type': 'application/x-gzip'},
145+ body=gzipped_body_file.getvalue())
146+ cve_updater = CVEUpdater(
147+ 'cve-updater', test_args=[], logger=DevNullLogger())
148+ self.assertEqual(body, cve_updater.fetchCVEURL(url))