# Copyright (c) 2015 Rackspace, Inc.
# Copyright (c) 2015 Hewlett Packard Enterprise
#
# SPDX-License-Identifier: Apache-2.0
r"""
==============
HTML formatter
==============
This formatter outputs the issues as HTML.
:Example:
.. code-block:: html
Bandit Report
Metrics:
Total lines of code:
9
Total lines skipped (#nosec):
0
.. versionadded:: 0.14.0
.. versionchanged:: 1.5.0
New field `more_info` added to output
.. versionchanged:: 1.7.3
New field `CWE` added to output
"""
import logging
import sys
from html import escape as html_escape
from bandit.core import docs_utils
from bandit.core import test_properties
from bandit.formatters import utils
LOG = logging.getLogger(__name__)
@test_properties.accepts_baseline
def report(manager, fileobj, sev_level, conf_level, lines=-1):
"""Writes issues to 'fileobj' in HTML format
:param manager: the bandit manager object
:param fileobj: The output file object, which may be sys.stdout
:param sev_level: Filtering severity level
:param conf_level: Filtering confidence level
:param lines: Number of lines to report, -1 for all
"""
header_block = """
Bandit Report
"""
report_block = """
{metrics}
{skipped}
{results}
"""
issue_block = """
{test_name}: {test_text}
Test ID: {test_id}
Severity: {severity}
Confidence: {confidence}
CWE: CWE-{cwe.id}
File: {path}
Line number: {line_number}
More info: {url}
{code}
{candidates}
"""
code_block = """
"""
candidate_block = """
Candidates:
{candidate_list}
"""
candidate_issue = """
"""
skipped_block = """
Skipped files:
{files_list}
"""
metrics_block = """
Metrics:
Total lines of code:
{loc}
Total lines skipped (#nosec):
{nosec}
"""
issues = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level)
baseline = not isinstance(issues, list)
# build the skipped string to insert in the report
skipped_str = "".join(
f"{fname} reason: {reason}
"
for fname, reason in manager.get_skipped()
)
if skipped_str:
skipped_text = skipped_block.format(files_list=skipped_str)
else:
skipped_text = ""
# build the results string to insert in the report
results_str = ""
for index, issue in enumerate(issues):
if not baseline or len(issues[issue]) == 1:
candidates = ""
safe_code = html_escape(
issue.get_code(lines, True).strip("\n").lstrip(" ")
)
code = code_block.format(code=safe_code)
else:
candidates_str = ""
code = ""
for candidate in issues[issue]:
candidate_code = html_escape(
candidate.get_code(lines, True).strip("\n").lstrip(" ")
)
candidates_str += candidate_issue.format(code=candidate_code)
candidates = candidate_block.format(candidate_list=candidates_str)
url = docs_utils.get_url(issue.test_id)
results_str += issue_block.format(
issue_no=index,
issue_class=f"issue-sev-{issue.severity.lower()}",
test_name=issue.test,
test_id=issue.test_id,
test_text=issue.text,
severity=issue.severity,
confidence=issue.confidence,
cwe=issue.cwe,
cwe_link=issue.cwe.link(),
path=issue.fname,
code=code,
candidates=candidates,
url=url,
line_number=issue.lineno,
)
# build the metrics string to insert in the report
metrics_summary = metrics_block.format(
loc=manager.metrics.data["_totals"]["loc"],
nosec=manager.metrics.data["_totals"]["nosec"],
)
# build the report and output it
report_contents = report_block.format(
metrics=metrics_summary, skipped=skipped_text, results=results_str
)
with fileobj:
wrapped_file = utils.wrap_file_object(fileobj)
wrapped_file.write(header_block)
wrapped_file.write(report_contents)
if fileobj.name != sys.stdout.name:
LOG.info("HTML output written to file: %s", fileobj.name)