Skip to content

Commit

Permalink
feat: enable out of tree parsers
Browse files Browse the repository at this point in the history
Asciinema: https://asciinema.org/a/664036
Signed-off-by: John Andersen <[email protected]>
  • Loading branch information
John Andersen authored and pdxjohnny committed Jun 18, 2024
1 parent 4b87920 commit bf79cd7
Show file tree
Hide file tree
Showing 17 changed files with 181 additions and 80 deletions.
42 changes: 10 additions & 32 deletions cve_bin_tool/egg_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later

import ast
import importlib.util
import os
import sys
from io import StringIO

from setuptools import Distribution, find_packages
from setuptools import Distribution

try:
from cve_bin_tool.version import VERSION
Expand Down Expand Up @@ -59,44 +60,21 @@ def update_egg() -> None:
with StringIO() as f:
cwd = os.getcwd()
os.chdir(os.path.join(os.path.dirname(__file__), ".."))
setup_spec = importlib.util.spec_from_file_location(
"setup", os.path.join(os.path.dirname(__file__), "..", "setup.py")
)
setup_module = importlib.util.module_from_spec(setup_spec)
setup_spec.loader.exec_module(setup_module)
setup_kwargs = setup_module.setup_kwargs
sys.stdout = f
sys.stderr = f
dist = Distribution(
setup_kwargs.update(
dict(
script_name="setup.py",
script_args=["egg_info"],
name="cve-bin-tool",
version=VERSION,
packages=find_packages(
exclude=["locales", "presentation"],
),
entry_points={
"console_scripts": [
"cve-bin-tool = cve_bin_tool.cli:main",
"csv2cve = cve_bin_tool.csv2cve:main",
],
"cve_bin_tool.checker": [
"{} = cve_bin_tool.checkers.{}:{}".format(
filename.replace(".py", ""),
filename.replace(".py", ""),
"".join(
(filename.replace(".py", "") + " checker")
.replace("_", " ")
.title()
.split()
),
)
for filename in os.listdir(
os.path.join(
os.path.abspath(os.path.dirname(__file__)),
"checkers",
)
)
if filename.endswith(".py") and "__init__" not in filename
],
},
)
)
dist = Distribution(setup_kwargs)
dist.parse_command_line()
dist.run_commands()
sys.stdout = sys.__stdout__
Expand Down
1 change: 1 addition & 0 deletions cve_bin_tool/parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from cve_bin_tool.util import ProductInfo, ScanInfo

__all__ = [
"parse",
"Parser",
"java",
"javascript",
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/dart.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class DartParser(Parser):
https://dart.dev/overview
"""

PARSER_MATCH_FILENAMES = [
"pubspec.lock",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "pub"
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/go.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class GoParser(Parser):
"""

PARSER_MATCH_FILENAMES = [
"go.mod",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "golang"
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
class JavaParser(Parser):
"""Class to handle parsing Java-based Packages."""

PARSER_MATCH_FILENAMES = [
"pom.xml",
]

def __init__(self, cve_db, logger, validate=True):
super().__init__(cve_db, logger)
self.validate = validate
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
class JavascriptParser(Parser):
"""Parser for javascript's package-lock.json files"""

PARSER_MATCH_FILENAMES = [
"package-lock.json",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "npm"
Expand Down
62 changes: 33 additions & 29 deletions cve_bin_tool/parsers/parse.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
# Copyright (C) 2022 Intel Corporation
# SPDX-License-Identifier: GPL-3.0-or-later
import sys
from typing import Type

from cve_bin_tool.parsers.dart import DartParser
from cve_bin_tool.parsers.go import GoParser
from cve_bin_tool.parsers.java import JavaParser
from cve_bin_tool.parsers.javascript import JavascriptParser
from cve_bin_tool.parsers.perl import PerlParser
from cve_bin_tool.parsers.php import PhpParser
from cve_bin_tool.parsers.python import PythonParser, PythonRequirementsParser
from cve_bin_tool.parsers.r import RParser
from cve_bin_tool.parsers.ruby import RubyParser
from cve_bin_tool.parsers.rust import RustParser
from cve_bin_tool.parsers.swift import SwiftParser

valid_files = {
"pom.xml": JavaParser,
"package-lock.json": JavascriptParser,
"Cargo.lock": RustParser,
"renv.lock": RParser,
"requirements.txt": PythonRequirementsParser,
"go.mod": GoParser,
"PKG-INFO: ": PythonParser,
"METADATA: ": PythonParser,
"Gemfile.lock": RubyParser,
"Package.resolved": SwiftParser,
"composer.lock": PhpParser,
"cpanfile": PerlParser,
"pubspec.lock": DartParser,
}
if sys.version_info >= (3, 10):
from importlib import metadata as importlib_metadata
else:
import importlib_metadata

from cve_bin_tool.parsers import Parser

PARSERS_ENTRYPOINT = "cve_bin_tool.parsers"


def load_valid_files() -> dict[str, list[Type[Parser]]]:
"""Loads file parsers"""
valid_files: dict[str, list[Type[Parser]]] = {}
for entrypoint in importlib_metadata.entry_points().select(
group=PARSERS_ENTRYPOINT
):
parser_cls = entrypoint.load()
for match_filename in getattr(parser_cls, "PARSER_MATCH_FILENAMES", []):
valid_files.setdefault(match_filename, [])
valid_files[match_filename].append(parser_cls)
for match_filename in valid_files:
valid_files[match_filename] = list(set(valid_files[match_filename]))
return valid_files


valid_files = load_valid_files()


def parse(filename, output, cve_db, logger):
"""
Parses the given filename using the appropriate parser.
"""
parsers = []
for file in list(valid_files.keys()):
if file in output:
parser = valid_files[file](cve_db, logger)
yield from parser.run_checker(filename)
for valid_file_parser in valid_files[file]:
parsers.append(valid_file_parser(cve_db, logger))
for parser in parsers:
yield from parser.run_checker(filename)
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/perl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
class PerlParser(Parser):
"""Parser for perl's cpan files"""

PARSER_MATCH_FILENAMES = [
"cpanfile",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "cpan"
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/php.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class PhpParser(Parser):
generate PURLs (Package URLs) for the listed packages.
"""

PARSER_MATCH_FILENAMES = [
"composer.lock",
]

def __init__(self, cve_db, logger):
"""Initialize the PhpParser."""
super().__init__(cve_db, logger)
Expand Down
9 changes: 9 additions & 0 deletions cve_bin_tool/parsers/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class PythonRequirementsParser(Parser):
requirements.txt) and generate PURLs (Package URLs) for the listed packages.
"""

PARSER_MATCH_FILENAMES = [
"requirements.txt",
]

def __init__(self, cve_db, logger):
"""Initialize the python requirements file parser."""
super().__init__(cve_db, logger)
Expand Down Expand Up @@ -114,6 +118,11 @@ class PythonParser(Parser):
PKG-INFO or METADATA) and generate PURLs (Package URLs) for the package.
"""

PARSER_MATCH_FILENAMES = [
"PKG-INFO: ",
"METADATA: ",
]

def __init__(self, cve_db, logger):
"""Initialize the python package metadata parser."""
super().__init__(cve_db, logger)
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/r.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class RParser(Parser):
"""

PARSER_MATCH_FILENAMES = [
"renv.lock",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "cran"
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/ruby.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class RubyParser(Parser):
"""

PARSER_MATCH_FILENAMES = [
"Gemfile.lock",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "gem"
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class RustParser(Parser):
Parse the Rust dependency file and yield valid PURLs for the packages listed in the file.
"""

PARSER_MATCH_FILENAMES = [
"Cargo.lock",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "cargo"
Expand Down
4 changes: 4 additions & 0 deletions cve_bin_tool/parsers/swift.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class SwiftParser(Parser):
"""

PARSER_MATCH_FILENAMES = [
"Package.resolved",
]

def __init__(self, cve_db, logger):
super().__init__(cve_db, logger)
self.purl_pkg_type = "swift"
Expand Down
24 changes: 6 additions & 18 deletions cve_bin_tool/version_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations

import itertools
import subprocess
import sys
from logging import Logger
from pathlib import Path, PurePath
from typing import Iterator
from pathlib import Path
from typing import Iterator, Type

from cve_bin_tool.checkers import Checker
from cve_bin_tool.cvedb import CVEDB
Expand All @@ -30,10 +31,6 @@
from importlib import metadata as importlib_metadata
else:
import importlib_metadata
if sys.version_info >= (3, 9):
import importlib.resources as resources
else:
import importlib_resources as resources


class InvalidFileError(Exception):
Expand All @@ -49,7 +46,7 @@ def __init__(
self,
should_extract: bool = False,
exclude_folders: list[str] = [],
checkers: dict[str, type[Checker]] | None = None,
checkers: dict[str, Type[Checker]] | None = None,
logger: Logger | None = None,
error_mode: ErrorMode = ErrorMode.TruncTrace,
score: int = 0,
Expand Down Expand Up @@ -82,7 +79,7 @@ def __init__(
self.language_checkers = self.available_language_checkers()

@classmethod
def load_checkers(cls) -> dict[str, type[Checker]]:
def load_checkers(cls) -> dict[str, Type[Checker]]:
"""Loads CVE checkers"""
checkers = dict(
map(
Expand Down Expand Up @@ -129,16 +126,7 @@ def number_of_checkers(self) -> int:
@classmethod
def available_language_checkers(cls) -> list[str]:
"""Find Language checkers"""
language_directory = resources.files(cls.LANGUAGE_CHECKER_ENTRYPOINT)
parsers = language_directory.iterdir()
language_checkers = []
for parser in parsers:
if str(parser).endswith(".py"):
language = PurePath(parser).name.replace(".py", "").capitalize()
if language not in ["__init__", "Parse"]:
language_checkers.append(language)

return sorted(language_checkers)
return list(sorted(map(str, set(itertools.chain(*valid_files.values())))))

def print_language_checkers(self) -> None:
"""Logs the message that lists the names of the language checkers"""
Expand Down
Loading

0 comments on commit bf79cd7

Please sign in to comment.