Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: console output to a file #1632

Merged
merged 12 commits into from
May 31, 2022
3 changes: 2 additions & 1 deletion cve_bin_tool/output_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
32 changes: 25 additions & 7 deletions cve_bin_tool/output_engine/console.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"""

Expand Down Expand Up @@ -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"]:
Expand Down
54 changes: 48 additions & 6 deletions test/test_output_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -730,20 +737,55 @@ 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"
self.mock_file.seek(0) # reset file position
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"""

Expand Down