Skip to content

Commit 94e5a2d

Browse files
authored
feat: add purl2cpe as a data source (#4179)
* feat: added purl2cpe as a data source * feat: Separated data source integration from previous PR Signed-off-by: Meet Soni <[email protected]>
1 parent 49883ec commit 94e5a2d

File tree

4 files changed

+86
-5
lines changed

4 files changed

+86
-5
lines changed

cve_bin_tool/cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
gad_source,
4949
nvd_source,
5050
osv_source,
51+
purl2cpe_source,
5152
redhat_source,
5253
)
5354
from cve_bin_tool.error_handler import (
@@ -722,6 +723,10 @@ def main(argv=None):
722723
source_curl = curl_source.Curl_Source()
723724
enabled_sources.append(source_curl)
724725

726+
if "PURL2CPE" not in disabled_sources:
727+
source_purl2cpe = purl2cpe_source.PURL2CPE_Source()
728+
enabled_sources.append(source_purl2cpe)
729+
725730
if "NVD" not in disabled_sources:
726731
source_nvd = nvd_source.NVD_Source(
727732
nvd_type=nvd_type,

cve_bin_tool/cvedb.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
gad_source,
2929
nvd_source,
3030
osv_source,
31+
purl2cpe_source,
3132
)
3233
from cve_bin_tool.error_handler import ERROR_CODES, CVEDBError, ErrorMode, SigningError
3334
from cve_bin_tool.fetch_json_db import Fetch_JSON_DB
@@ -57,6 +58,7 @@ class CVEDB:
5758
curl_source.Curl_Source,
5859
osv_source.OSV_Source,
5960
gad_source.GAD_Source,
61+
purl2cpe_source.PURL2CPE_Source,
6062
nvd_source.NVD_Source, # last to avoid data overwrites
6163
]
6264

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from __future__ import annotations
2+
3+
import zipfile
4+
from io import BytesIO
5+
from pathlib import Path
6+
7+
import aiohttp
8+
9+
from cve_bin_tool.data_sources import DISK_LOCATION_DEFAULT, Data_Source
10+
from cve_bin_tool.error_handler import ErrorMode
11+
from cve_bin_tool.log import LOGGER
12+
from cve_bin_tool.version import HTTP_HEADERS
13+
14+
15+
class PURL2CPE_Source(Data_Source):
16+
"""Class to retrieve purl-cpe mapping database (PURL2CPE)"""
17+
18+
SOURCE = "PURL2CPE"
19+
CACHEDIR = DISK_LOCATION_DEFAULT
20+
LOGGER = LOGGER.getChild("CVEDB")
21+
PURL2CPE_URL = "https://github.com/scanoss/purl2cpe/raw/main/purl2cpe.db.zip"
22+
23+
def __init__(
24+
self, error_mode: ErrorMode = ErrorMode.TruncTrace, incremental_update=False
25+
):
26+
self.cachedir = self.CACHEDIR
27+
self.purl2cpe_path = str(Path(self.cachedir) / "purl2cpe")
28+
self.source_name = self.SOURCE
29+
self.error_mode = error_mode
30+
self.incremental_update = incremental_update
31+
self.purl2cpe_url = self.PURL2CPE_URL
32+
self.session = None
33+
34+
async def fetch_cves(self):
35+
"""Fetches PURL2CPE database and places it in purl2cpe_path."""
36+
LOGGER.info("Getting PURL2CPE data...")
37+
38+
if not Path(self.purl2cpe_path).exists():
39+
Path(self.purl2cpe_path).mkdir()
40+
41+
if not self.session:
42+
connector = aiohttp.TCPConnector(limit_per_host=10)
43+
self.session = aiohttp.ClientSession(
44+
connector=connector, headers=HTTP_HEADERS, trust_env=True
45+
)
46+
47+
try:
48+
response = await self.session.get(self.purl2cpe_url)
49+
if response.status == 200:
50+
data = await response.read()
51+
with zipfile.ZipFile(BytesIO(data), "r") as zip_ref:
52+
zip_ref.extractall(self.purl2cpe_path)
53+
else:
54+
LOGGER.debug(f"Failed to download file. Status code: {response.status}")
55+
56+
except Exception as e:
57+
LOGGER.debug(f"Error fetching PURL2CPE data: {e}")
58+
59+
await self.session.close()
60+
self.session = None
61+
62+
async def get_cve_data(self):
63+
"""Fetches PURL2CPE Database."""
64+
# skip if connection fails
65+
try:
66+
await self.fetch_cves()
67+
except Exception as e:
68+
LOGGER.debug(f"Error while fetching PURL2CPE Data: {e}")
69+
LOGGER.error("Unable to fetch PURL2CPE Data, skipping PURL2CPE.")
70+
if self.session is not None:
71+
await self.session.close()
72+
return (list(), list()), self.source_name
73+
74+
if self.session is not None:
75+
await self.session.close()
76+
return (list(), list()), self.source_name

cve_bin_tool/parsers/python.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ class PythonRequirementsParser(Parser):
2222

2323
def __init__(self, cve_db, logger):
2424
"""Initialize the python requirements file parser."""
25-
self.purl_pkg_type = "pypi"
2625
super().__init__(cve_db, logger)
26+
self.purl_pkg_type = "pypi"
2727

2828
def generate_purl(self, product, vendor, qualifier={}, subpath=None):
2929
"""Generates PURL after normalizing all components."""
3030
product = re.sub(r"[^a-zA-Z0-9._-]", "", product).lower()
31-
vendor = "UNKNOWN"
3231

3332
if not product:
3433
return None
@@ -98,6 +97,7 @@ def run_checker(self, filename):
9897
product = line["metadata"]["name"]
9998
version = line["metadata"]["version"]
10099
vendor = self.find_vendor(product, version)
100+
101101
if vendor is not None:
102102
yield from vendor
103103
self.logger.debug(f"Done scanning file: {self.filename}")
@@ -112,13 +112,12 @@ class PythonParser(Parser):
112112

113113
def __init__(self, cve_db, logger):
114114
"""Initialize the python package metadata parser."""
115-
self.purl_pkg_type = "pypi"
116115
super().__init__(cve_db, logger)
116+
self.purl_pkg_type = "pypi"
117117

118118
def generate_purl(self, product, vendor, qualifier={}, subpath=None):
119119
"""Generates PURL after normalizing all components."""
120120
product = re.sub(r"[^a-zA-Z0-9._-]", "", product).lower()
121-
vendor = "UNKNOWN"
122121

123122
if not product:
124123
return None
@@ -154,7 +153,6 @@ def run_checker(self, filename):
154153
yield ScanInfo(
155154
ProductInfo(vendor, product, version, location), file_path
156155
)
157-
158156
# There are packages with a METADATA file in them containing different data from what the tool expects
159157
except AttributeError:
160158
self.logger.debug(f"{filename} is an invalid METADATA/PKG-INFO")

0 commit comments

Comments
 (0)