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

add warning html render #8

Merged
merged 2 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 96 additions & 64 deletions csaf/csaf-cli/src/cmd/report/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,8 @@ struct HtmlReport<'r>(&'r ReportResult<'r>, &'r RenderOptions);

impl HtmlReport<'_> {
fn render_duplicates(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Self::title(f, "Duplicates", self.0.duplicates.duplicates.len())?;

if !self.0.duplicates.duplicates.is_empty() {
let num = self.0.duplicates.duplicates.len();
let total: usize = self.0.duplicates.duplicates.values().sum();
writeln!(
f,
"<p>{num} duplicates URLs found, totalling {total} redundant entries</p>",
)?;

writeln!(f, "<p>The following URLs have duplicate entries:</p>")?;

writeln!(
f,
r#"
<table class="table">
<thead>
<tr>
<th scope="col">File</th>
<th scope="col"># Duplicates</th>
</tr>
</thead>

<tbody>
"#
)?;

let count = self.0.duplicates.duplicates.len();
let data = |f: &mut Formatter<'_>| {
for (k, v) in &self.0.duplicates.duplicates {
writeln!(
f,
Expand All @@ -78,45 +53,29 @@ impl HtmlReport<'_> {
k = html_escape::encode_text(&k),
)?;
}
Ok(())
};
if !self.0.duplicates.duplicates.is_empty() {
let total: usize = self.0.duplicates.duplicates.values().sum();

writeln!(
Self::render_table(
f,
r#"
<tbody>
</table>
"#
count,
"Duplicates",
format!(
"{:?} duplicates URLs found, totalling {:?} redundant entries",
count, total
)
.as_str(),
data,
)?;
}

Ok(())
}

fn render_errors(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Self::title(f, "Errors", self.0.errors.len())?;

if !self.0.errors.is_empty() {
let num = self.0.errors.len();
let s = match num {
1 => "",
_ => "s",
};
writeln!(f, "<p>{num} error{s} detected</p>")?;

writeln!(
f,
r#"
<table class="table">
<thead>
<tr>
<th scope="col">File</th>
<th scope="col">Error</th>
</tr>
</thead>

<tbody>
"#
)?;

let count = self.0.errors.len();
let data = |f: &mut Formatter<'_>| {
for (k, v) in self.0.errors {
let k: Cow<str> = match (&self.1.base_url, Url::parse(k)) {
(Some(base_url), Ok(url)) => match base_url.make_relative(&url) {
Expand All @@ -138,16 +97,89 @@ impl HtmlReport<'_> {
v = html_escape::encode_text(&v),
)?;
}
Ok(())
};
Self::render_table(
f,
count,
"Error",
format!("{:?} error(s) detected", count).as_str(),
data,
)?;
Ok(())
}

fn render_table<F>(
f: &mut Formatter<'_>,
count: usize,
title: &str,
sub_title: &str,
data: F,
) -> std::fmt::Result
where
F: Fn(&mut Formatter<'_>) -> std::fmt::Result,
{
Self::title(f, title, count)?;
writeln!(f, "<p>{sub_title}</p>")?;
writeln!(
f,
r#"
<table class="table">
<thead>
<tr>
<th scope="col">File</th>
<th scope="col">{title}</th>
</tr>
</thead>

writeln!(
f,
r#"
<tbody>
</table>
"#
)?;
)?;
data(f)?;
writeln!(f, "</tbody></table>")?;

Ok(())
}

fn render_warnings(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut count = 0;
for errors in self.0.warnings.values() {
count += errors.len();
}

let data = |f: &mut Formatter<'_>| {
for (k, v) in self.0.warnings {
let k: Cow<str> = match (&self.1.base_url, Url::parse(k)) {
(Some(base_url), Ok(url)) => match base_url.make_relative(&url) {
Some(url) => Cow::Owned(url),
None => Cow::Borrowed(k),
},
_ => Cow::Borrowed(k),
};

for text in v {
writeln!(
f,
r#"
<tr>
<td><a href="{k}" target="_blank">{k}</a></td>
<td><code>{v}</code></td>
</tr>
"#,
k = html_escape::encode_quoted_attribute(&k),
v = html_escape::encode_text(&text),
)?;
}
}
Ok(())
};
Self::render_table(
f,
count,
"Warning",
format!("{:?} warning(s) detected", count).as_str(),
data,
)?;
Ok(())
}

Expand Down Expand Up @@ -184,7 +216,7 @@ impl<'r> Display for HtmlReport<'r> {

self.render_duplicates(f)?;
self.render_errors(f)?;

self.render_warnings(f)?;
Ok(())
}
}
2 changes: 1 addition & 1 deletion csaf/src/verification/check/informational_advisory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub fn check_vulnerabilities_not_exits(csaf: &Csaf) -> Vec<CheckError> {
}
Checking::new()
.require(
"The casf file should not related to a vulnerability ",
"The csaf file should not related to a vulnerability ",
csaf.vulnerabilities.is_some(),
)
.done()
Expand Down
4 changes: 2 additions & 2 deletions csaf/src/verification/check/security_incident_response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use csaf::definitions::{NoteCategory, ReferenceCategory};
use csaf::Csaf;

pub fn check_csaf_document_notes(csaf: &Csaf) -> Vec<CheckError> {
if !is_security_incident_response(csaf) && is_security_informational_advisory(csaf) {
if !is_security_incident_response(csaf) && !is_security_informational_advisory(csaf) {
return vec![];
}
let mut result = false;
Expand All @@ -24,7 +24,7 @@ pub fn check_csaf_document_notes(csaf: &Csaf) -> Vec<CheckError> {
}

pub fn check_csaf_document_references(csaf: &Csaf) -> Vec<CheckError> {
if !is_security_incident_response(csaf) && is_security_informational_advisory(csaf) {
if !is_security_incident_response(csaf) && !is_security_informational_advisory(csaf) {
return vec![];
}
let mut result = false;
Expand Down
24 changes: 14 additions & 10 deletions csaf/src/verification/check/vex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn check_vulnerabilities_size(csaf: &Csaf) -> Vec<CheckError> {
result = false;
}
Checking::new()
.require("The csaf file's Vulnerabilities is empty", result)
.require("The csaf file's Vulnerabilities is empty", !result)
.done()
}

Expand Down Expand Up @@ -108,7 +108,7 @@ pub fn check_vulnerabilities_cve_ids(csaf: &Csaf) -> Vec<CheckError> {
check_erroies.extend(
Checking::new()
.require(
"The csaf file have a vulnerability",
"The csaf file have a vulnerability",
vuln.cve.is_none() | vuln.ids.is_none(),
)
.done(),
Expand Down Expand Up @@ -166,24 +166,24 @@ pub fn check_branches_relationships_product_match(csaf: &Csaf) -> Vec<CheckError
result
}

fn get_all_product_names(product_tree: &ProductTree, products: &mut Vec<String>) {
fn get_all_product_names(product_tree: &ProductTree, products: &mut HashSet<String>) {
if let Some(relationships) = &product_tree.relationships {
for r in relationships {
let id = &r.full_product_name.product_id;
products.push(id.clone().0);
products.insert(id.clone().0);
}
}
}

fn check_product(
product_names: &mut [String],
product_names: &mut HashSet<String>,
product_id_t: &ProductIdT,
erroies: &mut Vec<CheckError>,
) {
erroies.extend(
Checking::new()
.require(
format!("product {:?} does not exits in product tree", &product_id_t),
format!("The product under the 'product status' section of the vulnerabilities division, identified as {:?}, is missing from the product tree.", &product_id_t),
product_names.contains(&product_id_t.0),
)
.done(),
Expand All @@ -195,10 +195,14 @@ pub fn check_all_products_v11ies_exits_in_product_tree(csaf: &Csaf) -> Vec<Check
if !is_vex(csaf) && !is_security_advisory(csaf) {
return vec![];
}

let mut results = vec![];
if let Some(products_tree) = &csaf.product_tree {
let mut product_names = vec![];
let mut product_names = HashSet::new();

if let Some(branches) = &products_tree.branches {
get_all_product_id_from_product_tree_branches(branches, &mut product_names);
}

get_all_product_names(products_tree, &mut product_names);
if let Some(v11y) = &csaf.vulnerabilities {
for v in v11y {
Expand Down Expand Up @@ -252,7 +256,7 @@ pub fn check_all_products_v11ies_exits_in_product_tree(csaf: &Csaf) -> Vec<Check
Checking::new()
.require(
format!(
"product {:?} does not exits in product tree",
"The product under the 'remediation' section of the vulnerabilities division, identified as {:?}, is missing from the product tree.",
product_id.clone().0
),
product_names.contains(&product_id.0),
Expand Down Expand Up @@ -394,7 +398,7 @@ mod tests {
let csaf: Csaf =
serde_json::from_str(include_str!("../../../../test-data/rhsa-2023_3408.json"))
.unwrap();
assert_eq!(check_vulnerabilities_size(&csaf).len(), 0);
assert_eq!(check_vulnerabilities_size(&csaf).len(), 1);
}

/// Verify product do not match in branches and relationships
Expand Down