1use std::{borrow::Cow, collections::HashMap};
2
3use jiff::Timestamp;
4use roxmltree::{Node, StringStorage};
5
6use crate::{StringStorageExt, assert_empty_text, error::FormatError};
7
8#[derive(Debug)]
14pub struct Policy<'input> {
15 pub policy_name: Cow<'input, str>,
17 pub policy_comments: Option<Cow<'input, str>>,
19 pub preferences: Preferences<'input>,
21 pub family_selection: Vec<FamilyItem<'input>>,
23 pub individual_plugin_selection: Vec<PluginItem<'input>>,
25}
26
27impl<'input> Policy<'input> {
28 pub(crate) fn from_xml_node(node: Node<'_, 'input>) -> Result<Self, FormatError> {
29 let mut policy_name = None;
30 let mut policy_comments = None;
31 let mut preferences = None;
32 let mut family_selection = None;
33 let mut individual_plugin_selection = None;
34
35 for child in node.children() {
36 match child.tag_name().name() {
37 "policyName" => {
38 if policy_name.is_some() {
39 return Err(FormatError::RepeatedTag("policyName"));
40 }
41 policy_name = child.text_storage().map(StringStorageExt::to_cow);
42 }
43 "policyComments" => {
44 if policy_comments.is_some() {
45 return Err(FormatError::RepeatedTag("policyComments"));
46 }
47 policy_comments = child.text_storage().map(StringStorageExt::to_cow);
48 }
49 "Preferences" => {
50 if preferences.is_some() {
51 return Err(FormatError::RepeatedTag("Preferences"));
52 }
53 preferences = Some(Preferences::from_xml_node(child)?);
54 }
55 "FamilySelection" => {
56 if family_selection.is_some() {
57 return Err(FormatError::RepeatedTag("FamilySelection"));
58 }
59
60 let mut items = vec![];
61 for child in child.children() {
62 if child.tag_name().name() == "FamilyItem" {
63 items.push(FamilyItem::from_xml_node(child)?);
64 } else {
65 assert_empty_text(child)?;
66 }
67 }
68 family_selection = Some(items);
69 }
70 "IndividualPluginSelection" => {
71 if individual_plugin_selection.is_some() {
72 return Err(FormatError::RepeatedTag("IndividualPluginSelection"));
73 }
74
75 let mut items = vec![];
76 for child in child.children() {
77 if child.tag_name().name() == "PluginItem" {
78 items.push(PluginItem::from_xml_node(child)?);
79 } else {
80 assert_empty_text(child)?;
81 }
82 }
83 individual_plugin_selection = Some(items);
84 }
85 _ => assert_empty_text(child)?,
86 }
87 }
88
89 Ok(Self {
90 policy_name: policy_name.ok_or(FormatError::MissingTag("policyName"))?,
91 policy_comments,
92 preferences: preferences.ok_or(FormatError::MissingTag("Preferences"))?,
93 family_selection: family_selection.ok_or(FormatError::MissingTag("FamilySelection"))?,
94 individual_plugin_selection: individual_plugin_selection
95 .ok_or(FormatError::MissingTag("IndividualPluginSelection"))?,
96 })
97 }
98}
99
100#[derive(Debug)]
105pub struct Preferences<'a> {
106 pub server_preferences: ServerPreferences<'a>,
108 pub plugins_preferences: Vec<PluginPreferenceItem<'a>>,
110}
111
112impl<'input> Preferences<'input> {
113 fn from_xml_node(node: Node<'_, 'input>) -> Result<Self, FormatError> {
114 let mut server_preferences = None;
115 let mut plugins_preferences = None;
116
117 for child in node.children() {
118 match child.tag_name().name() {
119 "ServerPreferences" => {
120 if server_preferences.is_some() {
121 return Err(FormatError::RepeatedTag("ServerPreferences"));
122 }
123 server_preferences = Some(ServerPreferences::from_xml_node(child)?);
124 }
125 "PluginsPreferences" => {
126 if plugins_preferences.is_some() {
127 return Err(FormatError::RepeatedTag("PluginsPreferences"));
128 }
129 let mut items = vec![];
130 for item_node in child.children() {
131 if item_node.tag_name().name() == "item" {
132 items.push(PluginPreferenceItem::from_xml_node(item_node)?);
133 } else {
134 assert_empty_text(item_node)?;
135 }
136 }
137 plugins_preferences = Some(items);
138 }
139 _ => assert_empty_text(child)?,
140 }
141 }
142
143 Ok(Self {
144 server_preferences: server_preferences
145 .ok_or(FormatError::MissingTag("ServerPreferences"))?,
146 plugins_preferences: plugins_preferences
147 .ok_or(FormatError::MissingTag("PluginsPreferences"))?,
148 })
149 }
150}
151
152#[derive(Debug)]
155pub struct ServerPreferences<'input> {
156 pub whoami: Cow<'input, str>,
158 pub scan_name: Option<Cow<'input, str>>,
160 pub scan_description: Cow<'input, str>,
162 pub description: Option<Cow<'input, str>>,
164 pub target: Vec<&'input str>,
167 pub port_range: &'input str,
169 pub scan_start_timestamp_seconds: jiff::Timestamp,
171 pub scan_end_timestamp_seconds: Option<jiff::Timestamp>,
173 pub plugin_set: &'input str,
176 pub name: Cow<'input, str>,
178 pub discovery_mode: Option<&'input str>,
183 pub others: HashMap<&'input str, Option<Cow<'input, str>>>,
186}
187
188impl<'input> ServerPreferences<'input> {
189 #[allow(clippy::too_many_lines)]
190 fn from_xml_node(node: Node<'_, 'input>) -> Result<Self, FormatError> {
191 let mut whoami = None;
192 let mut scan_name = None;
193 let mut scan_description = None;
194 let mut description = None;
195 let mut target = None;
196 let mut port_range = None;
197 let mut scan_start_timestamp_seconds = None;
198 let mut scan_end_timestamp_seconds = None;
199 let mut plugin_set = None;
200 let mut name_name = None;
201 let mut discovery_mode = None;
202
203 let mut others = HashMap::new();
204
205 for child in node.children() {
206 if child.tag_name().name() != "preference" {
207 assert_empty_text(child)?;
208 continue;
209 }
210
211 let (name, value) = get_preference_name_value(child)?;
212
213 match name {
214 "whoami" => {
215 if whoami.is_some() {
216 return Err(FormatError::RepeatedTag("whoami"));
217 }
218 whoami = Some(value.to_cow());
219 }
220 "scan_name" => {
221 if scan_name.is_some() {
222 return Err(FormatError::RepeatedTag("scan_name"));
223 }
224 scan_name = Some(value.to_cow());
225 }
226 "scan_description" => {
227 if scan_description.is_some() {
228 return Err(FormatError::RepeatedTag("scan_description"));
229 }
230 scan_description = Some(value.to_cow());
231 }
232 "description" => {
233 if description.is_some() {
234 return Err(FormatError::RepeatedTag("description"));
235 }
236 description = Some(value.to_cow());
237 }
238 "TARGET" => {
239 if target.is_some() {
240 return Err(FormatError::RepeatedTag("TARGET"));
241 }
242 target = Some(value.to_str()?.split(',').collect());
243 }
244 "port_range" => {
245 if port_range.is_some() {
246 return Err(FormatError::RepeatedTag("port_range"));
247 }
248 port_range = Some(value.to_str()?);
249 }
250 "scan_start_timestamp" => {
251 if scan_start_timestamp_seconds.is_some() {
252 return Err(FormatError::RepeatedTag("scan_start_timestamp"));
253 }
254 scan_start_timestamp_seconds =
255 Some(Timestamp::from_second(value.parse::<i64>()?)?);
256 }
257 "scan_end_timestamp" => {
258 if scan_end_timestamp_seconds.is_some() {
259 return Err(FormatError::RepeatedTag("scan_end_timestamp"));
260 }
261 scan_end_timestamp_seconds =
262 Some(Timestamp::from_second(value.parse::<i64>()?)?);
263 }
264 "plugin_set" => {
265 if plugin_set.is_some() {
266 return Err(FormatError::RepeatedTag("plugin_set"));
267 }
268
269 plugin_set = Some(value.to_str()?);
270 }
271 "name" => {
272 if name_name.is_some() {
273 return Err(FormatError::RepeatedTag("name"));
274 }
275 name_name = Some(value.to_cow());
276 }
277 "discovery_mode" => {
278 if discovery_mode.is_some() {
279 return Err(FormatError::RepeatedTag("discovery_mode"));
280 }
281 discovery_mode = Some(value.to_str()?);
282 }
283 other_name => {
284 others.insert(other_name, Some(value.to_cow()));
285 }
286 }
287 }
288
289 Ok(Self {
290 whoami: whoami.ok_or(FormatError::MissingTag("whoami"))?,
291 scan_name,
292 scan_description: scan_description
293 .ok_or(FormatError::MissingTag("scan_description"))?,
294 description,
295 target: target.ok_or(FormatError::MissingTag("TARGET"))?,
296 port_range: port_range.ok_or(FormatError::MissingTag("port_range"))?,
297 scan_start_timestamp_seconds: scan_start_timestamp_seconds
298 .ok_or(FormatError::MissingTag("scan_start_timestamp"))?,
299 scan_end_timestamp_seconds,
300 plugin_set: plugin_set.ok_or(FormatError::MissingTag("plugin_set"))?,
301 name: name_name.ok_or(FormatError::MissingTag("name"))?,
302 discovery_mode,
303 others,
304 })
305 }
306}
307
308fn get_preference_name_value<'input, 'a>(
309 child: Node<'a, 'input>,
310) -> Result<(&'input str, &'a StringStorage<'input>), FormatError> {
311 let mut name = None;
312 let mut value = None;
313
314 for sub_node in child.children() {
315 match sub_node.tag_name().name() {
316 "name" => {
317 if name.is_some() {
318 return Err(FormatError::RepeatedTag("name"));
319 }
320 name = sub_node
321 .text_storage()
322 .map(StringStorageExt::to_str)
323 .transpose()?;
324 }
325 "value" => {
326 if value.is_some() {
327 return Err(FormatError::RepeatedTag("value"));
328 }
329 value = Some(
330 sub_node
331 .text_storage()
332 .unwrap_or(&StringStorage::Borrowed("")),
333 );
334 }
335 _ => assert_empty_text(sub_node)?,
336 }
337 }
338
339 let name = name.ok_or(FormatError::MissingTag("name"))?;
340 let value = value.ok_or(FormatError::MissingTag("value"))?;
341
342 Ok((name, value))
343}
344
345#[derive(Debug)]
347pub struct PluginPreferenceItem<'input> {
348 pub plugin_name: Cow<'input, str>,
349 pub plugin_id: u32,
350 pub full_name: Cow<'input, str>,
351 pub preference_name: Cow<'input, str>,
352 pub preference_type: Cow<'input, str>,
353 pub preference_values: Option<Cow<'input, str>>,
354 pub selected_value: Option<Cow<'input, str>>,
355}
356
357impl<'input> PluginPreferenceItem<'input> {
358 fn from_xml_node(node: Node<'_, 'input>) -> Result<Self, FormatError> {
359 let mut plugin_name = None;
360 let mut plugin_id = None;
361 let mut full_name = None;
362 let mut preference_name = None;
363 let mut preference_type = None;
364 let mut preference_values = None;
365 let mut selected_value = None;
366
367 for child in node.children() {
368 match child.tag_name().name() {
369 "pluginName" => {
370 plugin_name = child.text_storage().map(StringStorageExt::to_cow);
371 }
372 "pluginId" => {
373 let val = child.text().ok_or(FormatError::MissingTag("pluginId"))?;
374 plugin_id = Some(val.parse()?);
375 }
376 "fullName" => {
377 full_name = child.text_storage().map(StringStorageExt::to_cow);
378 }
379 "preferenceName" => {
380 preference_name = child.text_storage().map(StringStorageExt::to_cow);
381 }
382 "preferenceType" => {
383 preference_type = child.text_storage().map(StringStorageExt::to_cow);
384 }
385 "preferenceValues" => {
386 preference_values = child.text_storage().map(StringStorageExt::to_cow);
387 }
388 "selectedValue" => {
389 selected_value = child.text_storage().map(StringStorageExt::to_cow);
390 }
391 _ => assert_empty_text(child)?,
392 }
393 }
394
395 Ok(Self {
396 plugin_name: plugin_name.ok_or(FormatError::MissingTag("pluginName"))?,
397 plugin_id: plugin_id.ok_or(FormatError::MissingTag("pluginId"))?,
398 full_name: full_name.ok_or(FormatError::MissingTag("fullName"))?,
399 preference_name: preference_name.ok_or(FormatError::MissingTag("preferenceName"))?,
400 preference_type: preference_type.ok_or(FormatError::MissingTag("preferenceType"))?,
401 preference_values,
402 selected_value,
403 })
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq)]
408pub enum FamilyStatus {
409 Enabled,
410 Disabled,
411 Mixed,
412}
413
414impl std::str::FromStr for FamilyStatus {
415 type Err = FormatError;
416
417 fn from_str(s: &str) -> Result<Self, Self::Err> {
418 match s {
419 "enabled" => Ok(Self::Enabled),
420 "disabled" => Ok(Self::Disabled),
421 "mixed" => Ok(Self::Mixed),
422 _ => Err(FormatError::UnexpectedFormat("FamilyStatus")),
423 }
424 }
425}
426
427#[derive(Debug)]
429pub struct FamilyItem<'input> {
430 pub family_name: Cow<'input, str>,
431 pub status: FamilyStatus,
432}
433
434impl<'input> FamilyItem<'input> {
435 fn from_xml_node(node: Node<'_, 'input>) -> Result<Self, FormatError> {
436 let mut family_name = None;
437 let mut status = None;
438
439 for child in node.children() {
440 match child.tag_name().name() {
441 "FamilyName" => {
442 if family_name.is_some() {
443 return Err(FormatError::RepeatedTag("FamilyName"));
444 }
445 family_name = child.text_storage().map(StringStorageExt::to_cow);
446 }
447 "Status" => {
448 if status.is_some() {
449 return Err(FormatError::RepeatedTag("Status"));
450 }
451 let val = child.text().ok_or(FormatError::MissingTag("Status"))?;
452 status = Some(val.parse()?);
453 }
454 _ => assert_empty_text(child)?,
455 }
456 }
457
458 Ok(Self {
459 family_name: family_name.ok_or(FormatError::MissingTag("FamilyName"))?,
460 status: status.ok_or(FormatError::MissingTag("Status"))?,
461 })
462 }
463}
464
465#[derive(Debug)]
467pub struct PluginItem<'input> {
468 pub plugin_id: u32,
469 pub plugin_name: Cow<'input, str>,
470 pub family: Cow<'input, str>,
471 pub status: FamilyStatus,
472}
473
474impl<'input> PluginItem<'input> {
475 fn from_xml_node(node: Node<'_, 'input>) -> Result<Self, FormatError> {
476 let mut plugin_id = None;
477 let mut plugin_name = None;
478 let mut family = None;
479 let mut status = None;
480
481 for child in node.children() {
482 match child.tag_name().name() {
483 "PluginId" => {
484 if plugin_id.is_some() {
485 return Err(FormatError::RepeatedTag("PluginId"));
486 }
487 let val = child.text().ok_or(FormatError::MissingTag("PluginId"))?;
488 plugin_id = Some(val.parse()?);
489 }
490 "PluginName" => {
491 if plugin_name.is_some() {
492 return Err(FormatError::RepeatedTag("PluginName"));
493 }
494 plugin_name = child.text_storage().map(StringStorageExt::to_cow);
495 }
496 "Family" => {
497 if family.is_some() {
498 return Err(FormatError::RepeatedTag("Family"));
499 }
500 family = child.text_storage().map(StringStorageExt::to_cow);
501 }
502 "Status" => {
503 if status.is_some() {
504 return Err(FormatError::RepeatedTag("Status"));
505 }
506 let val = child.text().ok_or(FormatError::MissingTag("Status"))?;
507 status = Some(val.parse()?);
508 }
509 _ => assert_empty_text(child)?,
510 }
511 }
512
513 Ok(Self {
514 plugin_id: plugin_id.ok_or(FormatError::MissingTag("PluginId"))?,
515 plugin_name: plugin_name.ok_or(FormatError::MissingTag("PluginName"))?,
516 family: family.ok_or(FormatError::MissingTag("Family"))?,
517 status: status.ok_or(FormatError::MissingTag("Status"))?,
518 })
519 }
520}