1919# for any legal advice.
2020# VulnerableCode is a free software code scanning tool from nexB Inc. and others.
2121# Visit https://github.com/nexB/vulnerablecode/ for support and download.
22-
2322import asyncio
2423import dataclasses
25- import pytz
2624import xml .etree .ElementTree as ET
27- from bs4 import BeautifulSoup
28- from dateutil import parser as dateparser
25+ from datetime import datetime
2926from json import JSONDecodeError
27+ from typing import List
3028from typing import Mapping
3129from typing import Set
32- from typing import List
33- from datetime import datetime
3430
3531from aiohttp import ClientSession
3632from aiohttp .client_exceptions import ClientResponseError
3733from aiohttp .client_exceptions import ServerDisconnectedError
34+ from bs4 import BeautifulSoup
35+ from dateutil import parser as dateparser
3836
3937
4038@dataclasses .dataclass (frozen = True )
@@ -65,6 +63,18 @@ def get(self, package_name, until=None) -> Set[str]:
6563
6664 return VersionResponse (valid_versions = valid_versions , newer_versions = new_versions )
6765
66+ async def load_api (self , pkg_set ):
67+ async with client_session () as session :
68+ await asyncio .gather (
69+ * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
70+ )
71+
72+ async def fetch (self , pkg , session ):
73+ """
74+ Override this method to fetch the pkg's version in the cache
75+ """
76+ raise NotImplementedError
77+
6878
6979def client_session ():
7080 return ClientSession (raise_for_status = True , trust_env = True )
@@ -74,15 +84,7 @@ class LaunchpadVersionAPI(VersionAPI):
7484
7585 package_type = "deb"
7686
77- async def load_api (self , pkg_set ):
78- async with client_session () as session :
79- await asyncio .gather (
80- * [self .set_api (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
81- )
82-
83- async def set_api (self , pkg , session ):
84- if pkg in self .cache :
85- return
87+ async def fetch (self , pkg , session ):
8688 url = (
8789 "https://api.launchpad.net/1.0/ubuntu/+archive/"
8890 "primary?ws.op=getPublishedSources&"
@@ -111,12 +113,6 @@ class PypiVersionAPI(VersionAPI):
111113
112114 package_type = "pypi"
113115
114- async def load_api (self , pkg_set ):
115- async with client_session () as session :
116- await asyncio .gather (
117- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
118- )
119-
120116 async def fetch (self , pkg , session ):
121117 url = f"https://pypi.org/pypi/{ pkg } /json"
122118 versions = set ()
@@ -150,12 +146,6 @@ class CratesVersionAPI(VersionAPI):
150146
151147 package_type = "cargo"
152148
153- async def load_api (self , pkg_set ):
154- async with client_session () as session :
155- await asyncio .gather (
156- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
157- )
158-
159149 async def fetch (self , pkg , session ):
160150 url = f"https://crates.io/api/v1/crates/{ pkg } "
161151 response = await session .request (method = "GET" , url = url )
@@ -176,12 +166,6 @@ class RubyVersionAPI(VersionAPI):
176166
177167 package_type = "gem"
178168
179- async def load_api (self , pkg_set ):
180- async with client_session () as session :
181- await asyncio .gather (
182- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
183- )
184-
185169 async def fetch (self , pkg , session ):
186170 url = f"https://rubygems.org/api/v1/versions/{ pkg } .json"
187171 versions = set ()
@@ -205,12 +189,6 @@ class NpmVersionAPI(VersionAPI):
205189
206190 package_type = "npm"
207191
208- async def load_api (self , pkg_set ):
209- async with client_session () as session :
210- await asyncio .gather (
211- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
212- )
213-
214192 async def fetch (self , pkg , session ):
215193 url = f"https://registry.npmjs.org/{ pkg } "
216194 versions = set ()
@@ -242,12 +220,10 @@ async def load_api(self, pkg_set):
242220 raise_for_status = True , headers = {"Connection" : "keep-alive" }
243221 ) as session :
244222 await asyncio .gather (
245- * [self .set_api (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
223+ * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
246224 )
247225
248- async def set_api (self , pkg , session , retry_count = 5 ):
249- if pkg in self .cache :
250- return
226+ async def fetch (self , pkg , session , retry_count = 5 ):
251227 url = "https://sources.debian.org/api/src/{}" .format (pkg )
252228 try :
253229 all_versions = set ()
@@ -271,12 +247,6 @@ class MavenVersionAPI(VersionAPI):
271247
272248 package_type = "maven"
273249
274- async def load_api (self , pkg_set ):
275- async with client_session () as session :
276- await asyncio .gather (
277- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
278- )
279-
280250 async def fetch (self , pkg , session ) -> None :
281251 artifact_comps = pkg .split (":" )
282252 endpoint = self .artifact_url (artifact_comps )
@@ -327,12 +297,6 @@ class NugetVersionAPI(VersionAPI):
327297
328298 package_type = "nuget"
329299
330- async def load_api (self , pkg_set ):
331- async with client_session () as session :
332- await asyncio .gather (
333- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
334- )
335-
336300 async def fetch (self , pkg , session ) -> None :
337301 endpoint = self .nuget_url (pkg )
338302 resp = await session .request (method = "GET" , url = endpoint )
@@ -370,12 +334,6 @@ class ComposerVersionAPI(VersionAPI):
370334
371335 package_type = "composer"
372336
373- async def load_api (self , pkg_set ):
374- async with client_session () as session :
375- await asyncio .gather (
376- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
377- )
378-
379337 async def fetch (self , pkg , session ) -> None :
380338 endpoint = self .composer_url (pkg )
381339 if endpoint :
@@ -415,30 +373,16 @@ class GitHubTagsAPI(VersionAPI):
415373
416374 package_type = "github"
417375
418- async def load_api (self , repo_set ):
419- session = client_session ()
420- async with session as session :
421- await asyncio .gather (
422- * [
423- self .fetch (owner_repo .lower ())
424- for owner_repo in repo_set
425- if owner_repo .lower () not in self .cache
426- ]
427- )
428-
429- async def fetch (self , owner_repo : str , endpoint = None ) -> None :
376+ async def fetch (self , owner_repo : str , session ) -> None :
430377 """
431378 owner_repo is a string of format "{repo_owner}/{repo_name}"
432379 Example value of owner_repo = "nexB/scancode-toolkit"
433380 """
434- if owner_repo not in self .cache :
435- self . cache [ owner_repo ] = set ()
381+ self .cache [ owner_repo ] = set ()
382+ endpoint = f"https://github.com/ { owner_repo } /tags"
436383
437- if not endpoint :
438- endpoint = f"https://github.com/{ owner_repo } /tags"
439- async with client_session () as session :
440- resp = await session .get (endpoint )
441- resp = await resp .read ()
384+ resp = await session .get (endpoint )
385+ resp = await resp .read ()
442386
443387 soup = BeautifulSoup (resp , features = "lxml" )
444388 for release_entry in soup .find_all ("div" , {"class" : "commit" }):
@@ -467,12 +411,6 @@ async def fetch(self, owner_repo: str, endpoint=None) -> None:
467411
468412
469413class HexVersionAPI (VersionAPI ):
470- async def load_api (self , pkg_set ):
471- async with client_session () as session :
472- await asyncio .gather (
473- * [self .fetch (pkg , session ) for pkg in pkg_set if pkg not in self .cache ]
474- )
475-
476414 async def fetch (self , pkg , session ):
477415 url = f"https://hex.pm/api/packages/{ pkg } "
478416 versions = set ()
0 commit comments