#![allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
use bytecount::count;
use handlebars::{Context, Handlebars as Registry, Helper, Output, RenderContext, RenderError};
use serde_json::Value;
use super::utils::{html_escape, split_indent};
pub fn line_numbers(
h: &Helper<'_, '_>,
_: &Registry,
_: &Context,
_: &mut RenderContext<'_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let vulnerability = h
.param(0)
.and_then(|v| v.value().as_object())
.ok_or_else(|| {
RenderError::new(
"to generate the vulnerability index, the first parameter must be a \
vulnerability",
)
})?;
let line_separator = if let Some(s) = h.param(1) {
if let Value::String(ref s) = *s.value() {
s
} else {
return Err(RenderError::new(
"the provided line separator for the code lines was \
not a string",
));
}
} else {
"<br>"
};
let (start_line, end_line) = if let Some(l) = vulnerability.get("line") {
let line = l.as_i64().unwrap();
(line, line)
} else {
let start_line = vulnerability.get("start_line").unwrap().as_i64().unwrap();
let end_line = vulnerability.get("end_line").unwrap().as_i64().unwrap();
(start_line, end_line)
};
let iter_start = if start_line > 5 { start_line - 4 } else { 1 };
let iter_end = end_line + 5;
let mut rendered =
String::with_capacity((line_separator.len() + 1) * (iter_end - iter_start) as usize);
for l in iter_start..iter_end {
rendered.push_str(&format!("{}", l));
rendered.push_str(line_separator);
}
out.write(&rendered)?;
Ok(())
}
pub fn all_lines(
h: &Helper<'_, '_>,
_: &Registry,
_: &Context,
_: &mut RenderContext<'_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let code = h
.param(0)
.and_then(|v| v.value().as_str())
.ok_or_else(|| RenderError::new("the code must be a string"))?;
let line_separator = if let Some(s) = h.param(1) {
if let Value::String(ref s) = *s.value() {
s
} else {
return Err(RenderError::new(
"the provided line separator for the code lines was \
not a string",
));
}
} else {
"<br>"
};
let line_count = count(code.as_bytes(), b'\n');
let mut rendered = String::with_capacity((line_separator.len() + 1) * line_count);
for l in 1..=line_count {
rendered.push_str(format!("{}", l).as_str());
rendered.push_str(line_separator);
}
out.write(&rendered)?;
Ok(())
}
pub fn all_code(
h: &Helper<'_, '_>,
_: &Registry,
_: &Context,
_: &mut RenderContext<'_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let code = h
.param(0)
.and_then(|v| v.value().as_str())
.ok_or_else(|| RenderError::new("the code must be a string"))?;
let line_separator = if let Some(s) = h.param(1) {
if let Value::String(ref s) = *s.value() {
s
} else {
return Err(RenderError::new(
"the provided line separator for the code lines was \
not a string",
));
}
} else {
"<br>"
};
for (i, line) in code.lines().enumerate() {
let (indent, line) = split_indent(line);
let line = format!(
"<code id=\"code-line-{}\">{}<span \
class=\"line_body\">{}</span></code>{}",
i + 1,
indent,
html_escape(line),
line_separator
);
out.write(&line)?;
}
Ok(())
}
pub fn html_code(
h: &Helper<'_, '_>,
_: &Registry,
_: &Context,
_: &mut RenderContext<'_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let vulnerability = h
.param(0)
.and_then(|v| v.value().as_object())
.ok_or_else(|| {
RenderError::new(
"to generate the vulnerability index, the first parameter must be a \
vulnerability",
)
})?;
let line_separator = if let Some(s) = h.param(1) {
if let Value::String(ref s) = *s.value() {
s
} else {
return Err(RenderError::new(
"the provided line separator for the code lines was \
not a string",
));
}
} else {
"<br>"
};
let (start_line, end_line) = if let Some(l) = vulnerability.get("line") {
let line = l.as_i64().unwrap();
(line, line)
} else {
let start_line = vulnerability.get("start_line").unwrap().as_i64().unwrap();
let end_line = vulnerability.get("end_line").unwrap().as_i64().unwrap();
(start_line, end_line)
};
let iter_start = if start_line > 5 { start_line - 4 } else { 1 };
for (i, line) in vulnerability
.get("code")
.unwrap()
.as_str()
.unwrap()
.lines()
.enumerate()
{
let line_number = i + iter_start as usize;
let rendered = if line_number >= start_line as usize && line_number <= end_line as usize {
let (indent, code) = split_indent(line);
format!(
"<code class=\"vulnerable_line {}\">{}<span \
class=\"line_body\">{}</span></code>{}",
vulnerability.get("criticality").unwrap().as_str().unwrap(),
indent,
html_escape(code),
line_separator
)
} else {
format!("{}{}", html_escape(line), line_separator)
};
out.write(&rendered)?;
}
Ok(())
}
#[allow(clippy::cast_precision_loss)]
pub fn report_index(
h: &Helper<'_, '_>,
_: &Registry,
_: &Context,
_: &mut RenderContext<'_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let vulnerability = h
.param(0)
.and_then(|v| v.value().as_object())
.ok_or_else(|| {
RenderError::new(
"to generate the vulnerability index, the first parameter must be a vulnerability",
)
})?;
let index = h.param(1).and_then(|v| v.value().as_u64()).ok_or_else(|| {
RenderError::new(
"the index of the vulnerability in the current list must be the second parameter",
)
})? as usize
+ 1;
let list_len = h.param(2).unwrap().value().as_u64().unwrap();
let char_index = vulnerability
.get("criticality")
.unwrap()
.as_str()
.unwrap()
.to_uppercase()
.chars()
.next()
.unwrap();
let mut index_padding = (list_len as f64 + 1_f64).log10().ceil() as usize + 1;
if index_padding < 2 {
index_padding = 2;
}
let rendered = format!("{}{:#02$}", char_index, index, index_padding);
out.write(&rendered)?;
Ok(())
}
pub fn generate_menu(
h: &Helper<'_, '_>,
_: &Registry,
_: &Context,
_: &mut RenderContext<'_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
let menu = h
.param(0)
.and_then(|m| m.value().as_array())
.ok_or_else(|| {
RenderError::new("to generate the menu, the first parameter must be a menu array")
})?;
out.write("<ul>")?;
render_menu(menu, out)?;
out.write("</ul>")?;
Ok(())
}
fn render_menu(menu: &[Value], renderer: &mut dyn Output) -> Result<(), RenderError> {
for value in menu {
if let Value::Object(ref item) = *value {
renderer.write("<li>")?;
let name = item
.get("name")
.and_then(Value::as_str)
.ok_or_else(|| RenderError::new("invalid menu object type"))?;
if let Some(&Value::Array(ref menu)) = item.get("menu") {
renderer.write(
format!(
"<a href=\"#\" title=\"{0}\"><img src=\"../img/folder.svg\">{0}</a>",
name
)
.as_str(),
)?;
renderer.write("<ul>")?;
render_menu(menu, renderer)?;
renderer.write("</ul>")?;
} else {
let path = item
.get("path")
.and_then(Value::as_str)
.ok_or_else(|| RenderError::new("invalid menu object type"))?;
let file_type = item
.get("type")
.and_then(Value::as_str)
.ok_or_else(|| RenderError::new("invalid menu object type"))?;
renderer.write(
format!(
"<a href=\"{1}.html\" title=\"{0}\" target=\"code\"><img src=\"../img/{2}.svg\">{0}</a>",
name, path, file_type
).as_str()
)?;
}
renderer.write("</li>")?;
} else {
return Err(RenderError::new("invalid menu object type"));
}
}
Ok(())
}