1use crate::{
2 analyze::{AnalysisSummary, AnalyzerResult, Summary, WpScanAnalysis},
3 errors::*,
4};
5
6use failure::Fail;
7use prettytable::{color, format, format::Alignment, Attr, Cell, Row, Table};
8use serde_json;
9use std::{io::Write, str::FromStr};
10
11#[derive(Debug, PartialEq)]
12pub enum OutputFormat {
13 Human,
14 Json,
15 None,
16}
17
18impl FromStr for OutputFormat {
19 type Err = Error;
20
21 fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
22 match s.to_lowercase().as_ref() {
23 "human" => Ok(OutputFormat::Human),
24 "json" => Ok(OutputFormat::Json),
25 "none" => Ok(OutputFormat::None),
26 _ => Err(ErrorKind::InvalidOutputFormat(s.to_string()).into()),
27 }
28 }
29}
30
31#[derive(Debug, PartialEq)]
32pub enum OutputDetail {
33 NotOkay,
34 All,
35}
36
37impl FromStr for OutputDetail {
38 type Err = Error;
39
40 fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
41 match s.to_lowercase().as_ref() {
42 "nok" => Ok(OutputDetail::NotOkay),
43 "all" => Ok(OutputDetail::All),
44 _ => Err(ErrorKind::InvalidOutputDetail(s.to_string()).into()),
45 }
46 }
47}
48
49#[derive(Debug)]
50pub struct OutputConfig {
51 pub detail: OutputDetail,
52 pub format: OutputFormat,
53 pub color: bool,
54}
55
56pub trait JsonOutput {
57 fn output<T: Write>(&self, output_config: &OutputConfig, writer: &mut T) -> Result<usize>;
58}
59
60pub trait HumanOutput {
61 fn output<T: Write>(&self, output_config: &OutputConfig, writer: &mut T) -> Result<usize>;
62 fn output_tty(&self, output_config: &OutputConfig) -> Result<usize>;
63}
64
65impl<'a> JsonOutput for WpScanAnalysis<'a> {
66 fn output<T: Write>(&self, _: &OutputConfig, writer: &mut T) -> Result<usize> {
67 let json_str = serde_json::to_string(self).map_err(|e| e.context(ErrorKind::OutputFailed))?;
68 let bytes = json_str.as_bytes();
69 writer.write(bytes).map_err(|e| e.context(ErrorKind::OutputFailed))?;
70
71 Ok(bytes.len())
72 }
73}
74
75impl<'a> HumanOutput for WpScanAnalysis<'a> {
76 fn output<T: Write>(&self, output_config: &OutputConfig, writer: &mut T) -> Result<usize> {
77 self.build_table(output_config)
78 .print(writer)
79 .map_err(|e| e.context(ErrorKind::OutputFailed).into())
80 }
81
82 fn output_tty(&self, output_config: &OutputConfig) -> Result<usize> {
83 if output_config.color {
84 let len = self.build_table(output_config).printstd();
85 Ok(len)
86 } else {
87 let stdout = ::std::io::stdout();
88 let mut writer = stdout.lock();
89 self.build_table(output_config)
90 .print(&mut writer)
91 .map_err(|e| e.context(ErrorKind::OutputFailed).into())
92 }
93 }
94}
95
96impl<'a> WpScanAnalysis<'a> {
97 fn build_table(&self, output_config: &OutputConfig) -> Table {
98 let mut table = Table::new();
99 table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
100
101 table.set_titles(Row::new(vec![
102 Cell::new("Component"),
103 Cell::new("Version"),
104 Cell::new("Version State"),
105 Cell::new("Vulnerabilities"),
106 Cell::new("Processing"),
107 Cell::new("Result"),
108 ]));
109
110 table.add_row(result_to_row("WordPress", &self.word_press));
111 table.add_row(result_to_row("Main Theme", &self.main_theme));
112 for (k, v) in self.plugins.iter() {
113 if output_config.detail == OutputDetail::NotOkay && v.summary() == AnalysisSummary::Ok {
114 continue;
115 }
116 let text = format!("Plugin: {}", k);
117 table.add_row(result_to_row(text.as_ref(), v));
118 }
119
120 table
121 }
122}
123
124fn result_to_row(name: &str, result: &AnalyzerResult) -> Row {
125 Row::new(vec![
126 Cell::new(name),
127 version_to_cell(result),
128 if result.outdated() {
129 Cell::new_align("Outdated", Alignment::CENTER).with_style(Attr::ForegroundColor(color::YELLOW))
130 } else {
131 Cell::new_align("Latest", Alignment::CENTER).with_style(Attr::ForegroundColor(color::GREEN))
132 },
133 if result.vulnerabilities() > 0 {
134 Cell::new_align(
135 format!("{} vulnerabilities", result.vulnerabilities()).as_ref(),
136 Alignment::CENTER,
137 )
138 .with_style(Attr::ForegroundColor(color::RED))
139 } else {
140 Cell::new_align("No vulnerabilities", Alignment::CENTER)
141 },
142 if result.failed() {
143 Cell::new_align("Failed", Alignment::CENTER).with_style(Attr::ForegroundColor(color::RED))
144 } else {
145 Cell::new_align("Ok", Alignment::CENTER)
146 },
147 summary_to_cell(result),
148 ])
149}
150
151fn version_to_cell(result: &AnalyzerResult) -> Cell {
152 let text = match result.version() {
153 Some(version) => version,
154 None => "-",
155 };
156
157 Cell::new(text)
158}
159
160fn summary_to_cell(result: &AnalyzerResult) -> Cell {
161 let mut cell = match result.summary() {
162 AnalysisSummary::Ok => Cell::new("Ok"),
163 AnalysisSummary::Outdated => Cell::new("Outdated").with_style(Attr::ForegroundColor(color::YELLOW)),
164 AnalysisSummary::Vulnerable => Cell::new("Vulnerable").with_style(Attr::ForegroundColor(color::RED)),
165 AnalysisSummary::Failed => Cell::new("Failed").with_style(Attr::ForegroundColor(color::RED)),
166 };
167 cell.align(Alignment::CENTER);
168
169 cell
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 use spectral::prelude::*;
177
178 #[test]
179 fn output_format_from_str() {
180 assert_that(&OutputFormat::from_str("human"))
181 .is_ok()
182 .is_equal_to(OutputFormat::Human);
183 assert_that(&OutputFormat::from_str("json"))
184 .is_ok()
185 .is_equal_to(OutputFormat::Json);
186 assert_that(&OutputFormat::from_str("none"))
187 .is_ok()
188 .is_equal_to(OutputFormat::None);
189 assert_that(&OutputFormat::from_str("lukas")).is_err();
190 }
191
192 #[test]
193 fn output_detail_from_str() {
194 assert_that(&OutputDetail::from_str("all"))
195 .is_ok()
196 .is_equal_to(OutputDetail::All);
197 assert_that(&OutputDetail::from_str("nok"))
198 .is_ok()
199 .is_equal_to(OutputDetail::NotOkay);
200 assert_that(&OutputDetail::from_str("lukas")).is_err();
201 }
202}