diff --git a/cve_bin_tool/output_engine/__init__.py b/cve_bin_tool/output_engine/__init__.py index 65f1f3f455..26ce074a29 100644 --- a/cve_bin_tool/output_engine/__init__.py +++ b/cve_bin_tool/output_engine/__init__.py @@ -396,7 +396,8 @@ def output_cves(self, outfile, output_type="console"): self.all_cve_version_info, self.time_of_last_update, self.affected_versions, - exploits=self.exploits, + self.exploits, + outfile, ) if isinstance(self.append, str): diff --git a/cve_bin_tool/output_engine/console.py b/cve_bin_tool/output_engine/console.py index 0017aaefc9..61062cd620 100644 --- a/cve_bin_tool/output_engine/console.py +++ b/cve_bin_tool/output_engine/console.py @@ -1,10 +1,12 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: GPL-3.0-or-later +from __future__ import annotations + import textwrap from collections import defaultdict from datetime import datetime -from typing import DefaultDict, Dict, List +from typing import Any, DefaultDict from rich.console import Console from rich.markdown import Markdown @@ -61,13 +63,29 @@ def format_version_range(version_info: VersionInfo) -> str: return "-" -def output_console( - all_cve_data: Dict[ProductInfo, CVEData], - all_cve_version_info: Dict[str, VersionInfo], - time_of_last_update, +def output_console(*args: Any): + """wrapper function for _output_console to enable output to a file""" + + ls_args = list(args) + output_file = ls_args[-1] + ls_args.pop() + + if output_file: + with open(output_file, "wt", encoding="utf-8") as f: + console = Console(theme=cve_theme, file=f) + ls_args.append(console) + _output_console_nowrap(*ls_args) + else: + _output_console_nowrap(*ls_args) + + +def _output_console_nowrap( + all_cve_data: dict[ProductInfo, CVEData], + all_cve_version_info: dict[str, VersionInfo], + time_of_last_update: datetime | str, affected_versions: int, - console=Console(theme=cve_theme), exploits: bool = False, + console: Console = Console(theme=cve_theme), ): """Output list of CVEs in a tabular format with color support""" @@ -119,7 +137,7 @@ def output_console( console.print(Panel("CVE SUMMARY", expand=False)) console.print(table) - cve_by_remarks: DefaultDict[Remarks, List[Dict[str, str]]] = defaultdict(list) + cve_by_remarks: DefaultDict[Remarks, list[dict[str, str]]] = defaultdict(list) # group cve_data by its remarks for product_info, cve_data in all_cve_data.items(): for cve in cve_data["cves"]: diff --git a/test/test_output_engine.py b/test/test_output_engine.py index 1431ece27e..900da977e1 100644 --- a/test/test_output_engine.py +++ b/test/test_output_engine.py @@ -713,13 +713,20 @@ def test_output_pdf(self): def test_output_console(self): """Test Formatting Output as console""" + time_of_last_update = datetime.today() + affected_versions = 0 + exploits = False console = Console(file=self.mock_file) + outfile = None + output_console( self.MOCK_OUTPUT, self.MOCK_ALL_CVE_VERSION_INFO, - time_of_last_update=datetime.today(), - affected_versions=0, - console=console, + time_of_last_update, + affected_versions, + exploits, + console, + outfile, ) expected_output = "│ vendor0 │ product0 │ 1.0 │ CVE-1234-1234 │ MEDIUM │ 4.2 (v2) │\n│ vendor0 │ product0 │ 1.0 │ CVE-1234-1234 │ LOW │ 1.2 (v2) │\n│ vendor0 │ product0 │ 2.8.6 │ CVE-1234-1234 │ LOW │ 2.5 (v3) │\n│ vendor1 │ product1 │ 3.2.1.0 │ CVE-1234-1234 │ HIGH │ 7.5 (v2) │\n└─────────┴──────────┴─────────┴───────────────┴──────────┴──────────────────────┘\n" @@ -730,13 +737,20 @@ def test_output_console(self): def test_output_console_affected_versions(self): """Test Formatting Output as console with affected-versions""" + time_of_last_update = datetime.today() + affected_versions = 1 + exploits = False console = Console(file=self.mock_file) + outfile = None + output_console( self.MOCK_ALL_CVE_DATA, self.MOCK_ALL_CVE_VERSION_INFO, - time_of_last_update=datetime.today(), - affected_versions=1, - console=console, + time_of_last_update, + affected_versions, + exploits, + console, + outfile, ) expected_output = "│ vendor0 │ product0 │ 1.0 │ UNKNOWN │ UNKNOWN │ 0 (v0) │ - │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0001 │ MEDIUM │ 4.2 (v2) │ [0.9.0 - 1.2.0] │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0002 │ MEDIUM │ 4.2 (v2) │ [0.9.0 - 1.2.0) │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0003 │ MEDIUM │ 4.2 (v2) │ (0.9.0 - 1.2.0] │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0004 │ MEDIUM │ 4.2 (v2) │ (0.9.0 - 1.2.0) │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0005 │ MEDIUM │ 4.2 (v2) │ >= 0.9.0 │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0006 │ MEDIUM │ 4.2 (v2) │ > 0.9.0 │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0007 │ MEDIUM │ 4.2 (v2) │ <= 1.2.0 │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-0008 │ MEDIUM │ 4.2 (v2) │ < 1.2.0 │\n│ vendor0 │ product0 │ 1.0 │ CVE-9999-9999 │ LOW │ 1.2 (v2) │ - │\n└─────────┴──────────┴─────────┴───────────────┴──────────┴──────────────────────┴───────────────────┘\n" @@ -744,6 +758,34 @@ def test_output_console_affected_versions(self): result = self.mock_file.read() self.assertIn(expected_output, result) + def test_output_console_outfile(self): + """Test output to a file""" + + tmpf = tempfile.NamedTemporaryFile(mode="w+", delete=False, encoding="utf-8") + tmpf.close() # accessing open tempfile on windows gives error + + time_of_last_update = datetime.today() + affected_versions = 0 + exploits = False + outfile = tmpf.name + + output_console( + self.MOCK_OUTPUT, + self.MOCK_ALL_CVE_VERSION_INFO, + time_of_last_update, + affected_versions, + exploits, + outfile, + ) + + expected_output = "│ vendor0 │ product0 │ 1.0 │ CVE-1234-1234 │ MEDIUM │ 4.2 (v2) │\n│ vendor0 │ product0 │ 1.0 │ CVE-1234-1234 │ LOW │ 1.2 (v2) │\n│ vendor0 │ product0 │ 2.8.6 │ CVE-1234-1234 │ LOW │ 2.5 (v3) │\n│ vendor1 │ product1 │ 3.2.1.0 │ CVE-1234-1234 │ HIGH │ 7.5 (v2) │\n└─────────┴──────────┴─────────┴───────────────┴──────────┴──────────────────────┘\n" + + with open(tmpf.name, encoding="utf-8") as f: + result = f.read() + + self.assertIn(expected_output, result) + os.unlink(tmpf.name) # deleting tempfile + def test_output_html(self): """Test formatting output as HTML"""