1+ import dataclasses
2+ import logging
13import traceback
24import xml .etree .ElementTree as ET
5+ from datetime import datetime
36from json import JSONDecodeError
47from typing import List
58from typing import MutableMapping
69from typing import Optional
710from typing import Set
811
9- from aiohttp import ClientResponseError
10- from aiohttp import ClientSession
11- from aiohttp .web_exceptions import HTTPGone
12+ import requests
1213from dateutil import parser as dateparser
1314from django .utils .dateparse import parse_datetime
1415
1516from vulnerabilities .package_managers import Version as LegacyVersion
1617from vulnerabilities .package_managers import VersionResponse
1718
19+ LOGGER = logging .getLogger (__name__ )
20+
21+
22+ @dataclasses .dataclass (frozen = True )
23+ class Version :
24+ value : str
25+ release_date : Optional [datetime ] = None
26+
27+
28+ def get_response (url , type = "json" ):
29+ resp = requests .get (url = url )
30+ type_of_response = {
31+ "json" : resp .json (),
32+ "text" : resp .text ,
33+ "read" : resp .content ,
34+ }
35+ if resp .status_code == 200 :
36+ return type_of_response [type ]
37+ else :
38+ LOGGER .error (f"Error while fetching { url } : { resp .status_code } " )
39+ return None
40+
1841
1942class VersionAPI :
2043 def __init__ (self , cache : MutableMapping [str , Set [LegacyVersion ]] = None ):
@@ -35,13 +58,12 @@ def load_api(self, pkg_set):
3558 """
3659 Populate the cache with the versions of the packages in pkg_set
3760 """
38- with ClientSession () as session :
39- for pkg in pkg_set :
40- if pkg in self .cache :
41- continue
42- self .fetch (pkg , session )
61+ for pkg in pkg_set :
62+ if pkg in self .cache :
63+ continue
64+ self .fetch (pkg )
4365
44- def fetch (self , pkg , session ):
66+ def fetch (self , pkg ):
4567 """
4668 Override this method to fetch the pkg's version in the cache
4769 """
@@ -52,54 +74,50 @@ class PypiVersionAPI(VersionAPI):
5274
5375 package_type = "pypi"
5476
55- def fetch (self , pkg , session ):
77+ def fetch (self , pkg ):
5678 url = f"https://pypi.org/pypi/{ pkg } /json"
5779 versions = set ()
58- try :
59- response = session . request ( method = "GET" , url = url )
60- response = response . json ()
61- for version , download_items in response [ "releases" ]. items ():
62- if download_items :
63- latest_download_item = max (
64- download_items ,
65- key = lambda download_item : dateparser . parse (
66- download_item [ "upload_time_iso_8601" ]
67- ) ,
68- )
69- versions . add (
70- LegacyVersion (
71- value = version ,
72- release_date = dateparser . parse (
73- latest_download_item [ "upload_time_iso_8601" ]
74- ) ,
75- )
80+ response = get_response ( url = url )
81+
82+ if not response :
83+ self . cache [ pkg ] = versions
84+ return
85+
86+ for version , download_items in response [ "releases" ]. items ():
87+ if download_items :
88+ latest_download_item = max (
89+ download_items ,
90+ key = lambda download_item : dateparser . parse (
91+ download_item [ "upload_time_iso_8601" ]
92+ ),
93+ )
94+ versions . add (
95+ LegacyVersion (
96+ value = version ,
97+ release_date = dateparser . parse ( latest_download_item [ "upload_time_iso_8601" ]),
7698 )
77- except ClientResponseError :
78- # PYPI removed this package.
79- # https://www.zdnet.com/article/twelve-malicious-python-libraries-found-and-removed-from-pypi/ # nopep8
80- pass
99+ )
81100 self .cache [pkg ] = versions
82101
83102
84103class RubyVersionAPI (VersionAPI ):
85104
86105 package_type = "gem"
87106
88- def fetch (self , pkg , session ):
107+ def fetch (self , pkg ):
89108 url = f"https://rubygems.org/api/v1/versions/{ pkg } .json"
90109 versions = set ()
91- try :
92- response = session . request ( method = "GET" , url = url )
93- response = response . json ()
94- for release in response :
95- versions . add (
96- LegacyVersion (
97- value = release [ "number" ],
98- release_date = dateparser . parse ( release ["created_at" ]) ,
99- )
110+ response = get_response ( url = url )
111+ if not response :
112+ self . cache [ pkg ] = versions
113+ return
114+ for release in response :
115+ versions . add (
116+ LegacyVersion (
117+ value = release ["number" ] ,
118+ release_date = dateparser . parse ( release [ "created_at" ]),
100119 )
101- except (ClientResponseError , JSONDecodeError ):
102- pass
120+ )
103121
104122 self .cache [pkg ] = versions
105123
@@ -108,22 +126,20 @@ class NpmVersionAPI(VersionAPI):
108126
109127 package_type = "npm"
110128
111- def fetch (self , pkg , session ):
129+ def fetch (self , pkg ):
112130 url = f"https://registry.npmjs.org/{ pkg } "
113131 versions = set ()
114- try :
115- response = session .request (method = "GET" , url = url )
116- response = response .json ()
117- for version in response .get ("versions" , []):
118- release_date = response .get ("time" , {}).get (version )
119- if release_date :
120- release_date = dateparser .parse (release_date )
121- versions .add (LegacyVersion (value = version , release_date = release_date ))
122- else :
123- versions .add (LegacyVersion (value = version , release_date = None ))
124-
125- except ClientResponseError :
126- pass
132+ response = get_response (url = url )
133+ if not response :
134+ self .cache [pkg ] = versions
135+ return
136+ for version in response .get ("versions" , []):
137+ release_date = response .get ("time" , {}).get (version )
138+ if release_date :
139+ release_date = dateparser .parse (release_date )
140+ versions .add (LegacyVersion (value = version , release_date = release_date ))
141+ else :
142+ versions .add (LegacyVersion (value = version , release_date = None ))
127143
128144 self .cache [pkg ] = versions
129145
@@ -132,14 +148,13 @@ class MavenVersionAPI(VersionAPI):
132148
133149 package_type = "maven"
134150
135- def fetch (self , pkg , session ) -> None :
151+ def fetch (self , pkg ) -> None :
136152 artifact_comps = pkg .split (":" )
137153 endpoint = self .artifact_url (artifact_comps )
138- try :
139- resp = session .request (method = "GET" , url = endpoint )
140- resp = resp .read ()
141154
142- except ClientResponseError :
155+ resp = get_response (url = endpoint , type = "read" )
156+
157+ if not resp :
143158 self .cache [pkg ] = set ()
144159 return
145160
@@ -182,10 +197,12 @@ class NugetVersionAPI(VersionAPI):
182197
183198 package_type = "nuget"
184199
185- def fetch (self , pkg , session ) -> None :
200+ def fetch (self , pkg ) -> None :
186201 endpoint = self .nuget_url (pkg )
187- resp = session .request (method = "GET" , url = endpoint )
188- resp = resp .json ()
202+ resp = get_response (url = endpoint )
203+ if not resp :
204+ self .cache [pkg ] = set ()
205+ return
189206 self .cache [pkg ] = self .extract_versions (resp )
190207
191208 @staticmethod
@@ -271,9 +288,7 @@ def escape_path(path: str) -> str:
271288 return escaped_path
272289
273290 @staticmethod
274- def parse_version_info (
275- version_info : str , escaped_pkg : str , session : ClientSession
276- ) -> Optional [LegacyVersion ]:
291+ def parse_version_info (version_info : str , escaped_pkg : str ) -> Optional [LegacyVersion ]:
277292 v = version_info .split ()
278293 if not v :
279294 return None
@@ -283,33 +298,28 @@ def parse_version_info(
283298 release_date = parse_datetime (v [1 ])
284299 else :
285300 escaped_ver = GoproxyVersionAPI .escape_path (value )
286- try :
287- response = session .request (
288- method = "GET" ,
289- url = f"https://proxy.golang.org/{ escaped_pkg } /@v/{ escaped_ver } .info" ,
290- )
291- resp_json = response .json ()
292- release_date = parse_datetime (resp_json .get ("Time" , "" ))
293- except :
301+ resp_json = get_response (
302+ url = f"https://proxy.golang.org/{ escaped_pkg } /@v/{ escaped_ver } .info"
303+ )
304+ if not resp_json :
294305 traceback .print_exc ()
295306 print (
296307 f"error while fetching version info for { escaped_pkg } /{ escaped_ver } from goproxy"
297308 )
298- release_date = None
309+ release_date = parse_datetime (resp_json .get ("Time" , "" )) if resp_json else None
310+
299311 return LegacyVersion (value = value , release_date = release_date )
300312
301- def fetch (self , pkg : str , session : ClientSession ):
313+ def fetch (self , pkg : str ):
302314 # escape uppercase in module path
303315 escaped_pkg = GoproxyVersionAPI .escape_path (pkg )
304316 trimmed_pkg = pkg
305317 resp_text = None
306318 # resolve module name from package name, see https://go.dev/ref/mod#resolve-pkg-mod
307319 while escaped_pkg is not None :
308320 url = f"https://proxy.golang.org/{ escaped_pkg } /@v/list"
309- try :
310- response = session .request (method = "GET" , url = url )
311- resp_text = response .text ()
312- except HTTPGone :
321+ resp_text = get_response (url = url , type = "text" )
322+ if not resp_text :
313323 escaped_pkg = GoproxyVersionAPI .trim_url_path (escaped_pkg )
314324 trimmed_pkg = GoproxyVersionAPI .trim_url_path (trimmed_pkg ) or ""
315325 continue
@@ -320,7 +330,7 @@ def fetch(self, pkg: str, session: ClientSession):
320330 self .module_name_by_package_name [pkg ] = trimmed_pkg
321331 versions = set ()
322332 for version_info in resp_text .split ("\n " ):
323- version = GoproxyVersionAPI .parse_version_info (version_info , escaped_pkg , session )
333+ version = GoproxyVersionAPI .parse_version_info (version_info , escaped_pkg )
324334 if version is not None :
325335 versions .add (version )
326336 self .cache [pkg ] = versions
@@ -330,11 +340,13 @@ class ComposerVersionAPI(VersionAPI):
330340
331341 package_type = "composer"
332342
333- def fetch (self , pkg , session ) -> None :
343+ def fetch (self , pkg ) -> None :
334344 endpoint = self .composer_url (pkg )
335345 if endpoint :
336- resp = session .request (method = "GET" , url = endpoint )
337- resp = resp .json ()
346+ resp = get_response (url = endpoint )
347+ if not resp :
348+ self .cache [pkg ] = set ()
349+ return
338350 self .cache [pkg ] = self .extract_versions (resp , pkg )
339351
340352 @staticmethod
0 commit comments