1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use roxmltree::{Document, Node};
pub mod host;
pub mod port;
use crate::host::Host;
use crate::port::Port;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("error parsing file as XML document")]
XmlError(#[from] roxmltree::Error),
#[error("error parsing Nmap XML output: {0}")]
InvalidNmapOutput(String),
}
impl From<&str> for Error {
fn from(s: &str) -> Self {
Self::InvalidNmapOutput(s.to_string())
}
}
#[derive(Clone, Debug)]
pub struct NmapResults {
hosts: Vec<Host>,
pub scan_start_time: i64,
pub scan_end_time: Option<i64>,
}
impl NmapResults {
pub fn parse(xml: &str) -> Result<Self, Error> {
let doc = Document::parse(&xml)?;
let root_element = doc.root_element();
if root_element.tag_name().name() != "nmaprun" {
return Err(Error::from("expected `nmaprun` root tag"));
}
let scan_start_time = root_element
.attribute("start")
.ok_or_else(|| Error::from("expected start time attribute"))
.and_then(|s| {
s.parse::<i64>()
.map_err(|_| Error::from("failed to parse start time"))
})?;
let mut hosts: Vec<Host> = Vec::new();
let mut scan_end_time = None;
for child in root_element.children() {
match child.tag_name().name() {
"host" => {
hosts.push(Host::parse(child)?);
}
"runstats" => scan_end_time = Some(parse_runstats(child)?),
_ => {}
}
}
Ok(NmapResults {
hosts,
scan_start_time,
scan_end_time,
})
}
pub fn hosts(&self) -> std::slice::Iter<Host> {
self.hosts.iter()
}
pub fn iter_ports(&self) -> std::vec::IntoIter<(&Host, &Port)> {
let mut results = Vec::new();
for host in &self.hosts {
for port in &host.port_info.ports {
results.push((host, port));
}
}
results.into_iter()
}
}
fn parse_runstats(node: Node) -> Result<i64, Error> {
for child in node.children() {
if child.tag_name().name() == "finished" {
let finished = child
.attribute("time")
.ok_or_else(|| Error::from("expected `time` `runstats`.`finished`"))
.and_then(|s| {
s.parse::<i64>()
.map_err(|_| Error::from("failed to parse end time"))
})?;
return Ok(finished);
}
}
Err(Error::from("expected `finished` tag in `runstats`"))
}