wpscan_analyze/
output.rs

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}