Skip to content

Commit e9f1ea8

Browse files
authored
feat: added a function to utilize purl integration (#4164)
modified python language parser, tailored purls according to the purl2cpe requirement, resolves docutils name collision Signed-off-by: Meet Soni <[email protected]>
1 parent 94e5a2d commit e9f1ea8

File tree

2 files changed

+90
-11
lines changed

2 files changed

+90
-11
lines changed

cve_bin_tool/parsers/__init__.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Copyright (C) 2022 Intel Corporation
22
# SPDX-License-Identifier: GPL-3.0-or-later
33

4+
import re
5+
import sqlite3
6+
from pathlib import Path
7+
from typing import List, Tuple
8+
49
from packageurl import PackageURL
510

11+
from cve_bin_tool.error_handler import CVEDBError
612
from cve_bin_tool.util import ProductInfo, ScanInfo
713

814
__all__ = [
@@ -89,3 +95,62 @@ def generate_purl(self, product, vendor, qualifier={}, subpath=None):
8995
subpath=subpath,
9096
)
9197
return purl
98+
99+
def find_vendor_from_purl(self, purl, ver) -> Tuple[List[ScanInfo], bool]:
100+
"""
101+
Finds the vendor information for a given PackageURL (purl) and version from the database.
102+
103+
This method queries the database to retrieve Common Platform Enumeration (CPE) data associated with the given purl.
104+
It then decodes the CPE data to extract vendor, product, and version information. If the version matches the provided
105+
version, it constructs a ScanInfo object for each matching entry and returns a list of these objects.
106+
"""
107+
108+
query = "SELECT cpe from purl2cpe WHERE purl=?"
109+
cursor = self.db_open_and_get_cursor()
110+
cursor.execute(query, [str(purl)])
111+
cpeList = cursor.fetchall()
112+
vendorlist: list[ScanInfo] = []
113+
vendors = set()
114+
115+
if cpeList != []:
116+
for item in cpeList:
117+
vendor, product, version = self.decode_cpe23(str(item))
118+
vendors.add((vendor, product))
119+
else:
120+
return vendorlist, False
121+
122+
for vendor, product in vendors:
123+
vendorlist.append(
124+
ScanInfo(
125+
ProductInfo(vendor, product, ver, "/usr/local/bin/product"),
126+
self.filename,
127+
)
128+
)
129+
130+
return vendorlist, True
131+
132+
def db_open_and_get_cursor(self) -> sqlite3.Cursor:
133+
"""Opens connection to sqlite database, returns cursor object."""
134+
135+
dbpath = (
136+
Path("~").expanduser() / ".cache" / "cve-bin-tool" / "purl2cpe/purl2cpe.db"
137+
)
138+
connection = sqlite3.connect(dbpath)
139+
140+
if connection is not None:
141+
cursor = connection.cursor()
142+
if cursor is None:
143+
raise CVEDBError
144+
return cursor
145+
146+
def decode_cpe23(self, cpe23) -> Tuple[str, str, str]:
147+
"""
148+
Decodes a CPE 2.3 formatted string to extract vendor, product, and version information.
149+
150+
"""
151+
152+
# split on `:` only if it's not escaped
153+
cpe = re.split(r"(?<!\\):", cpe23)
154+
vendor, product, version = cpe[3], cpe[4], cpe[5]
155+
# Return available data, convert empty fields to None
156+
return (vendor, product, version)

cve_bin_tool/parsers/python.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ def run_checker(self, filename):
9696
for line in lines["install"]:
9797
product = line["metadata"]["name"]
9898
version = line["metadata"]["version"]
99-
vendor = self.find_vendor(product, version)
99+
purl = self.generate_purl(product, "")
100+
vendor, result = self.find_vendor_from_purl(purl, version)
101+
102+
if not result:
103+
vendor = self.find_vendor(product, version)
100104

101105
if vendor is not None:
102106
yield from vendor
@@ -143,16 +147,26 @@ def run_checker(self, filename):
143147
try:
144148
product = search(compile(r"^Name: (.+)$", MULTILINE), lines).group(1)
145149
version = search(compile(r"^Version: (.+)$", MULTILINE), lines).group(1)
146-
vendor_package_pair = self.cve_db.get_vendor_product_pairs(product)
147-
if vendor_package_pair != []:
148-
for pair in vendor_package_pair:
149-
vendor = pair["vendor"]
150-
location = pair.get("location", "/usr/local/bin/product")
151-
file_path = self.filename
152-
self.logger.debug(f"{file_path} is {vendor}.{product} {version}")
153-
yield ScanInfo(
154-
ProductInfo(vendor, product, version, location), file_path
155-
)
150+
purl = self.generate_purl(product, "")
151+
vendor, result = self.find_vendor_from_purl(purl, version)
152+
153+
if vendor is not None:
154+
yield from vendor
155+
156+
if not result:
157+
vendor_package_pair = self.cve_db.get_vendor_product_pairs(product)
158+
if vendor_package_pair != []:
159+
for pair in vendor_package_pair:
160+
vendor = pair["vendor"]
161+
location = pair.get("location", "/usr/local/bin/product")
162+
file_path = self.filename
163+
self.logger.debug(
164+
f"{file_path} is {vendor}.{product} {version}"
165+
)
166+
yield ScanInfo(
167+
ProductInfo(vendor, product, version, location), file_path
168+
)
169+
156170
# There are packages with a METADATA file in them containing different data from what the tool expects
157171
except AttributeError:
158172
self.logger.debug(f"{filename} is an invalid METADATA/PKG-INFO")

0 commit comments

Comments
 (0)