diff --git a/csaf/csaf-cli/src/cmd/report/render.rs b/csaf/csaf-cli/src/cmd/report/render.rs index 825d4b1..397c14a 100644 --- a/csaf/csaf-cli/src/cmd/report/render.rs +++ b/csaf/csaf-cli/src/cmd/report/render.rs @@ -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, - "

{num} duplicates URLs found, totalling {total} redundant entries

", - )?; - - writeln!(f, "

The following URLs have duplicate entries:

")?; - - writeln!( - f, - r#" - - - - - - - - - -"# - )?; - + let count = self.0.duplicates.duplicates.len(); + let data = |f: &mut Formatter<'_>| { for (k, v) in &self.0.duplicates.duplicates { writeln!( f, @@ -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#" - -
File# Duplicates
-"# + 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, "

{num} error{s} detected

")?; - - writeln!( - f, - r#" - - - - - - - - - -"# - )?; - + let count = self.0.errors.len(); + let data = |f: &mut Formatter<'_>| { for (k, v) in self.0.errors { let k: Cow = match (&self.1.base_url, Url::parse(k)) { (Some(base_url), Ok(url)) => match base_url.make_relative(&url) { @@ -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: &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, "

{sub_title}

")?; + writeln!( + f, + r#" +
FileError
+ + + + + + - writeln!( - f, - r#" -
File{title}
"# - )?; + )?; + data(f)?; + writeln!(f, "")?; + + 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 = 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#" + + {k} + {v} + + "#, + 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(()) } @@ -184,7 +216,7 @@ impl<'r> Display for HtmlReport<'r> { self.render_duplicates(f)?; self.render_errors(f)?; - + self.render_warnings(f)?; Ok(()) } } diff --git a/csaf/src/verification/check/informational_advisory/mod.rs b/csaf/src/verification/check/informational_advisory/mod.rs index edb255e..c308972 100644 --- a/csaf/src/verification/check/informational_advisory/mod.rs +++ b/csaf/src/verification/check/informational_advisory/mod.rs @@ -10,7 +10,7 @@ pub fn check_vulnerabilities_not_exits(csaf: &Csaf) -> Vec { } 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() diff --git a/csaf/src/verification/check/security_incident_response/mod.rs b/csaf/src/verification/check/security_incident_response/mod.rs index 9351429..d793d9d 100644 --- a/csaf/src/verification/check/security_incident_response/mod.rs +++ b/csaf/src/verification/check/security_incident_response/mod.rs @@ -3,7 +3,7 @@ use csaf::definitions::{NoteCategory, ReferenceCategory}; use csaf::Csaf; pub fn check_csaf_document_notes(csaf: &Csaf) -> Vec { - 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; @@ -24,7 +24,7 @@ pub fn check_csaf_document_notes(csaf: &Csaf) -> Vec { } pub fn check_csaf_document_references(csaf: &Csaf) -> Vec { - 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; diff --git a/csaf/src/verification/check/vex/mod.rs b/csaf/src/verification/check/vex/mod.rs index 3feb8c8..3cbe514 100644 --- a/csaf/src/verification/check/vex/mod.rs +++ b/csaf/src/verification/check/vex/mod.rs @@ -17,7 +17,7 @@ pub fn check_vulnerabilities_size(csaf: &Csaf) -> Vec { result = false; } Checking::new() - .require("The csaf file's Vulnerabilities is empty", result) + .require("The csaf file's Vulnerabilities is empty", !result) .done() } @@ -108,7 +108,7 @@ pub fn check_vulnerabilities_cve_ids(csaf: &Csaf) -> Vec { 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(), @@ -166,24 +166,24 @@ pub fn check_branches_relationships_product_match(csaf: &Csaf) -> Vec) { +fn get_all_product_names(product_tree: &ProductTree, products: &mut HashSet) { 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, product_id_t: &ProductIdT, erroies: &mut Vec, ) { 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(), @@ -195,10 +195,14 @@ pub fn check_all_products_v11ies_exits_in_product_tree(csaf: &Csaf) -> Vec Vec