Skip to main content

fenir/
lib.rs

1#![deny(clippy::mem_forget)]
2
3/// NVD link Rest API for CVE request
4pub const NVD_CVE_LINK: &str = "https://services.nvd.nist.gov/rest/json/cves/2.0";
5
6/// NVD api key name to use to by the underlying OS
7pub const NVD_KEY_NAME: &str = "NVD_API_KEY";
8
9/// Space substitution in URL
10const URL_SPACE: &str = "%20";
11
12/// The `package` module provides all the necessary components
13/// for managing and manipulating software packages.
14/// This module includes handling errors, defining package concepts,
15/// managing facilities, and other related utilities.
16pub mod package {
17    /// The `concepts` module defines core structures and traits for handling
18    /// package information, changelogs, and CVE (Common Vulnerabilities and Exposures) management
19    ///
20    pub mod concepts {
21        pub use crate::cve::Cve;
22        use crate::facilities::Uppercase;
23        use crate::package::errors::write_error_message_and_exit;
24        use crate::{section, section_level};
25        use regex::Regex;
26        use std::fmt::{Display, Formatter};
27        use std::ops::Add;
28        use std::process::Output;
29
30        /// Represents a package status that's representing the supported status.
31        ///
32        #[derive(Eq, PartialEq, Copy, Clone)]
33        pub enum PackageStatus {
34            /// The package is currently installed on the system.
35            Installed,
36
37            /// The package is not found on the system.
38            NotFound,
39
40            /// The package has been uninstalled from the system. In some system, this status can be
41            /// appearing after a package uninstallation.
42            Uninstalled,
43        }
44
45        /// The `Package` struct is used to encapsulate information about a software package,
46        /// such as the package's name, its version, and its current status in the system.
47        #[derive(PartialEq, Eq, Clone)]
48        pub struct Package {
49            /// The name of the package.
50            pub name: String,
51
52            /// The version of the package.
53            pub version: String,
54
55            /// The current status of the package (e.g., see corresponding enum).
56            pub status: PackageStatus,
57        }
58
59        impl Display for Package {
60            fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
61                write!(formatter, "{} => {}", self.name, self.version)
62            }
63        }
64
65        /// Creates a default `Package` instance.
66        ///
67        /// This function initializes a `Package` with default values for its fields.
68        /// It can be used when a default or placeholder instance of `Package` is required.
69        ///
70        /// # Returns
71        ///
72        /// A `Package` instance with default values:
73        /// * name as an empty string,
74        /// * version as an empty string,
75        /// * status set to `PackageStatus::NotFound`.
76        pub fn default_package() -> Package {
77            Package {
78                name: String::new(),
79                version: String::new(),
80                status: PackageStatus::NotFound,
81            }
82        }
83
84        /// Represents a repository that can hold any type of repository type. The repository type
85        /// depends on the underlying operating system.
86        pub struct Repository<T> {
87            /// The effective repository
88            pub repos: T,
89        }
90
91        /// Trait defining standard requests related to packages.
92        pub trait Request {
93            /// Asks for a package given its name and version, returning a `Package` object.
94            ///
95            /// # Arguments
96            ///
97            /// * `package` - A string slice that holds the name of the package.
98            /// * `version` - A string slice that holds the version of the package.
99            ///
100            /// # Returns
101            ///
102            /// A Package object.
103            ///
104            fn ask_package(&self, package: &str, version: &str) -> Package;
105
106            /// Asks for the changelog of a package. The aim of this command is the access of a standard
107            /// way to the changelog's package. The standard way means the changelog content thanks to
108            /// the packaging standard feature of the current system.
109            ///
110            /// # Arguments
111            ///
112            /// * `package` - The `Package` object for which the changelog is requested.
113            ///
114            /// # Returns
115            ///
116            /// The changelog content.
117            ///
118            fn ask_changelog_package(&self, package: Package) -> String;
119
120            /// Reads the changelog local files of a package. As the opposite of `ask_changelog_package`
121            /// this method is an alternative way to access to local changelogs. Thanks to this method,
122            /// it's possible to access to local changelog files if the standard way give no data.
123            ///
124            /// # Arguments
125            ///
126            /// * `package` - The `Package` object for which the changelog files are to be read.
127            ///
128            /// # Returns
129            ///
130            /// The local changelog files content.
131            ///
132            fn read_changelog_files(&self, package: Package) -> String;
133        }
134
135        /// Displays a list of CVEs (Common Vulnerabilities and Exposures) for a given package.
136        ///
137        /// # Arguments
138        /// * `cve_list` - A vector containing CVE entries.
139        /// * `package` - The package for which the CVEs are being shown.
140        ///
141        /// # Examples
142        ///
143        /// The arguments are showing with the form like this (eg: with command `get-cve less`):
144        /// ```shell
145        /// [ less => 2.9+b1 ]
146        /// CVE-2023-1234
147        /// CVE-2024-2232
148        /// CVE-2024-5664
149        /// ```
150        ///
151        pub fn show_cve_list(cve_list: Vec<Cve>, package: Package) {
152            if cve_list.is_empty() {
153                println!("No cve for: {}", package);
154            } else {
155                section_level!(package);
156                cve_list.iter().for_each(print_cve);
157            }
158        }
159
160        /// Print the cve
161        ///
162        /// # Arguments
163        ///
164        /// * `cve`- The cve to display
165        ///
166        pub fn print_cve(cve: &Cve) {
167            println!(" {}", cve);
168        }
169
170        /// Filters a list of CVEs based on an optional filter string.
171        ///
172        /// # Arguments
173        ///
174        /// * `cve_list` - A vector of `Cve` objects to be filtered.
175        ///   - Each `Cve` represents a Common Vulnerabilities and Exposures entry.
176        ///
177        /// * `filter` - An optional reference to a string to filter the `cve_list`.
178        ///   - If `Some` filter is provided, it will be used for filtering the list of CVEs.
179        ///   - If `None`, the original list will be returned with no filter applied.
180        ///
181        /// # Returns
182        ///
183        /// * A vector of filtered `Cve` objects based on the provided filter string or the original list if no filter is provided.
184        ///
185        /// # Examples
186        ///
187        /// ```shell
188        /// get-cve less --filter 2023
189        ///
190        /// [less => 2.9+b1]
191        /// CVE-2023-1234
192        /// ```
193        ///
194        pub fn filtering_cve(cve_list: Vec<Cve>, filter: Option<&String>) -> Vec<Cve> {
195            match filter {
196                Some(filter) => filter_cve_list(cve_list, &filter.to_uppercase()),
197                None => cve_list,
198            }
199        }
200
201        fn filter_cve_list(cve_list: Vec<Cve>, filter: &str) -> Vec<Cve> {
202            let filter_regex = Regex::new(filter).unwrap();
203            cve_list
204                .into_iter()
205                .filter(|cve| filter_regex.is_match(&cve.reference))
206                .collect()
207        }
208
209        /// Trait defining a contract for extracting CVEs from changelog content.
210        ///
211        pub trait Changelog {
212            /// Extracts a list of CVEs from the given changelog content.
213            ///
214            /// # Arguments
215            ///
216            /// * `changelog_content` - A string slice that holds the content of the changelog.
217            ///
218            /// # Returns
219            ///
220            /// A vector of `Cve` structs that were found in the changelog content.
221            fn cve_list(&self, changelog_content: &str) -> Vec<Cve>;
222        }
223
224        impl Changelog for Package {
225            /// Implementation of the `cve_list` method for the `Package` struct.
226            ///
227            /// This method parses the provided changelog content and returns a list of CVEs.
228            ///
229            /// # Note
230            ///
231            /// The actual implementation uses a regex to search for CVE entries in the provided
232            /// changelog content.
233            fn cve_list(&self, changelog_content: &str) -> Vec<Cve> {
234                const CVE_REGEX_PATTERN: &str = "CVE-[0-9]{4}-[0-9]+";
235                let cve_regex = Regex::new(CVE_REGEX_PATTERN).unwrap();
236
237                let mut cve_found: Vec<Cve> = extract_cves(&cve_regex, changelog_content);
238                simplify_cve_list_content(&mut cve_found);
239
240                cve_found
241            }
242        }
243
244        fn extract_cves(cve_regex: &Regex, content: &str) -> Vec<Cve> {
245            let mut cve_list = Vec::new();
246            if cve_regex.is_match(&content.to_uppercase()) {
247                for cve in cve_regex
248                    .captures_iter(content)
249                    .filter_map(|c| c.get(0).map(|m| m.as_str().to_string()))
250                {
251                    cve_list.push(Cve::new(cve));
252                }
253            }
254            cve_list
255        }
256
257        /// Generates a changelog result based on the output of a changelog command. The alternative
258        /// changelog files content are using in complement of the standard command uses to ensure to collect
259        /// the maximum of cve data.
260        ///
261        /// # Arguments
262        ///
263        /// * `package` - A `Package` struct representing the package for which the changelog is searched.
264        /// * `changelog_command` - An `Output` struct representing the output of the changelog command.
265        /// * `local_changelog_content` - A `String` that represents the local changelog files content to use to
266        ///   with the standard changelog command access.
267        ///
268        /// # Returns
269        ///
270        /// Returns a `String` containing the changelog result if the command was successful, or an empty string otherwise.
271        ///
272        pub fn changelog_result(
273            package: Package,
274            changelog_command: Output,
275            local_changelog_content: String,
276        ) -> String {
277            let is_success = changelog_command.status.success();
278            if is_success {
279                handle_success(
280                    changelog_command.stdout,
281                    package.name.as_str(),
282                    &local_changelog_content,
283                )
284            } else {
285                String::new()
286            }
287        }
288
289        fn handle_success(stdout: Vec<u8>, package_name: &str, alternative: &str) -> String {
290            handle_stdout(stdout, package_name).add(alternative)
291        }
292
293        fn handle_stdout(stdout: Vec<u8>, package_name: &str) -> String {
294            String::from_utf8(stdout).unwrap_or_else(|_| {
295                handle_output_error(package_name);
296                String::new()
297            })
298        }
299
300        fn handle_output_error(package_name: &str) {
301            write_error_message_and_exit("Output error", Some(package_name));
302        }
303
304        /// Simplifies the content of a list of CVEs by sorting and removing duplicates.
305        ///
306        /// # Arguments
307        ///
308        /// * `cve_list` - A mutable reference to a vector of `Cve` objects to be simplified.
309        ///   - This vector will be sorted in place and then deduplicated, removing any duplicate `Cve` entries.
310        ///
311        pub fn simplify_cve_list_content(cve_list: &mut Vec<Cve>) {
312            cve_list.sort();
313            cve_list.dedup();
314        }
315    }
316
317    /// The `errors` module contains utilities for handling errors related to packages.
318    ///
319    pub mod errors {
320        use crate::package::concepts::Package;
321        use std::process::exit;
322
323        /// Shows the standard error message and exit the current process with the system value `1` as error code.
324        ///
325        /// The standard error message is: `Missing argument. See help.`.
326        #[macro_export]
327        macro_rules! no_argument_and_exit {
328            () => {
329                write_error_message_and_exit("Missing argument. See help.", None);
330            };
331        }
332
333        /// Stops the execution if the package is invalid.
334        ///
335        /// # Arguments
336        /// * `package` - A reference to the `Package` to be validated.
337        ///
338        /// An invalid package is one where both name and version are empty.
339        /// In such cases, an error message is written and the process exits.
340        ///
341        pub fn stop_if_invalid_package(package: &Package) {
342            if package.name.is_empty() && package.version.is_empty() {
343                write_error_message_and_exit("Unsupported OS", None);
344            }
345        }
346
347        /// Writes an error message with an optional argument and exits the process with a status code of 1.
348        ///
349        /// # Arguments
350        /// * `message` - The error message to be written.
351        /// * `argument` - An optional argument to be included in the error message.
352        ///
353        pub fn write_error_message_and_exit(message: &str, argument: Option<&str>) {
354            write_error_message(message, argument);
355            exit(1);
356        }
357
358        /// Writes an error message to standard error with an optional argument. No exit value.
359        ///
360        /// # Arguments
361        /// * `message` - The error message to be written.
362        /// * `argument` - An optional argument to be included in the error message.
363        ///
364        pub fn write_error_message(message: &str, argument: Option<&str>) {
365            let final_message = match argument {
366                Some(a) => format!("{}: {}", message, a),
367                None => message.to_string(),
368            };
369
370            eprintln!("{final_message}");
371        }
372    }
373}
374
375/// The `facilities` module provides utility functions for handling
376/// temporary directories and processing command results.
377pub mod facilities {
378    use crate::package::errors::write_error_message_and_exit;
379    use chrono::{Datelike, Days, Local};
380    use csv::{Reader, ReaderBuilder};
381    use enable_ansi_support::enable_ansi_support;
382    use indicatif::{ProgressBar, ProgressStyle};
383    use regex::Regex;
384    use serde_json::Value;
385    use std::fs::File;
386    use std::ops::Add;
387    use std::path::{Path, PathBuf};
388    use std::process::Output;
389    use std::string::FromUtf8Error;
390    use std::{env, io};
391
392    /// Shows a flow of values
393    #[macro_export]
394    macro_rules! flow {
395        ($value:expr) => {
396            flow!(',', $value)
397        };
398
399        ($sep:expr, $value:expr) => {
400            $value.split($sep).for_each(|x| value!(x.trim()))
401        };
402    }
403
404    /// Formats a header for presentation. The text is formatted in upper cases with a green foreground.
405    ///
406    /// # Argument
407    ///
408    /// * `text`- The header text
409    ///
410    /// # Return
411    ///
412    /// A formatted String for this header.
413    #[macro_export]
414    macro_rules! header {
415        ($value:expr) => {
416            section!($value.to_string().to_ascii_uppercase()).fg(Color::Green)
417        };
418    }
419
420    /// Shows a section with a level for section positioning. If one value is given, this value will
421    /// be considering as the level 0.
422    ///
423    #[macro_export]
424    macro_rules! section_level {
425        ($value:expr) => {
426            section_level!(0, $value)
427        };
428
429        ($n:expr, $value:expr) => {{
430            let w: usize = $n;
431            println!("{:>w$} {}", "", section!($value))
432        }};
433    }
434
435    /// Formats a section for presentation.
436    ///
437    /// # Arguments
438    ///
439    /// * `title`- Title's section
440    /// * `fg`- Text's colour. If `None` uses the default colour.
441    /// * `bg`- Text background's colour. If `None` uses the default colour.
442    ///
443    /// # Returns
444    ///
445    /// A formatted String with foreground and background colours.
446    #[macro_export]
447    macro_rules! section {
448        ($value:expr) => {{
449            use termint::enums::Modifier;
450            use termint::widgets::ToSpan;
451
452            format!("[ {} ]", $value.to_string().first_uppercase())
453                .as_str()
454                .modifier(Modifier::BOLD)
455        }};
456
457        ($value:expr, $fg:expr) => {
458            section!($value).fg($fg)
459        };
460
461        ($value:expr, $fg:expr, $bg:expr) => {
462            section!($value, $fg).bg($bg)
463        };
464    }
465
466    /// Show a label and value with a standard formatted way.
467    ///
468    /// # Arguments
469    ///
470    /// * `label` - optional label to add to the output
471    /// * `value` - value to show on the output. The argument's name is using to build an automatic label
472    ///
473    #[macro_export]
474    macro_rules! stamp {
475        ($value:expr) => {{
476            let values = stringify!($value).split(".").collect::<Vec<&str>>();
477            if values.len() == 2 {
478                println!(
479                    "{:>6} {}: {}",
480                    "-",
481                    values[1]
482                        .to_string()
483                        .wording()
484                        .replace("_", " ")
485                        .first_uppercase(),
486                    $value
487                );
488            } else {
489                stamp!($value, $value);
490            }
491        }};
492
493        ($label:expr, $value:expr) => {
494            println!(
495                "{:>6} {}: {}",
496                "-",
497                $label.to_string().first_uppercase(),
498                $value
499            )
500        };
501    }
502
503    /// Shows the formatted value with a previous space of 6 positions
504    ///
505    /// # Argument
506    /// * `value` - The value to show
507    #[macro_export]
508    macro_rules! value {
509        ($value:expr) => {
510            println!("{:>6} {}", "-", $value)
511        };
512    }
513
514    /// Prints a slice of `Value` items, cleaning each value and separating them by commas.
515    ///
516    /// # Argument
517    ///
518    /// * `values` - A reference to a slice of `Value` objects that will be printed.
519    ///
520    pub fn print_values(values: &[Value]) {
521        values.iter().for_each(|value| {
522            match !value.is_object() {
523                true => println!("{:>7} {}", "-", String::cleaning(value.to_string())),
524                false => {
525                    let map_value = value.as_object().unwrap();
526                    map_value.iter().for_each(|(key, value)| {
527                        println!("{:>6} {}:", "-", key.wording());
528                        print_values(std::slice::from_ref(value));
529                    });
530                }
531            };
532        })
533    }
534
535    /// Returns the current date as a formatted string.
536    ///
537    /// # Examples
538    /// ```
539    /// use fenir::facilities::get_current_date;
540    ///
541    /// let current_date = get_current_date();
542    /// println!("Today's date is: {}", current_date); // Output: "2023-10-05" (value may vary based on the current date)
543    /// ```
544    ///
545    /// # Returns
546    /// A `String` containing the current date in the format "YYYY-MM-DD".
547    ///
548    /// # Dependencies
549    /// - `chrono`: This function uses the `chrono` crate for date and time operations.
550    ///
551    /// # Panics
552    /// This function should not panic under normal circumstances.
553    pub fn get_current_date() -> String {
554        let local_date = Local::now();
555        format!(
556            "{}-{:02}-{:02}",
557            local_date.year(),
558            local_date.month(),
559            local_date.day()
560        )
561    }
562
563    /// Constructs a date range string from the provided arguments.
564    ///
565    /// This function extracts two dates from the `args` vector based on the
566    /// index provided by `option_new_date`, and returns them concatenated
567    /// with a semicolon (`;`) delimiter.
568    ///
569    /// # Arguments
570    ///
571    /// * `option_new_date` - An optional index pointing to the position
572    ///   in `args` where the dates should be extracted.
573    /// * `args` - A vector of strings from which the dates are to be extracted.
574    ///
575    /// # Returns
576    ///
577    /// A `String` containing the concatenated date range, or an empty string
578    /// if the dates aren't found.
579    ///
580    /// # Examples
581    ///
582    /// ```
583    /// use fenir::facilities::build_dates_range;
584    ///
585    /// let dates = build_dates_range(Some(0), &["".to_string(), "2023-01-01".to_string(), "2023-12-31".to_string()]);
586    /// assert_eq!(dates, "2023-01-01;2023-12-31");
587    /// ```
588    pub fn build_dates_range(option_new_date: Option<usize>, args: &[String]) -> String {
589        let mut dates = String::new();
590        let regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
591        if let Some(date_first) = args.get(option_new_date.unwrap() + 1) {
592            if regex.is_match(date_first.as_str()) {
593                dates = dates.add(date_first);
594            } else {
595                dates = create_default_date();
596            }
597        } else {
598            dates = create_default_date();
599        }
600
601        if let Some(date_last) = args.get(option_new_date.unwrap() + 2) {
602            if regex.is_match(date_last.as_str()) {
603                dates = dates.add(";").add(date_last);
604            } else {
605                dates = create_next_date(dates);
606            }
607        } else {
608            dates = create_next_date(dates);
609        }
610
611        dates
612    }
613
614    fn create_next_date(dates: String) -> String {
615        let date_next = Local::now();
616        let mut date_result = String::new();
617        let result = date_next.checked_add_days(Days::new(1));
618        if let Some(date) = result {
619            date_result = dates
620                .add(";")
621                .add(date.format("%Y-%m-%d").to_string().as_str());
622        }
623
624        date_result
625    }
626
627    fn create_default_date() -> String {
628        let date_current = Local::now();
629        date_current.format("%Y-%m-%d").to_string()
630    }
631
632    /// Restores the current directory to its previous state and removes the specified temporary directory.
633    ///
634    /// # Arguments
635    /// * `current_dir` - A result containing the path to the current directory.
636    /// * `temp_dir_name` - The name of the temporary directory to be removed.
637    ///
638    /// This function sets the current directory to the one provided in `current_dir`,
639    /// and then removes the temporary directory specified by `temp_dir_name`
640    ///
641    pub fn restore_current_dir_and_remove_temp_dir(
642        current_dir: io::Result<PathBuf>,
643        temp_dir_name: &str,
644    ) {
645        env::set_current_dir(current_dir.unwrap()).expect(
646            "Can't restore initial \
647                directory",
648        );
649        let _ = std::fs::remove_dir_all(format!("/var/tmp/{}", temp_dir_name).as_str());
650    }
651
652    /// Displays the result of a command execution.
653    ///
654    /// # Arguments
655    /// * `command_result` - An `Output` struct representing the result of a command execution.
656    ///
657    /// # Returns
658    /// * `Result<String, FromUtf8Error>` - The output of the command as a `String` if successful,
659    ///   or an error if the output cannot be converted from UTF-8.
660    ///
661    /// This function checks if the command was successful.
662    /// If the command failed, it writes an error message and exits.
663    /// If the output of the command cannot be converted to a string, it also writes an error message and exits.
664    ///
665    pub fn show_command_result(command_result: Output) -> Result<String, FromUtf8Error> {
666        if !command_result.status.success() {
667            write_error_message_and_exit("Package not found", None);
668        }
669
670        let command_output = String::from_utf8(command_result.stdout);
671        if command_output.is_err() {
672            write_error_message_and_exit("Error on output parsing", None);
673        }
674        command_output
675    }
676
677    /// Trait for cleaning string content. Used for remove the `"`, `FIELD_SEPARATOR_CHAR` characters
678    /// from value string.
679    pub trait Cleaning {
680        fn cleaning(value: String) -> String;
681    }
682
683    impl Cleaning for String {
684        fn cleaning(value: String) -> String {
685            let cleaning_char = ['"', FIELD_SEPARATOR_CHAR];
686
687            let mut final_result = value.clone();
688
689            if value.starts_with(cleaning_char) {
690                final_result.remove(0);
691            }
692
693            if value.ends_with(cleaning_char) {
694                final_result.remove(final_result.len() - 1);
695            }
696
697            final_result.replace("\\n", "\n")
698        }
699    }
700
701    /// This trait allows to split a string to words. The words are identifying with the first char as uppercase.
702    ///
703    /// # Example
704    ///
705    /// ```rust
706    /// use fenir::facilities::Wording;
707    ///
708    /// let result = "AStringForExample".to_string();
709    ///
710    /// assert_eq!("Astring for example".to_string(), result.wording())
711    /// ```
712    ///
713    pub trait Wording {
714        fn wording(&self) -> String;
715    }
716
717    impl Wording for String {
718        fn wording(&self) -> String {
719            let mut result = String::new();
720
721            let mut chars = self.chars();
722            let first = chars.nth(0);
723
724            if let Some(c) = first {
725                if c.is_ascii_lowercase() {
726                    result.push(c.to_ascii_uppercase());
727                } else {
728                    result.push(c);
729                }
730
731                let mut previous = ' ';
732
733                chars.for_each(|x| {
734                    if x.is_ascii_lowercase() {
735                        if previous.is_ascii_uppercase() {
736                            result.push(' ');
737                        }
738                        result.push(x);
739                        previous = x;
740                    } else if x.is_ascii_uppercase() {
741                        if previous.is_ascii_lowercase() {
742                            result.push(' ');
743                            previous = ' ';
744                        }
745                        result.push(x.to_ascii_lowercase());
746                    } else {
747                        result.push(x);
748                        previous = x;
749                    }
750                });
751            }
752
753            result
754        }
755    }
756
757    /// Categorizes the String content to verify if all char in the string is only in uppercases.
758    pub trait Categorize {
759        /// Verify if the string content is composing only of upper cases.
760        ///
761        /// # Arguments
762        ///
763        /// * `string` - The string for analysing.
764        ///
765        /// # Returns
766        ///
767        /// `true` if all string characters are uppercases, otherwise `false`.
768        ///
769        /// # Notes
770        ///
771        /// If the string is an empty string, will return `false`.
772        fn only_uppercases(string: String) -> bool;
773    }
774
775    impl Categorize for String {
776        fn only_uppercases(string: String) -> bool {
777            let mut status = false;
778            for chr in string.chars() {
779                if chr == ' ' {
780                    continue;
781                }
782
783                status = chr.is_ascii_uppercase();
784
785                if !status {
786                    break;
787                }
788            }
789
790            status
791        }
792    }
793
794    /// String standardization for data displaying. This trait prepares a string into the
795    /// format awaiting by `hierarchical_show()` function.
796    ///
797    pub trait Standardize {
798        /// Standardization is a process that allows to build a valid String format for hierarchical showing
799        ///
800        /// # Arguments
801        ///
802        /// * `self` - Self element on which the standardization will be applied.
803        /// * `keywords`- Keywords list to take account during the standardization.
804        ///
805        /// # Return
806        ///
807        /// The new String with the standardization
808        ///
809        /// # See
810        /// * `hierarchical_show()`.
811        ///
812        fn standardize(&self, keywords: Vec<&str>) -> String;
813    }
814
815    /// Char field separator for fields content long string from database
816    const FIELD_SEPARATOR_CHAR: char = ':';
817
818    /// String field separator for fields content long string from database
819    pub const FIELD_SEPARATOR_STRING: &str = ":";
820
821    pub const FIELD_SEPARATOR_STRING_LONG: &str = "::";
822
823    impl Standardize for String {
824        fn standardize(&self, keywords: Vec<&str>) -> String {
825            if self.is_empty() {
826                return String::new();
827            }
828
829            let elements = self.split(FIELD_SEPARATOR_CHAR).collect::<Vec<&str>>();
830            let ref_value = 2;
831            let mut counter = 0;
832            let mut result = Vec::<String>::new();
833
834            elements.iter().for_each(|s| {
835                if s.is_empty() {
836                    if counter != ref_value {
837                        counter += 1;
838                        result.push(FIELD_SEPARATOR_STRING.to_string());
839                    } else {
840                        counter = 0;
841                    }
842                } else {
843                    if result.last().is_some() && result.last().unwrap() != FIELD_SEPARATOR_STRING {
844                        result.push(FIELD_SEPARATOR_STRING.to_string());
845                    }
846                    if keywords.contains(s) {
847                        result.push(s.to_string().to_uppercase());
848                    } else if String::only_uppercases(s.to_string()) {
849                        result.push(s.to_string().to_lowercase());
850                    } else {
851                        result.push(s.to_string());
852                    }
853
854                    counter = 0;
855                }
856            });
857
858            begin_complement(&mut result);
859
860            end_complement(&mut result);
861
862            String::from_iter(result)
863        }
864    }
865
866    fn begin_complement(result: &mut Vec<String>) {
867        result[0..2]
868            .iter_mut()
869            .filter(|x| *x != FIELD_SEPARATOR_STRING)
870            .for_each(|x| x.insert(0, FIELD_SEPARATOR_STRING.parse().unwrap()));
871    }
872
873    fn end_complement(result: &mut Vec<String>) {
874        let length = result.len();
875        result[length - 2..length]
876            .iter_mut()
877            .filter(|x| *x != FIELD_SEPARATOR_STRING)
878            .for_each(|x| x.push(FIELD_SEPARATOR_STRING_LONG.parse().unwrap()));
879    }
880
881    /// Transform the list of keywords tho a String value with some specifics separators.
882    /// With this trait, all keywords will be separated with specific separators.
883    ///
884    pub trait Transform {
885        fn transform(&self, keywords: Vec<&str>) -> String;
886    }
887
888    /// Transform the string value to another string that replace all separator `::` with `-` and end
889    /// of subline with the `\n`.
890    impl Transform for String {
891        fn transform(&self, keywords: Vec<&str>) -> String {
892            let mut result = String::new();
893            let tmp = format!("{}{}", FIELD_SEPARATOR_STRING, FIELD_SEPARATOR_STRING);
894            let mark = tmp.as_str();
895
896            let mut previous_key = "";
897            keywords.iter().for_each(|&keyword| {
898                let regex = Regex::new(keyword.to_string().as_str())
899                    .unwrap_or_else(|_| panic!("Invalid regular expression pattern: {}", keyword));
900                regex
901                    .split(self)
902                    .filter(|&sentence| !sentence.is_empty())
903                    .for_each(|sentence| {
904                        if sentence == mark {
905                            result.push_str(mark);
906                            result.push_str(keyword);
907                            previous_key = keyword;
908                        } else if sentence.chars().nth(0).unwrap() == FIELD_SEPARATOR_CHAR {
909                            if previous_key.is_empty() {
910                                result.push_str(mark);
911                                result.push_str(keyword);
912                            }
913                            result.push_str(sentence);
914                        } else {
915                            result = result.replace(keyword, "");
916                            result.push_str(mark);
917                            result.push_str(keyword);
918                            result.push(FIELD_SEPARATOR_CHAR);
919
920                            if !sentence.contains(previous_key) || previous_key.is_empty() {
921                                result.push_str(sentence);
922                            }
923
924                            previous_key = keyword;
925                        }
926                    });
927            });
928
929            if !result.ends_with(mark) && result.ends_with(FIELD_SEPARATOR_STRING) {
930                result.push(FIELD_SEPARATOR_CHAR);
931            }
932
933            result
934        }
935    }
936
937    /// Reduce a string to the given size. If the string length is > to the size value, the end of the
938    /// string is replaced by the `...`.
939    ///
940    /// # Example
941    ///
942    /// ```rust
943    /// use fenir::facilities::Reduce;
944    ///
945    /// let original = String::from("This is an example string");
946    /// let result = original.reduce(16);
947    ///
948    /// assert_eq!(String::from("This is an ex..."), result);
949    /// ```
950    pub trait Reduce {
951        /// Reduce the string to the given size.
952        ///
953        /// # Argument
954        ///
955        /// * `size` - The size of the new string
956        fn reduce(&self, size: usize) -> String;
957    }
958
959    impl Reduce for String {
960        fn reduce(&self, size: usize) -> String {
961            if self.is_empty()
962                || size == 0
963                || size > self.len()
964                || self == Value::Null.to_string().as_str()
965            {
966                self.clone()
967            } else if size <= "...".len() {
968                String::from("...")
969            } else {
970                let reduced =
971                    self.escape_default().to_string()[0..(size - "...".len())].to_string();
972                format!("{reduced}...")
973            }
974        }
975    }
976
977    /// Configures and returns a CSV reader with the specific settings.
978    ///
979    /// This function sets up a CSV reader with custom options such as delimiter,
980    /// headers, and flexible end-of-line handling.
981    ///
982    /// # Arguments
983    /// * `csv_path`- Path to the csv file.
984    ///
985    /// # Settings
986    ///
987    /// * `delimiter` - A `u8` byte set on `,` separator.
988    /// * `has_headers`- A bool that specifies the CSV contains some header, set to `true`.
989    /// * `flexible` - A `bool` that specifies whether to handle flexible end-of-line characters, set to `true`.
990    ///
991    /// # Returns
992    ///
993    /// * `csv::Reader<File>` - A `csv::Reader` configured based on the given settings.
994    pub fn configure_csv_reader(csv_path: &Path) -> csv::Result<Reader<File>> {
995        ReaderBuilder::new()
996            .delimiter(b',')
997            .flexible(true)
998            .has_headers(true)
999            .from_path(csv_path)
1000    }
1001
1002    /// Shows the element list as a hierarchical design.
1003    ///
1004    /// # Arguments
1005    /// * `section` - The section name for this hierarchy.
1006    /// * `elements` - List of elements separated by the ':' separator.
1007    ///
1008    pub fn hierarchical_show(section: &str, elements: String) {
1009        if elements.is_empty() {
1010            return;
1011        }
1012
1013        println!();
1014        let mut width: i32 = 0;
1015
1016        let str_cleaning = String::cleaning(elements);
1017
1018        section_level!(2, section);
1019        if !str_cleaning.contains(FIELD_SEPARATOR_STRING) {
1020            let w = (width + 7) as usize;
1021            println!("{:>w$} {}\n", "-", str_cleaning.trim());
1022            return;
1023        }
1024
1025        let parts: Vec<&str> = str_cleaning.split(FIELD_SEPARATOR_STRING).collect();
1026        let ref_section = parts.get(1).unwrap_or(&"");
1027
1028        section_show(ref_section, 0);
1029
1030        let mut old_value = vec![];
1031        let mut last_section = ref_section;
1032
1033        parts.iter().for_each(|part| {
1034            if !part.is_empty() && last_section != part {
1035                if String::only_uppercases(part.to_string()) {
1036                    last_section = part;
1037                    if last_section == ref_section {
1038                        width = -1;
1039                    }
1040                    width += 1;
1041                    section_show(part, width as usize);
1042                    old_value.clear();
1043                } else if !part.is_empty() && !old_value.contains(part) {
1044                    let w = (width + 7) as usize;
1045                    println!("{:>w$} {}", "-", part.trim());
1046                    old_value.push(*part);
1047                }
1048            }
1049        });
1050    }
1051
1052    fn section_show(value: &str, width: usize) {
1053        let lower = String::from(value).to_ascii_lowercase();
1054        let wording = lower.wording();
1055        let words: Vec<&str> = wording.as_str().split(' ').collect();
1056        match words.len() {
1057            1..3 => {
1058                if !value.is_empty() {
1059                    let w = width + 3;
1060                    section_level!(w, value);
1061                }
1062            }
1063            _ => {
1064                if !value.is_empty() {
1065                    let w = width + 7;
1066                    println!("{:>w$} {}", "-", value.trim());
1067                }
1068            }
1069        };
1070    }
1071
1072    /// Parse the current string for returning a string with the first character in uppercase.
1073    pub trait Uppercase {
1074        /// Transform the first character of a String to an uppercase
1075        ///
1076        /// # Returns
1077        /// The new string with the first character in uppercase
1078        fn first_uppercase(&self) -> String;
1079    }
1080
1081    impl Uppercase for String {
1082        fn first_uppercase(&self) -> String {
1083            let c = self.chars().nth(0).unwrap();
1084            format!("{}{}", c.to_ascii_uppercase(), &self[1..])
1085        }
1086    }
1087
1088    /// Configure the terminal for insure the ansi support colours
1089    /// for all platforms.
1090    pub fn configure_terminal() {
1091        let _ = enable_ansi_support();
1092    }
1093
1094    /// Progress bar for text console.
1095    pub struct ProgressText {
1096        progress: ProgressBar,
1097    }
1098
1099    impl ProgressText {
1100        /// Create a new occurrence of the ProgressText.
1101        ///
1102        /// # Arguments
1103        /// * `current` - Current value of the progress bar text.
1104        /// * `max`- Max value of the progress bar text.
1105        /// * `message`- Complement message adding at the end of the progress bar text.
1106        pub fn new(current: usize, max: usize, message: String) -> ProgressText {
1107            let progress = ProgressBar::new(max as u64)
1108                .with_message(message)
1109                .with_position(current as u64);
1110            progress.set_style(
1111                ProgressStyle::default_bar()
1112                    .template(
1113                        "[{elapsed_precise}] {bar:40.cyan/blue} {pos}/{len} records for '{msg}'",
1114                    )
1115                    .unwrap(),
1116            );
1117
1118            Self { progress }
1119        }
1120
1121        /// Increment the value of the progress bar and show the progression.
1122        ///
1123        /// # Note
1124        ///
1125        /// If the value of the progress bar is reach to the max value, this progress bar is considering as done.
1126        pub fn progress(&mut self) {
1127            if self.progress.position() == self.progress.length().unwrap() {
1128                self.progress.finish();
1129            } else {
1130                self.progress.inc(1);
1131            }
1132        }
1133
1134        /// # Return
1135        /// The current value of the progress bar.
1136        pub fn value(&self) -> usize {
1137            self.progress.position() as usize
1138        }
1139
1140        /// # Return
1141        /// The max value of the progress bar
1142        pub fn max(&self) -> usize {
1143            self.progress.length().unwrap() as usize
1144        }
1145
1146        /// Status of the progress bar.
1147        ///
1148        /// # Return
1149        /// `true` if the progress bar is done. That means if the progress bar is done, some internal values
1150        /// will be unavailable (like message).
1151        pub fn is_done(&self) -> bool {
1152            self.progress.is_finished()
1153        }
1154    }
1155}
1156
1157/// Several features for specific Os implementation. Provide several methods that can be used
1158/// for Os support implementations.
1159pub mod os {
1160    use std::path::{Path, PathBuf};
1161    use std::{env, fs};
1162
1163    /// The `OSFamily` enum represents different families of operating systems.
1164    ///
1165    /// This enum provides several variants:
1166    /// - `Debian`: Represents Debian-based operating systems.
1167    /// - `macOS`: Represents the macOS operating systems.
1168    /// - `RedHat`: Represents RedHat-based operating systems.
1169    /// - `Unsupported`: Represents unsupported or unrecognized operating systems.
1170    /// - `Windows`: Represents the Windows operating systems.
1171    ///
1172    #[derive(PartialEq, Eq)]
1173    pub enum OSFamily {
1174        /// Represents Debian-based operating systems.
1175        Debian,
1176
1177        /// Represents Macos-based operating systems.
1178        MacOS,
1179
1180        /// Represents RedHat-based operating systems.
1181        RedHat,
1182
1183        /// Represents unsupported or unrecognized operating systems.
1184        Unsupported,
1185
1186        ///Represents the Windows-based operating systems.
1187        Windows,
1188    }
1189
1190    // Constants for file paths
1191    const DEBIAN_PATH: &str = "/usr/bin/apt-get";
1192    const MACOS_PATH: &str = "/usr/bin/sw_vers";
1193    const REDHAT_PATH: &str = "/usr/bin/rpm";
1194    const WINDOWS_PATH: &str = "c:\\";
1195
1196    /// Control if the current Os is included into the supported Os.
1197    pub struct SupportedOs;
1198    impl SupportedOs {
1199        /// Detects the operating system by checking for the existence of certain files.
1200        ///
1201        /// # Returns
1202        ///
1203        /// * `OSFamily::Debian` - if the system has `/usr/bin/apt-get`.
1204        /// * `OSFamily::macOS` - if the system has `/usr/bin/sw_vers`.
1205        /// * `OSFamily::RedHat` - if the system has `/usr/bin/rpm`.
1206        /// * `OSFamily::Windows` - if the system has `c:\\`.
1207        /// * `OSFamily::Unsupported` - if none of those files are found.
1208        pub fn supported_os() -> OSFamily {
1209            Debian::detect_os()
1210        }
1211    }
1212
1213    /// Detect the current os family.
1214    trait OsDetector {
1215        /// Detects the current OSFamily.
1216        ///
1217        /// # Returns
1218        ///
1219        /// A value of type of `OSFamily`.
1220        fn detect_os() -> OSFamily;
1221    }
1222
1223    /// Debian Os detector
1224    struct Debian;
1225    impl OsDetector for Debian {
1226        fn detect_os() -> OSFamily {
1227            if file_exists(DEBIAN_PATH) {
1228                OSFamily::Debian
1229            } else {
1230                Redhat::detect_os()
1231            }
1232        }
1233    }
1234
1235    /// Redhat Os detector
1236    struct Redhat;
1237    impl OsDetector for Redhat {
1238        fn detect_os() -> OSFamily {
1239            if file_exists(REDHAT_PATH) {
1240                OSFamily::RedHat
1241            } else {
1242                MacOS::detect_os()
1243            }
1244        }
1245    }
1246
1247    /// MacOs detector
1248    struct MacOS;
1249    impl OsDetector for MacOS {
1250        fn detect_os() -> OSFamily {
1251            if file_exists(MACOS_PATH) {
1252                OSFamily::MacOS
1253            } else {
1254                Windows::detect_os()
1255            }
1256        }
1257    }
1258
1259    /// Windows Os detector.
1260    struct Windows;
1261    impl OsDetector for Windows {
1262        fn detect_os() -> OSFamily {
1263            if file_exists(WINDOWS_PATH) {
1264                OSFamily::Windows
1265            } else {
1266                OSFamily::Unsupported
1267            }
1268        }
1269    }
1270
1271    /// Checks if a file exists at the given path.
1272    fn file_exists(path: &str) -> bool {
1273        Path::new(path).exists()
1274    }
1275
1276    /// Find the tyr home's path directory according to current OS.
1277    ///
1278    /// # Returns
1279    ///
1280    /// The home path directory. If the OS is not supported, the home tyr path directory will be defined on the current dir.
1281    pub fn find_tyr_home_path() -> PathBuf {
1282        match SupportedOs::supported_os() {
1283            OSFamily::Windows => Path::new(Option::unwrap(env::var_os("HOMEDRIVE")).as_os_str())
1284                .join(Option::unwrap(env::var_os("HOMEPATH")).as_os_str())
1285                .join(".tyr"),
1286            OSFamily::Unsupported => Path::new(".").join(".tyr"),
1287            _ => Path::new(Option::unwrap(env::var_os("HOME")).as_os_str()).join(".tyr"),
1288        }
1289    }
1290
1291    /// Create the tyr home path directory.
1292    ///
1293    /// # Arguments
1294    ///
1295    /// * `tyr_path` - the tyr path that will be used as tyr home path.
1296    ///
1297    /// # Notes
1298    ///
1299    /// If the tyr home path directory exists yet, it will not be recreated.
1300    pub fn create_tyr_home(tyr_path: &Path) {
1301        if !tyr_path.exists() {
1302            fs::create_dir(tyr_path).expect("Can't create tyr home path");
1303        }
1304    }
1305
1306    /// Parse the raw id given as parameter and return the found id as u32
1307    ///
1308    /// # Arguments
1309    ///
1310    /// * `raw_id` - The raw cwe id of the form of `CWE-<num>` or `CAPEC-<num>`.
1311    ///
1312    /// # Results
1313    ///
1314    /// The id that is corresponding to the `<num>` part.
1315    pub fn parse_id(raw_id: &str, prefix: &str) -> Option<u32> {
1316        let parts: Vec<&str> = raw_id.split('-').collect();
1317        if parts.len() == 2 && parts[0].eq_ignore_ascii_case(prefix) {
1318            parts[1].parse().ok()
1319        } else {
1320            None
1321        }
1322    }
1323}
1324
1325/// Module for database features (based on SQLite)
1326pub mod database {
1327    use crate::capec::CapecRecord;
1328    use crate::cwe::CweRecord;
1329    use crate::download::download_mitre_archive;
1330    use crate::facilities::{FIELD_SEPARATOR_STRING, Uppercase};
1331    use crate::mitre::MitreRecord;
1332    use crate::network::{fetch_json_response, parse_response};
1333    use crate::os::{create_tyr_home, find_tyr_home_path};
1334    use crate::package::errors::{write_error_message, write_error_message_and_exit};
1335    use crate::section;
1336    use regex::Regex;
1337    use rusqlite::{Connection, Rows, Statement, params};
1338    use serde_json::Value;
1339    use std::error::Error;
1340    use std::path::{Path, PathBuf};
1341
1342    /// Mitre database name
1343    pub const MITRE_DB_NAME: &str = "mitre.db";
1344
1345    /// Open the database connexion
1346    /// 
1347    /// # Example
1348    /// ```no_run
1349    /// match db_mitre!() {
1350    ///     Ok(connection) => println!("Connected with {:?}", connection),
1351    ///     Err(_) => eprintln!("Connection failed"),
1352    /// };
1353    /// ```
1354    #[macro_export]
1355    macro_rules! db_mitre {
1356        () => {
1357            Connection::open(db_mitre_path())
1358        };
1359    }
1360
1361    /// The `MitreData` struct represents the necessary information to Mitre Data to used to.
1362    ///
1363    /// # Fields
1364    ///
1365    /// * `url` - A `String` that holds the URL for querying MITRE data to be used.
1366    /// * `sources` - A `Vector<String>` that denotes the list of path to download to be processed.
1367    #[derive(Clone)]
1368    pub struct MitreData {
1369        /// Data's name
1370        pub name: &'static str,
1371
1372        /// The url to use for the mitre query
1373        pub url: &'static str,
1374
1375        /// List of sources files
1376        pub files: Vec<&'static str>,
1377    }
1378
1379    /// Defines a MitreData definition.
1380    ///
1381    pub trait MitreDefinition {
1382        /// Define the MitreData
1383        fn define() -> MitreData;
1384    }
1385
1386    /// Define the tables definition for the Mitre data
1387    pub trait MitreDatabase {
1388        /// Gets the list of table definitions
1389        fn table_definitions(&self) -> Vec<(&'static str, &'static str)>;
1390
1391        /// Gets the list of index definitions
1392        fn index_definitions(&self) -> Vec<(&'static str, &'static str)>;
1393    }
1394
1395    /// Manage the database according to the database model defined with `MitreDatabase`.
1396    pub struct Database<T: MitreDatabase> {
1397        pub database: T,
1398    }
1399
1400    impl<T: MitreDatabase> Database<T> {
1401        /// Checks if the database is valid or not.
1402        pub fn check_db(&self) {
1403            let conn = db_mitre!().unwrap();
1404
1405            self.database
1406                .table_definitions()
1407                .iter()
1408                .for_each(|(table, _)| {
1409                    let query = format!("SELECT COUNT(*) FROM {}", table);
1410                    let result = conn.query_row(query.as_str(), [], |row| row.get::<usize, u32>(0));
1411                    if result.is_err() {
1412                        write_error_message_and_exit(
1413                            result.err().unwrap().to_string().as_str(),
1414                            None,
1415                        );
1416                    } else {
1417                        println!(
1418                            "Successfully queried MITRE table '{}' ({} records)",
1419                            table,
1420                            result.ok().unwrap()
1421                        );
1422                    }
1423                });
1424        }
1425
1426        /// Create the table list.
1427        pub fn init_db(&self) {
1428            self.drop_tables();
1429
1430            match db_mitre!() {
1431                Ok(connection) => {
1432                    self.database
1433                        .table_definitions()
1434                        .iter()
1435                        .for_each(|(name, definition)| {
1436                            match connection.execute(definition, params![]) {
1437                                Ok(_) => println!("Successfully created MITRE '{}'", name),
1438                                Err(e) => eprintln!("Error creating MITRE '{}' : {:?}", name, e),
1439                            };
1440                        });
1441
1442                    self.drop_indexes();
1443
1444                    self.database
1445                        .index_definitions()
1446                        .iter()
1447                        .for_each(|(idx, definition)| {
1448                            match connection.execute(definition, params![]) {
1449                                Ok(_) => println!("Successfully created MITRE index '{}'", idx),
1450                                Err(e) => eprintln!("Error creating MITRE index {}: {:?}", idx, e),
1451                            }
1452                        });
1453                }
1454                Err(e) => {
1455                    write_error_message_and_exit(
1456                        "Database connection error",
1457                        Some(e.to_string().as_str()),
1458                    );
1459                }
1460            };
1461        }
1462
1463        fn drop_tables(&self) {
1464            let connection = db_mitre!().unwrap();
1465            self.database
1466                .table_definitions()
1467                .iter()
1468                .for_each(|(table, _)| {
1469                    let _ = connection
1470                        .execute(
1471                            format!("DROP TABLE IF EXISTS {}", table).as_str(),
1472                            params![],
1473                        )
1474                        .unwrap_or_else(|_| panic!("Error on dropping table: {}", table));
1475                });
1476        }
1477
1478        fn drop_indexes(&self) {
1479            let connection = db_mitre!().unwrap();
1480            self.database
1481                .index_definitions()
1482                .iter()
1483                .for_each(|(idx, _)| {
1484                    let _ = connection
1485                        .execute(format!("DROP INDEX IF EXISTS {}", idx).as_str(), params![])
1486                        .unwrap_or_else(|_| panic!("Error on index dropping: {}", idx));
1487                });
1488        }
1489    }
1490
1491    /// Executes a given query and processes the response.
1492    ///
1493    /// This function takes a `Result` containing a `String` query or an error message.
1494    /// It unwraps the query string, fetches the response from the URL specified by the query,
1495    /// and then parses this response into a `Value`.
1496    ///
1497    /// # Arguments
1498    ///
1499    /// * `query` - A `Result` wrapping a `String` query or an error message.
1500    ///
1501    /// # Returns
1502    ///
1503    /// * A `Value` obtained from parsing the response. If the response fetching or parsing
1504    ///   fails, it returns a default `Value`.
1505    ///
1506    /// # Panics
1507    ///
1508    /// This function will panic if `query` is an `Err` variant.
1509    ///
1510    /// # Example
1511    ///
1512    /// ```rust
1513    /// use fenir::database::execute_query;
1514    ///
1515    /// let query = Ok(String::from("http://example.com/api"));
1516    /// let value = execute_query(query);
1517    /// ```
1518    pub fn execute_query(query: Result<String, &str>) -> Value {
1519        let query_str = query.unwrap();
1520        let response = fetch_json_response(&query_str);
1521
1522        parse_response(response)
1523    }
1524
1525    /// New type for injector function that will be used for injecting data into the local
1526    /// SQLite database.
1527    type InjectorFunction = fn(&Path) -> Result<(), Box<dyn Error>>;
1528
1529    /// Refresh the Mitre data that will be injected into the local database.
1530    ///
1531    /// # Arguments
1532    ///
1533    /// * `url` - The url to used to,
1534    /// * `file`- The file to download to.
1535    /// * `injector`- The method used for injecting the csv into the local database.
1536    ///
1537    /// # Results
1538    ///
1539    /// The final result of these operations.
1540    pub fn refresh_data_mitre_into_database(url: String, file: String, injector: InjectorFunction) {
1541        // Download and extract the archive
1542        let env_path = find_tyr_home_path();
1543        create_tyr_home(&env_path);
1544        let source_file = match download_mitre_archive(url, file.clone(), &env_path) {
1545            Err(e) => {
1546                write_error_message_and_exit("Error during download", Some(e.to_string().as_str()));
1547                String::new()
1548            }
1549            Ok(file) => file.clone(),
1550        };
1551
1552        if !source_file.is_empty() {
1553            println!(
1554                "Source file downloaded successfully: {}",
1555                source_file.clone()
1556            );
1557        }
1558
1559        match injector(&env_path.clone().join(source_file.clone())) {
1560            Err(e) => {
1561                write_error_message(
1562                    "Error during archive injection",
1563                    Some(e.to_string().as_str()),
1564                );
1565            }
1566            _ => {
1567                println!("Operation completed successfully: {}\n", file.clone());
1568            }
1569        }
1570
1571        let _ = std::fs::remove_file(env_path.join(file.clone()));
1572        let _ = std::fs::remove_file(env_path.join(source_file.clone()));
1573    }
1574
1575    /// find the cwe by its id.
1576    ///
1577    /// # Arguments
1578    ///
1579    /// * `cwe_id`- CWE's id
1580    ///
1581    /// # Returns
1582    ///
1583    /// A `CweRecord` if CWE found or `None`.
1584    ///
1585    pub fn find_cwe_by_id(cwe_id: u32) -> Option<CweRecord> {
1586        let connection = db_mitre!().unwrap();
1587        let mut statement = connection
1588            .prepare("SELECT * FROM cwe WHERE id= :id_cwe")
1589            .ok()?;
1590        let mut rows = statement.query(&[(":id_cwe", &cwe_id)]).ok()?;
1591        let row = rows.next().ok()?;
1592        if row.is_some() {
1593            Some(CweRecord {
1594                id: row?.get(0).unwrap(),
1595                name: row?.get(1).unwrap(),
1596                description: row?.get(2).unwrap(),
1597                extended: row?.get(3).unwrap_or("".to_string()),
1598                consequences: row?.get(4).unwrap(),
1599                detections: row?.get(5).unwrap_or("".to_string()),
1600                mitigations: row?.get(6).unwrap_or("".to_string()),
1601                attacks: row?.get(7).unwrap_or("".to_string()),
1602            })
1603        } else {
1604            None
1605        }
1606    }
1607
1608    /// find the capec by its id.
1609    ///
1610    /// # Arguments
1611    ///
1612    /// * `capec_id`- Capec's id
1613    ///
1614    /// # Returns
1615    ///
1616    /// A `CapecRecord` if CAPEC found or `None`.
1617    ///
1618    pub fn find_capec_by_id(capec_id: u32) -> Option<CapecRecord> {
1619        let connection = db_mitre!().unwrap();
1620        let mut statement = connection
1621            .prepare("SELECT * FROM capec WHERE id= :id_capec")
1622            .ok()?;
1623        let mut rows = statement.query(&[(":id_capec", &capec_id)]).ok()?;
1624        let row = rows.next().ok()?;
1625        if row.is_some() {
1626            Some(CapecRecord {
1627                id: row?.get(0).unwrap(),
1628                name: row?.get(1).unwrap(),
1629                abstraction: row?.get(2).unwrap(),
1630                status: row?.get(3).unwrap(),
1631                description: row?.get(4).unwrap(),
1632                alternates: row?.get(5).unwrap_or("".to_string()),
1633                likelihood: row?.get(6).unwrap_or("".to_string()),
1634                severity: row?.get(7).unwrap_or("".to_string()),
1635                attacks: row?.get(8).unwrap_or("".to_string()),
1636                flow: row?.get(9).unwrap_or("".to_string()),
1637                prerequisites: row?.get(10).unwrap_or("".to_string()),
1638                skills: row?.get(11).unwrap_or("".to_string()),
1639                resources: row?.get(12).unwrap_or("".to_string()),
1640                indicators: row?.get(13).unwrap_or("".to_string()),
1641                consequences: row?.get(14).unwrap_or("".to_string()),
1642                mitigations: row?.get(15).unwrap_or("".to_string()),
1643                instances: row?.get(16).unwrap_or("".to_string()),
1644                weaknesses: row?.get(17).unwrap_or("".to_string()),
1645                taxonomy: row?.get(18).unwrap_or("".to_string()),
1646            })
1647        } else {
1648            None
1649        }
1650    }
1651
1652    /// Analyse a string criteria and split each element by separating the words criteria from the operators.
1653    ///
1654    /// # Arguments
1655    ///
1656    /// * `criteria` - Criteria string for analysis
1657    ///
1658    /// # Results
1659    ///
1660    /// A couple of values where the first argument is the list of operators and the second the list of words of the criteria
1661    ///
1662    pub fn criteria_analysis(criteria: String) -> Option<(Vec<String>, Vec<String>)> {
1663        if criteria.is_empty() {
1664            None
1665        } else {
1666            let regex = Regex::new(r"\b(?:and|not|or)\b");
1667            let result = regex
1668                .clone()
1669                .unwrap()
1670                .captures_iter(&criteria)
1671                .collect::<Vec<_>>();
1672            let mut operators = Vec::<String>::new();
1673            result.iter().for_each(|cap| {
1674                operators.push(cap.get(0).unwrap().as_str().to_string());
1675            });
1676
1677            let mut crit = criteria.clone();
1678            ["not ", "and ", "or "].iter().for_each(|&o| {
1679                crit = crit.replace(o, "^");
1680            });
1681
1682            let words = words_collector(criteria);
1683            Some((words, operators))
1684        }
1685    }
1686    
1687    fn words_collector(criteria: String) -> Vec<String> {
1688        
1689        let mut crit = criteria.clone();
1690        ["not ", "and ", "or "].iter().for_each(|&o| {
1691            crit = crit.replace(o, "^");
1692        });
1693
1694        let mut words = Vec::<String>::new();
1695        let mut others = Vec::<char>::new();
1696        let mut keep_space = false;
1697        let chars = crit.chars();
1698        chars.for_each(|c| {
1699            if c != '\'' && c == '^' {
1700                if !others.is_empty() {
1701                    words.push(others.iter().collect());
1702                    others.clear();
1703                }
1704                words.push(String::new());
1705            } else if keep_space || c != ' ' {
1706                others.push(c);
1707            } else {
1708                keep_space = !keep_space;
1709            }
1710        });
1711
1712        if !others.is_empty() {
1713            words.push(others.iter().collect());
1714        }
1715        
1716        words
1717    }
1718
1719    /// Split the arguments list to `domain` and `criteria`. `domain` is corresponding to
1720    /// the name of field into the concerning table's database.
1721    ///
1722    /// # Arguments
1723    ///
1724    ///  * `arguments` - List of arguments that's composing the query
1725    ///
1726    /// # Results
1727    ///
1728    /// A couple of values with the domain for the first field, and left of criteria for the
1729    /// second field.
1730    pub fn find_domain_and_criteria(arguments: &[String]) -> Option<(String, String)> {
1731        let string_args = arguments.iter().last().unwrap();
1732        let mut pattern = "=";
1733        let is_sep = match string_args.find(pattern) {
1734            Some(s) => Some(s),
1735            None => {
1736                pattern = "not";
1737                string_args.find(pattern)
1738            }
1739        };
1740
1741        if let Some(pos_sep) = is_sep {
1742            let domain = String::from(string_args[0..pos_sep].trim());
1743            let criteria = match pattern {
1744                "not" => format!("not {}", string_args[pos_sep + pattern.len()..].trim()),
1745                _ => String::from(string_args[pos_sep + pattern.len()..].trim()),
1746            };
1747
1748            match domain.is_empty() || criteria.is_empty() {
1749                true => None,
1750                false => Some((domain, criteria)),
1751            }
1752        } else {
1753            None
1754        }
1755    }
1756
1757    /// Associate the list of arguments with the SQL query in construction
1758    ///
1759    /// # Arguments
1760    ///
1761    /// * `words` - list of arguments words
1762    /// * `statement`- statement SQL in construction
1763    ///
1764    /// # Results
1765    ///
1766    /// The statement completed by the criteria binding
1767    ///
1768    pub fn bind_criteria<'a>(
1769        words: &[String],
1770        statement: Result<Statement<'a>, rusqlite::Error>,
1771    ) -> Statement<'a> {
1772        let mut counter = 1;
1773        let mut statement = statement.unwrap();
1774        let nb = words.len();
1775        for word in words.iter().take(nb) {
1776            if !word.trim().is_empty() {
1777                statement
1778                    .raw_bind_parameter(counter, format!("%{}%", word.to_lowercase().trim()))
1779                    .unwrap_or_else(|_| {
1780                        panic!(
1781                            "{}",
1782                            format!("Can't bind parameter: {}", word.to_lowercase()).trim()
1783                        )
1784                    });
1785                counter += 1;
1786            }
1787        }
1788
1789        statement
1790    }
1791
1792    /// Prepare the query model given as parameter with the elements of criteria
1793    ///
1794    /// # Arguments
1795    ///
1796    /// * `query_template`- Query model used to prepare the final query
1797    /// * `words`- list of words to use as criteria into the query template.
1798    /// * `operators`- list of operators.
1799    ///
1800    /// # Results
1801    ///
1802    /// The final query
1803    pub fn prepare_query(
1804        query_template: String,
1805        words: Vec<String>,
1806        operators: &[String],
1807    ) -> FinalQuery {
1808        let mut query = query_template.clone();
1809        let domain = query_template.rsplit_once(' ').unwrap().1;
1810
1811        let mut count_like = 1;
1812        let mut count_op = 0;
1813
1814        words.iter().for_each(|word| {
1815            if !word.trim().is_empty() {
1816                query.push_str(format!(" LIKE ?{}", count_like).as_str());
1817                count_like += 1;
1818            } else if count_op < operators.len() {
1819                let op = &operators[count_op];
1820                count_op += 1;
1821                let query_str = match op.as_str() {
1822                  "not" => format!(" {} ", op.trim()),
1823                  _ => format!(" {op} {domain} ")
1824                };
1825                
1826                query.push_str(query_str.as_str());
1827            }
1828        });
1829        
1830        (query, words)
1831    }
1832
1833    /// Return the path of mitre database
1834    ///
1835    /// # Results
1836    ///
1837    /// Mitre database path
1838    pub fn db_mitre_path() -> PathBuf {
1839        let tyr_home = find_tyr_home_path();
1840        create_tyr_home(&tyr_home);
1841        tyr_home.join(MITRE_DB_NAME)
1842    }
1843
1844    /// Record extractor from the search result
1845    pub type SearchResultExtractor<T> = fn(&mut Rows) -> Vec<T>;
1846
1847    type FinalQuery = (String, Vec<String>);
1848
1849    /// Search the list of words into the database.
1850    ///
1851    /// `<T>` is the type of record as the result of the extraction.
1852    ///
1853    /// # Arguments
1854    /// * `query_string` - SQL string query
1855    /// * `words` - list of words for this query
1856    /// * `extractor`- function use to extract the searching result
1857    ///
1858    /// # Result
1859    ///
1860    /// A list of records according to T type
1861    ///
1862    pub fn search<T>(final_query: FinalQuery, extractor: SearchResultExtractor<T>) -> Vec<T> {
1863        let connection = db_mitre!().unwrap();
1864        let statement = connection.prepare(final_query.0.as_str());
1865        let mut binding = bind_criteria(&final_query.1, statement);
1866        let mut rows = binding.raw_query();
1867
1868        extractor(&mut rows)
1869    }
1870
1871    /// Parse the string values to a list of unsigned 32 bits.
1872    /// The string must be formatted like this: `::<num>::<num>::...::`
1873    ///
1874    /// # Argument
1875    /// * `values` - raw string values to parse to
1876    ///
1877    /// # Result
1878    /// The list of unsigned 32 values.
1879    pub fn parse_id_to_u32(values: String) -> Vec<u32> {
1880        let mut id_list: Vec<u32> = values
1881            .split(FIELD_SEPARATOR_STRING)
1882            .map(|v| {
1883                if !v.is_empty() {
1884                    let chars = v.chars();
1885                    let length = chars.clone().count();
1886                    let mut counter: usize = 0;
1887                    chars.for_each(|c| {
1888                        if c.is_ascii_digit() {
1889                            counter += 1;
1890                        }
1891                    });
1892
1893                    if counter == length {
1894                        v.parse::<u32>().unwrap()
1895                    } else {
1896                        0
1897                    }
1898                } else {
1899                    0
1900                }
1901            })
1902            .collect();
1903
1904        id_list.sort();
1905        id_list.dedup();
1906        id_list.retain(|x| *x != 0);
1907        id_list
1908    }
1909
1910    /// Finds the list of columns for the given table name
1911    ///
1912    /// # Argument
1913    ///
1914    /// * `table_name` - The table's name to explore.
1915    ///
1916    /// # Return
1917    ///
1918    /// The list of column's names for this table.
1919    pub fn find_columns(table_name: &str) -> Vec<String> {
1920        let db = db_mitre!().unwrap();
1921        let sql_select = format!("SELECT * FROM {table_name}");
1922        let stmt = db.prepare(sql_select.as_str()).unwrap();
1923
1924        stmt.column_names()
1925            .iter()
1926            .map(|x| x.to_string())
1927            .collect::<Vec<String>>()
1928    }
1929
1930    /// Search fonction type. The args ar the criteria used for the searching function implementation.
1931    type Searcher = fn(args: &[String]);
1932
1933    /// Search the values into the list of columns.
1934    ///
1935    /// # Arguments
1936    ///
1937    /// * `args`- Arguments of the searching.
1938    /// * `columns`- The list of columns in which all arguments will be applied.
1939    pub fn searching(args: &[String], columns: &[String], searcher: Searcher) {
1940        let args_id = args.iter().position(|x| x == "search" || x == "s").unwrap();
1941        let criteria = &args[args_id + 1..];
1942        if criteria[0].contains(&"all".to_string()) {
1943            let args_left = criteria[0]["all".len()..].to_string();
1944            columns.iter().for_each(|x| {
1945                if *x != "id" {
1946                    println!("{}", section!(format!("Searching for domain: {x}")));
1947                    let args = format!("{} {}", x, &args_left);
1948                    searcher(&[args]);
1949                }
1950            });
1951        } else {
1952            searcher(args);
1953        }
1954    }
1955
1956    /// Refresh the mitre database. the function takes a string that contains a list of csv files and the associated url.
1957    /// The method converts this string in an internal vector and create the query to download the CSV file from the
1958    /// associated url and inject these values into the local mitre database.
1959    ///
1960    /// # Arguments
1961    ///
1962    /// * `mitre_data` - MitreData description for the database
1963    /// * `injector` - the associated injector function that allows to store the downloaded data into the local database.
1964    ///
1965    pub fn refresh_mitre_database(mitre_data: MitreData, injector: InjectorFunction) {
1966        let csv_files = mitre_data.files;
1967        csv_files.iter().for_each(|&x| {
1968            refresh_data_mitre_into_database(mitre_data.url.to_string(), x.to_string(), injector);
1969        });
1970    }
1971
1972    /// Find the mitre record according to the mitre's identification.
1973    ///
1974    /// # Argument
1975    /// * `id` - Mitre identification. This id is a simple id value without the `T` of this id.
1976    ///
1977    /// # Return
1978    ///
1979    /// The Mitre record found or None.
1980    ///
1981    /// # Example
1982    ///
1983    /// ```rust
1984    /// use fenir::database::find_mitre_by_id;
1985    /// use fenir::mitre::MitreRecord;
1986    ///
1987    /// let result: Option<MitreRecord> = find_mitre_by_id("1531");
1988    ///
1989    /// if result.is_none() {
1990    ///     eprintln!("Mitre record not found.");
1991    /// } else {
1992    ///     println!("Mitre record found");
1993    /// }
1994    /// ```
1995    pub fn find_mitre_by_id(id: &str) -> Option<MitreRecord> {
1996        if id.is_empty() {
1997            return None;
1998        }
1999
2000        let conn = db_mitre!().unwrap();
2001        let query = format!("SELECT * FROM technique WHERE id = 'T{}'", id);
2002        let results = conn.prepare(query.as_str());
2003        if let Ok(mut results) = results {
2004            let value = results.query_row([], |row| {
2005                Ok(MitreRecord {
2006                    id: row.get(0).unwrap(),
2007                    name: row.get(1).unwrap(),
2008                    description: row.get(2).unwrap(),
2009                    url: row.get(3).unwrap(),
2010                    created: row.get(4).unwrap(),
2011                    modified: row.get(5).unwrap(),
2012                    domain: row.get(6).unwrap(),
2013                    version: row.get(7).unwrap(),
2014                    tactics: row.get(8).unwrap(),
2015                    platforms: row.get(9).unwrap(),
2016                    sub_techniques: row.get(10).unwrap_or("Unknown".to_string()),
2017                    contributors: row.get(11).unwrap_or("Unknown".to_string()),
2018                    support_remote: row.get(12).unwrap_or_default(),
2019                    impact_type: row.get(13).unwrap_or("Unknown".to_string()),
2020                    citations: row.get(14).unwrap(),
2021                    tactic_type: row.get(15).unwrap_or("Unknown".to_string()),
2022                    mtc_id: row.get(16).unwrap_or("Unknown".to_string()),
2023                })
2024            });
2025            value.ok()
2026        } else {
2027            None
2028        }
2029    }
2030
2031    /// Return the `Value::Null` if the string argument is empty. Otherwise return the corresponding `Value`/
2032    ///
2033    /// # Argument
2034    ///
2035    /// * `final_result`- The string value to parse to `Value` type.
2036    ///
2037    /// # Return
2038    ///
2039    /// The corresponding `Value`.
2040    ///
2041    /// # Example
2042    ///
2043    /// ```rust
2044    /// use rusqlite::types::Value;
2045    /// use fenir::database::text_or_null;
2046    ///
2047    /// let result = text_or_null("".to_string());
2048    ///
2049    /// assert_eq!(Value::Null, result);
2050    ///
2051    /// let other = text_or_null("A string".to_string());
2052    ///
2053    /// assert_eq!(Value::Text("A string".to_string()), other);
2054    /// ```
2055    pub fn text_or_null(final_result: String) -> rusqlite::types::Value {
2056        if final_result.is_empty() {
2057            rusqlite::types::Value::Null
2058        } else {
2059            rusqlite::types::Value::Text(final_result)
2060        }
2061    }
2062}
2063
2064/// Tools for download support
2065pub mod download {
2066    use std::error::Error;
2067    use std::fs::File;
2068    use std::io::{Read, Write};
2069    use std::path::Path;
2070    use ureq::Body;
2071    use ureq::http::Response;
2072    use zip::ZipArchive;
2073
2074    /// Downloads a MITRE archive from the given query URL. If the source file is a ZIP file type,
2075    /// extracts the source file from the ZIP archive.
2076    ///
2077    /// This function performs the following steps:
2078    /// 1. Fetches the source file content from the `query` URL.
2079    /// 2. Saves the downloaded the file specified by `query.source_file`.
2080    /// 3. Extracts the required ZIP archive specified if the file is a ZIP type.
2081    ///
2082    /// # Arguments
2083    ///
2084    /// * `query` - An instance of `QueryMitre` struct containing the required URL,
2085    ///   and source file.
2086    /// * `env_path`- The tyr home path.
2087    ///
2088    /// # Returns
2089    ///
2090    /// This function returns a `Result` type:
2091    /// * `Ok(String)` - If the process was successful. Contains the source file to used to.
2092    /// * `Err(Box<dyn std::error::Error>)` - If any error occurred during downloading, saving, or extracting.
2093    ///
2094    /// # Errors
2095    ///
2096    /// The function will return an error in cases such as:
2097    /// * Network issues while downloading the source file.
2098    /// * Problems during the unzipping file if it's a ZIP archive.
2099    ///
2100    /// # Example
2101    ///
2102    /// ```rust
2103    /// use fenir::download::{download_mitre_archive};
2104    /// use fenir::os::find_tyr_home_path;
2105    /// use std::path::Path;
2106    ///
2107    /// if let Err(e) = download_mitre_archive("https://example.com".to_string(), "data.csv.zip".to_string(), &find_tyr_home_path()) {
2108    ///     eprintln!("Error: {}", e);
2109    /// }
2110    ///
2111    /// let tyr_home = find_tyr_home_path();
2112    /// std::fs::remove_file(tyr_home.join(Path::new("data.csv.zip")));
2113    /// ```
2114    pub fn download_mitre_archive(
2115        url: String,
2116        file: String,
2117        env_path: &Path,
2118    ) -> Result<String, Box<dyn Error>> {
2119        let full_url = format!("{}/{}", url, file);
2120        let response = fetch_url(full_url.as_str());
2121
2122        save_downloaded_archive(response, file.as_str(), env_path)?;
2123
2124        if file.ends_with(".zip") {
2125            let target_file = file.trim_end_matches(".zip");
2126            extract_zip_file(file.as_str(), target_file, env_path)?;
2127            Ok(target_file.to_string())
2128        } else {
2129            Ok(file)
2130        }
2131    }
2132
2133    /// Fetches a CSV file from a given URL.
2134    ///
2135    /// This function sends a GET request to the specified URL and attempts to fetch the CSV file.
2136    /// It checks if the request was successful by verifying the HTTP status code.
2137    ///
2138    /// # Arguments
2139    ///
2140    /// * `url` - A string slice that holds the URL of the CSV file to be fetched.
2141    ///
2142    /// # Returns
2143    ///
2144    /// This function returns a `Result` containing a `Response` object on success, or a `Box<dyn Error>` on failure.
2145    ///
2146    /// # Errors
2147    ///
2148    /// This function will return an error if:
2149    ///
2150    /// * The request fails (e.g., due to network issues).
2151    /// * The server responds with a non-200 HTTP status code.
2152    ///
2153    /// # Examples
2154    ///
2155    /// ```rust
2156    /// use fenir::download::fetch_url;
2157    ///
2158    /// let url = "https://www.google.com";
2159    /// let response = fetch_url(url);
2160    ///
2161    /// // Process the response...
2162    /// if response.is_err() {
2163    ///     eprintln!("Url is not responding: {url}");
2164    /// } else {
2165    ///     println!("Fetched response for: {url}");
2166    /// }
2167    /// ```
2168    ///
2169    /// # Dependencies
2170    ///
2171    /// This function relies on the `ureq` crate for HTTP requests.
2172    pub fn fetch_url(url: &str) -> Result<Response<Body>, Box<dyn Error>> {
2173        // Send GET request to the URL
2174        let response = ureq::get(url).call()?;
2175
2176        // Ensure the request was successful
2177        if response.status() != 200 {
2178            return Err(format!("Failed to download archive: HTTP {}", response.status()).into());
2179        }
2180
2181        Ok(response)
2182    }
2183
2184    /// Saves a downloaded archive to a specified file.
2185    ///
2186    /// # Arguments
2187    ///
2188    /// * `response` - A `Result` wrapping the HTTP `Response` object if the request was successful,
2189    ///   or an error if the request failed.
2190    /// * `zip_file_name` - A string slice that holds the name of the file where the downloaded archive will be saved.
2191    /// * `env_path`- The tyr home path.
2192    ///
2193    /// # Returns
2194    ///
2195    /// This function returns a `Result`:
2196    ///
2197    /// * `Ok(())` if the file was successfully saved.
2198    /// * `Err(Box<dyn Error>)` if any error occurred during the process.
2199    /// * `env_path`- The tyr home path.
2200    ///
2201    /// # Errors
2202    ///
2203    /// This function will return an error if:
2204    ///
2205    /// * The file cannot be created.
2206    /// * Reading the response content fails.
2207    /// * Writing the content to the file fails.
2208    ///
2209    pub fn save_downloaded_archive(
2210        response: Result<Response<Body>, Box<dyn Error>>,
2211        zip_file_name: &str,
2212        env_path: &Path,
2213    ) -> Result<(), Box<dyn Error>> {
2214        let mut out = File::create(env_path.join(zip_file_name))?;
2215        let mut content = vec![];
2216        response?
2217            .into_body()
2218            .as_reader()
2219            .read_to_end(&mut content)?;
2220        out.write_all(&content)?;
2221        Ok(())
2222    }
2223
2224    /// Extracts a specified CSV file from a ZIP file and saves it to the filesystem.
2225    ///
2226    /// # Arguments
2227    ///
2228    /// * `from_zip_file_name` - A string slice that holds the name of the ZIP file to be read.
2229    /// * `to_csv_file_name` - A string slice that holds the name of the CSV file inside the ZIP to be extracted and saved.
2230    /// * `destination_path`- The path where to store the extracted zip.
2231    ///
2232    /// # Returns
2233    ///
2234    /// This function returns a `Result` type. On success, it returns an empty tuple `()`. On failure, it returns an error wrapped in a `Box<dyn Error>`.
2235    ///
2236    /// # Example
2237    ///
2238    /// ```
2239    /// use fenir::download::extract_zip_file;
2240    /// use fenir::os::find_tyr_home_path;
2241    ///
2242    /// let result = extract_zip_file("data.zip", "data.csv", &find_tyr_home_path());
2243    /// if let Err(e) = result {
2244    ///     println!("Error extracting file: {}", e);
2245    /// }
2246    /// ```
2247    ///
2248    /// # Errors
2249    ///
2250    /// The function will return an error in the following cases:
2251    /// * If the ZIP file cannot be opened.
2252    /// * If the specified CSV file does not exist in the ZIP.
2253    /// * If the CSV file cannot be read or written to the filesystem.
2254    pub fn extract_zip_file(
2255        from_zip_file_name: &str,
2256        to_csv_file_name: &str,
2257        destination_path: &Path,
2258    ) -> Result<(), Box<dyn Error>> {
2259        // Extract the CSV file from the ZIP
2260        let file = File::open(destination_path.join(from_zip_file_name))?;
2261        let mut archive = ZipArchive::new(file)?;
2262        let mut csv_file = archive.by_name(to_csv_file_name)?;
2263        let mut csv_content = String::new();
2264        csv_file.read_to_string(&mut csv_content)?;
2265        // Save the CSV content to a file
2266        let mut extracted_csv = File::create(destination_path.join(to_csv_file_name))?;
2267        extracted_csv.write_all(csv_content.as_bytes())?;
2268        Ok(())
2269    }
2270}
2271
2272/// Module with helper function for NVD REST network query.
2273pub mod query {
2274    use crate::cpe::NVD_CPE_LINK;
2275    use crate::facilities::get_current_date;
2276    use crate::query::QueryType::{ByCpeSearchWords, ByCveNew, ByCveUpdated};
2277    use crate::{NVD_CVE_LINK, URL_SPACE};
2278
2279    /// `QueryType` is an enumeration representing the types of queries that can be made
2280    /// for Common Vulnerabilities and Exposures (CVE). It derives `Eq`, `PartialEq`, and `Clone`
2281    /// traits for equality comparisons and cloning capabilities.
2282    #[derive(Eq, PartialEq, Clone)]
2283    pub enum QueryType {
2284        /// Query by a specific CVE identifier.
2285        ByCve,
2286
2287        /// Query by a CVE for a CPE name (string description)
2288        ByCveCpeName,
2289
2290        /// Query for a known exploited CVE list
2291        ByCveExploited,
2292
2293        /// Query to retrieve new CVEs.
2294        ByCveNew,
2295
2296        /// Query by search strings
2297        ByCveSearchString,
2298
2299        /// Query by search strings with strict mode
2300        ByCveSearchStrict,
2301
2302        /// Query to retrieve updated CVEs.
2303        ByCveUpdated,
2304
2305        /// Query by a Common Platform Enumeration (CPE) identifier.
2306        ByCpe,
2307
2308        /// Query to perform CPE matching.
2309        ByCpeMatch,
2310
2311        /// Query to perform CPE searching by considering the word arguments as a single criteria.
2312        ByCpeSearchStrict,
2313
2314        /// Query to perform CPE searching according to a list of words.
2315        ByCpeSearchWords,
2316
2317        /// Query about vulnerable CPE
2318        ByCpeVulnerable,
2319
2320        /// Query by a Common Weakness Enumeration (CWE) identifier.
2321        ByCwe,
2322
2323        /// Cve http link test
2324        LinkTest,
2325    }
2326
2327    macro_rules! query_dates_for_new_cve {
2328        ($d1:expr) => {
2329            query_dates_for_new_cve!($d1, "")
2330        };
2331
2332        ($d1:expr, $d2:expr) => {
2333            query_dates!("pub", $d1, $d2)
2334        };
2335    }
2336
2337    macro_rules! query_dates_for_updated_cve {
2338        ($d1: expr) => {
2339            query_dates_for_updated_cve!($d1, "")
2340        };
2341
2342        ($d1: expr, $d2:expr) => {
2343            query_dates!("lastMod", $d1, $d2)
2344        };
2345    }
2346
2347    macro_rules! query_dates {
2348        ($prefix:expr, $d1:expr, $d2:expr) => {{
2349            let date1 = match $d1.to_string().is_empty() {
2350                true => get_current_date(),
2351                false => $d1.to_string(),
2352            };
2353
2354            let date2 = match $d2.to_string().is_empty() {
2355                true => get_current_date(),
2356                false => $d2.to_string(),
2357            };
2358
2359            format!(
2360                "{}StartDate={}T00:00:00.000&{}EndDate={}T00:00:00.000",
2361                $prefix, date1, $prefix, date2
2362            )
2363        }};
2364    }
2365
2366    /// Generates a query string for CVE (Common Vulnerabilities and Exposures) based on the provided query date type
2367    /// and date values.
2368    ///
2369    /// # Arguments
2370    ///
2371    /// * `query_date` - An instance of `CveQueryType` enum specifying the type of date query (new CVEs or updated CVEs).
2372    /// * `value` - A string containing date values separated by a semicolon (`;`). The values represent start and end dates
2373    ///   respectively. If only one date is provided, it will be considered as both the start and end date.
2374    ///
2375    /// # Returns
2376    ///
2377    /// A `String` containing the constructed query with appropriate start and end dates.
2378    ///
2379    /// - For `CveNew`, it constructs `pubStartDate` and `pubEndDate` parameters.
2380    /// - For `CveUpdated`, it constructs `lastModStartDate` and `lastModEndDate` parameters.
2381    ///
2382    /// If a date is empty, the current date is used as a fallback.
2383    ///
2384    /// # Examples
2385    ///
2386    /// ```
2387    /// use fenir::query::{QueryType, create_query_dates};
2388    /// use fenir::facilities::get_current_date;
2389    ///
2390    /// let result = create_query_dates(QueryType::ByCveNew, "2023-01-01;2023-12-31");
2391    /// assert_eq!(result, "pubStartDate=2023-01-01T00:00:00.000&pubEndDate=2023-12-31T00:00:00.000");
2392    ///
2393    /// let result = create_query_dates(QueryType::ByCveUpdated, "2023-01-01;");
2394    /// let current_date = get_current_date();
2395    ///
2396    /// assert_eq!(result, format!("lastModStartDate=2023-01-01T00:00:00.000&lastModEndDate={}T00:00:00.000", current_date));
2397    /// ```
2398    pub fn create_query_dates(query_date: QueryType, value: &str) -> String {
2399        let dates: Vec<&str> = value.split(";").collect();
2400        match dates.len() {
2401            2 => match query_date {
2402                ByCveNew => {
2403                    query_dates_for_new_cve!(dates[0], dates[1])
2404                }
2405
2406                ByCveUpdated => {
2407                    query_dates_for_updated_cve!(dates[0], dates[1])
2408                }
2409                _ => String::new(),
2410            },
2411
2412            1 => match query_date {
2413                ByCveNew => {
2414                    query_dates_for_new_cve!(dates[0])
2415                }
2416
2417                ByCveUpdated => {
2418                    query_dates_for_updated_cve!(dates[0])
2419                }
2420
2421                _ => String::new(),
2422            },
2423            _ => String::new(),
2424        }
2425    }
2426
2427    /// Builds a query URL based on the type of query and a URL argument. Thanks to the `query_type`
2428    /// argument, the details for full URL constructing is taking in charge automatically.
2429    ///
2430    /// # Arguments
2431    ///
2432    /// * `query_type` - A variant of the `CveQueryType` enum specifying the type of CVE query.
2433    /// * `url_argument` - A string slice that holds the argument to be passed in the URL query.
2434    ///
2435    /// # Returns
2436    ///
2437    /// If successful, returns `Ok` containing the constructed query URL as a `String`.
2438    /// Otherwise, returns an `Err` with a static string slice indicating the error.
2439    ///
2440    /// # Examples
2441    /// ```
2442    /// use fenir::NVD_CVE_LINK;
2443    /// use fenir::query::{QueryType, build_query};
2444    ///
2445    /// let query_url = build_query(QueryType::ByCve, "CVE-2021-3456").unwrap();
2446    /// assert_eq!(query_url, format!("{}?cveId=CVE-2021-3456", NVD_CVE_LINK));
2447    /// ```
2448    pub fn build_query(query_type: QueryType, url_argument: &str) -> Result<String, &'static str> {
2449        match query_type {
2450            QueryType::ByCve => Ok(format!(
2451                "{NVD_CVE_LINK}?cveId={}",
2452                url_argument.to_uppercase()
2453            )),
2454
2455            QueryType::ByCveCpeName => Ok(format!(
2456                "{NVD_CVE_LINK}?virtualMatchString={}",
2457                url_argument
2458            )),
2459
2460            QueryType::ByCveExploited => Ok(format!("{NVD_CVE_LINK}?hasKev")),
2461
2462            ByCveNew | ByCveUpdated => Ok(format!(
2463                "{NVD_CVE_LINK}?{}",
2464                create_query_dates(query_type, url_argument)
2465            )),
2466
2467            QueryType::ByCwe => Ok(format!(
2468                "{NVD_CVE_LINK}?cweId={}",
2469                url_argument.to_uppercase()
2470            )),
2471
2472            QueryType::ByCpeVulnerable => {
2473                let query_str = build_query(QueryType::ByCveCpeName, url_argument)?;
2474                Ok(format!("{}&isVulnerable", query_str))
2475            }
2476
2477            QueryType::ByCveSearchString => {
2478                Ok(format!("{NVD_CVE_LINK}?keywordSearch={}", url_argument))
2479            }
2480
2481            QueryType::ByCveSearchStrict => {
2482                let query_str = build_query(QueryType::ByCveSearchString, url_argument)?;
2483                Ok(format!("{}&keywordExactMatch", query_str))
2484            }
2485
2486            QueryType::ByCpe => Ok(format!("{NVD_CPE_LINK}?cpeName={}", url_argument)),
2487
2488            QueryType::ByCpeMatch => Ok(format!(
2489                "{NVD_CPE_LINK}?cpeMatchString={}",
2490                url_argument.to_lowercase()
2491            )),
2492
2493            ByCpeSearchWords => Ok(format!(
2494                "{NVD_CPE_LINK}?keywordSearch={}",
2495                url_argument.replace(' ', URL_SPACE)
2496            )),
2497
2498            QueryType::ByCpeSearchStrict => {
2499                let first_part = build_query(ByCpeSearchWords, url_argument)?;
2500                Ok(format!("{first_part}&keywordExactMatch"))
2501            }
2502
2503            QueryType::LinkTest => Ok(url_argument.to_string()),
2504        }
2505    }
2506}
2507
2508/// Network module with functions used for HTTPS request and JSON exploration
2509pub mod network {
2510    use crate::NVD_KEY_NAME;
2511    use crate::database::MitreData;
2512    use crate::query::QueryType::LinkTest;
2513    use crate::query::build_query;
2514    use serde_json::Value;
2515    use std::env;
2516    use std::ffi::OsString;
2517    use termint::enums::Color;
2518    use termint::widgets::ToSpan;
2519    use ureq::Error;
2520
2521    /// Parses the given response from a `ureq` request.
2522    ///
2523    /// This function takes a `Result` containing either a `serde_json::Value` or a `ureq::Error`.
2524    /// If the result is an error, it returns an empty `Value`.
2525    /// If the result is a valid response, it returns a JSON `Value`.
2526    ///
2527    /// # Arguments
2528    ///
2529    /// * `response` - A `Result` containing either a `serde_json::Value` or `ureq::Error`.
2530    ///
2531    /// # Returns
2532    ///
2533    /// * A `serde_json::Value` parsed from the response, or an empty `Value::Null` if an error occurred.
2534    ///
2535    pub fn parse_response(response: Result<Value, Box<Error>>) -> Value {
2536        response.unwrap_or(Value::Null)
2537    }
2538
2539    /// Fetches a response from the given query URL. If the variable environment contains the
2540    /// `NVD_API_KEY`, this value will be used as NVD api key.
2541    ///
2542    /// This function uses the `ureq` crate to send an HTTP GET request to the specified URL.
2543    /// It returns the response if the request is successful, otherwise, it returns an error.
2544    /// The HTTP request is force to HTTPS only for secure reason.
2545    ///
2546    /// # Arguments
2547    ///
2548    /// * `query` - A string slice that holds the URL to which the GET request should be sent.
2549    ///
2550    /// # Returns
2551    ///
2552    /// This function returns a `Result` which is:
2553    /// - `Ok(serde_json::Value)` if the request is successful.
2554    /// - `Err(ureq::Error)` if there is an error during the request.
2555    ///
2556    /// # Examples
2557    ///
2558    /// ```
2559    /// use fenir::network::fetch_json_response;
2560    /// let url = "https://api.example.com/data";
2561    ///
2562    /// match fetch_json_response(url) {
2563    ///     Ok(response) => println!("Success: {:?}", response),
2564    ///     Err(error) => eprintln!("Error: {:?}", error),
2565    /// }
2566    /// ```
2567    ///
2568    /// # Errors
2569    ///
2570    /// This function will return an `Err` variant if the GET request fails for any reason,
2571    /// such as network issues or invalid URLs.
2572    ///
2573    pub fn fetch_json_response(query: &str) -> Result<Value, Box<Error>> {
2574        let apikey = check_nvd_api_key();
2575        if let Some(item) = apikey {
2576            let result: Value = ureq::get(query)
2577                .header("apiKey", item.to_str().unwrap())
2578                .call()?
2579                .body_mut()
2580                .with_config()
2581                .read_json()?;
2582            Ok(result)
2583        } else {
2584            let result = ureq::get(query)
2585                .call()?
2586                .body_mut()
2587                .with_config()
2588                .read_json()?;
2589
2590            Ok(result)
2591        }
2592    }
2593
2594    /// Check if the nvd api key is exiting
2595    ///
2596    /// # Returns
2597    ///
2598    /// - The result of checking. The presence of the NVD_API_KEY is optional
2599    pub fn check_nvd_api_key() -> Option<OsString> {
2600        env::var_os(NVD_KEY_NAME)
2601    }
2602
2603    /// Warning missing API key message
2604    pub fn check_nvd_api_key_warning() {
2605        eprintln!("{}", "WARNING: NVD api key not found. For a better performance, in your environment set NVD_API_KEY=<your_api_key_value>.".to_string().fg(Color::DarkYellow));
2606    }
2607
2608    /// Checks the validity of a connection by sending a GET request to the provided URL.
2609    ///
2610    /// # Arguments
2611    ///
2612    /// * `query` - A string slice that represents the URL to which the GET request is made.
2613    ///
2614    /// # Returns
2615    ///
2616    /// * `true` if the response for the GET request is successful (status code 200).
2617    /// * `false` if there is any error in making the request or the response indicates a failure.
2618    ///
2619    /// # Examples
2620    ///
2621    /// ```rust
2622    /// use fenir::NVD_CVE_LINK;
2623    /// use fenir::network::validate_connection;
2624    ///
2625    /// let is_valid = validate_connection(NVD_CVE_LINK);
2626    /// assert!(is_valid);
2627    /// ```
2628    pub fn validate_connection(query: &str) -> bool {
2629        // Simple URL validation
2630        if !query.starts_with("http://") && !query.starts_with("https://") {
2631            eprintln!("Invalid URL: {}", query);
2632            return false;
2633        }
2634
2635        let agent = ureq::Agent::new_with_defaults();
2636
2637        let query_test = build_query(LinkTest, query);
2638        let query_info_for_error = query_test.clone().unwrap();
2639
2640        match agent.get(query_test.unwrap().as_str()).call() {
2641            Ok(res) => {
2642                if res.status() == 200 {
2643                    true
2644                } else {
2645                    eprintln!(
2646                        "Request failed with status: {} ({})",
2647                        res.status(),
2648                        query_info_for_error
2649                    );
2650                    false
2651                }
2652            }
2653            Err(Error::StatusCode(res)) => {
2654                eprintln!(
2655                    "Request failed with response: {:?} ({})",
2656                    res, query_info_for_error
2657                );
2658                false
2659            }
2660            Err(Error::Http(transport)) => {
2661                eprintln!(
2662                    "Request http error: {} ({})",
2663                    transport, query_info_for_error
2664                );
2665                false
2666            }
2667            _ => false,
2668        }
2669    }
2670
2671    /// Check if the Mitre data is valid for exploitation. This feature check if the URL and associated
2672    /// sources are available.
2673    ///
2674    /// # Argument
2675    /// * `data`- MitreData used for Mitre data checking
2676    ///
2677    /// If the connection is valid, it prints "valid".
2678    /// If the connection is invalid, it prints "invalid".
2679    ///
2680    /// # Example
2681    ///
2682    /// ```rust
2683    ///  use fenir::network::check_mitre_data;
2684    ///  use fenir::database::{MitreData, MitreDefinition};
2685    ///
2686    ///  pub struct Link;
2687    ///
2688    ///  impl MitreDefinition for Link {
2689    ///     fn define() -> MitreData {
2690    ///         MitreData {
2691    ///             name: "Cpe",
2692    ///             url: "https://services.nvd.nist.gov/rest/json/cpes/2.0",
2693    ///             files: vec![],
2694    ///         }
2695    ///     }
2696    /// }
2697    ///
2698    /// check_mitre_data(Link::define());
2699    /// ```
2700    pub fn check_mitre_data(data: MitreData) {
2701        println!("Check mitre domain: {}", data.name);
2702        if data.files.is_empty() {
2703            print!("\tTest url: {}... ", data.url);
2704            if !validate_connection(data.url) {
2705                eprintln!("Failed");
2706            } else {
2707                println!("Valid");
2708            }
2709        }
2710
2711        data.files.iter().for_each(|&source| {
2712            let url_data = format!("{}/{}", data.url, source);
2713            print!("\tTest: {}... ", url_data);
2714            if !validate_connection(url_data.as_str()) {
2715                println!("Invalid");
2716            } else {
2717                println!("valid");
2718            }
2719        });
2720    }
2721}
2722
2723/// Module for CWE structure definition and its associated functions
2724pub mod cwe {
2725    use crate::database::{MitreData, MitreDefinition, find_cwe_by_id, parse_id_to_u32};
2726    use crate::facilities::{Cleaning, Uppercase};
2727    use crate::{header, section, section_level};
2728    use serde_json::Value;
2729    use std::fmt::{Display, Formatter};
2730    use termint::enums::Color;
2731    use termint::widgets::ToSpan;
2732
2733    /// Represents a CweRecord that's containing  all content of an existent CWE.
2734    #[derive(Clone, Eq, PartialEq, Debug)]
2735    pub struct CweRecord {
2736        /// the CWE id (without CWE string prefix)
2737        pub id: u32,
2738
2739        /// the name of the CWE
2740        pub name: String,
2741
2742        /// The primary description of the CWE
2743        pub description: String,
2744
2745        /// The full description of the CWE
2746        pub extended: String,
2747
2748        /// The associated capec
2749        pub attacks: String,
2750
2751        /// The consequences of the CWE
2752        pub consequences: String,
2753
2754        /// The detections feature for this CWE
2755        pub detections: String,
2756
2757        /// The mitigations for this CWE
2758        pub mitigations: String,
2759    }
2760
2761    impl Display for CweRecord {
2762        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2763            write!(
2764                f,
2765                "{}\n{} {}",
2766                header!(format!("CWE-{}", self.id)),
2767                format!("{:>4} {}", "-", "Name:").fg(Color::DarkBlue),
2768                self.name
2769            )
2770        }
2771    }
2772
2773    impl MitreDefinition for CweRecord {
2774        fn define() -> MitreData {
2775            MitreData {
2776                name: "Cwe",
2777                url: "https://cwe.mitre.org/data/csv",
2778                files: vec!["699.csv.zip", "1000.csv.zip", "1194.csv.zip"],
2779            }
2780        }
2781    }
2782
2783    /// Shows the weaknesses whose the ids are corresponding to the `raw_weaknesses` string.
2784    ///
2785    /// # Argument
2786    ///
2787    /// * `section` - Section to show as section level/
2788    /// * `raw_weaknesses`- The string of weaknesses id. This string is the format: `::<id1>::<id2>::...::<idN>::`.
2789    ///
2790    pub fn show_weaknesses(section: &str, raw_weaknesses: String) {
2791        let cwe_list = parse_id_to_u32(raw_weaknesses);
2792
2793        section_level!(2, section);
2794        for cwe in cwe_list {
2795            let result = find_cwe_by_id(cwe);
2796            if let Some(record) = result {
2797                println!("{:>6} CWE-{} - {}", "-", cwe, record.name);
2798            } else {
2799                println!("{:>6} CWE-{} - ???", "-", cwe);
2800            }
2801        }
2802    }
2803
2804    /// Flat the cwe values to a string value
2805    ///
2806    /// # Arguments
2807    ///
2808    /// * `values` - List of cwe values
2809    ///
2810    /// # Returns
2811    ///
2812    /// A string with all cwe id separate by a `,`.
2813    pub fn join_cwe_list(values: &[Value]) -> String {
2814        let mut result = Vec::new();
2815        values
2816            .iter()
2817            .for_each(|v| result.push(String::cleaning(v["value"].to_string())));
2818
2819        result.join(", ")
2820    }
2821}
2822
2823/// Module for CPE structure definition and its associated functions
2824pub mod cpe {
2825    use crate::database::{MitreData, MitreDefinition};
2826    use crate::facilities::Uppercase;
2827    use crate::package::errors::write_error_message_and_exit;
2828    use crate::{NOT_ENOUGH_ELEMENTS_ERROR, stamp};
2829    use std::fmt::{Display, Formatter};
2830
2831    /// NVD link Rest API for CPE request
2832    pub const NVD_CPE_LINK: &str = "https://services.nvd.nist.gov/rest/json/cpes/2.0";
2833
2834    /// NVD link Rest API for CPE/CVE matching
2835    pub const NVD_CPE_MATCH_LINK: &str = "https://services.nvd.nist.gov/rest/json/cpematch/2.0";
2836
2837    /// Represents a Common Platform Enumeration (CPE) identifier.
2838    ///
2839    /// CPE is a standardized method used to describe and identify classes of applications,
2840    /// operating systems, and hardware devices present within an enterprise's computing assets.
2841    ///
2842    /// This struct includes various fields that together form a complete CPE identifier.
2843    ///
2844    /// # Note
2845    ///
2846    /// All fields that are not explicitly specified, are marked with the `*` character.
2847    ///
2848    #[derive(Eq, PartialEq, Clone, Debug, Default)]
2849    pub struct Cpe {
2850        /// Type CPE type
2851        pub cpe_type: CpeType,
2852
2853        /// The product's vendor.
2854        pub vendor: String,
2855
2856        /// The product's name.
2857        pub product: String,
2858
2859        /// The product's version.
2860        pub version: String,
2861
2862        /// The update indication related to this version.
2863        pub update: String,
2864
2865        /// The product's edition.
2866        pub edition: String,
2867
2868        /// The product's language.
2869        pub language: String,
2870
2871        /// The software edition.
2872        pub sw_edition: String,
2873
2874        /// The target software.
2875        pub target_sw: String,
2876
2877        /// The target hardware.
2878        pub target_hw: String,
2879
2880        /// The other field for free indication.
2881        pub other: String,
2882    }
2883
2884    impl Cpe {
2885        /// Creates a new instance of the `cpe` struct with the provided `CpeType`.
2886        ///
2887        /// Fields such as `vendor`, `product`, `version`, `update`, and `edition` are initialized with default values (`"*"`).
2888        ///
2889        /// # Arguments
2890        ///
2891        /// * `cpe_type` - The type of the Common Platform Enumeration (CPE). This can be either `CpeType::Application` or `CpeType::OperatingSystem`.
2892        ///
2893        /// # Returns
2894        ///
2895        /// A new `cpe` instance with the specified CPE type and default field values.
2896        pub fn new(cpe_type: CpeType) -> Self {
2897            Cpe {
2898                cpe_type,
2899                vendor: "*".to_string(),
2900                product: "*".to_string(),
2901                version: "*".to_string(),
2902                update: "*".to_string(),
2903                edition: "*".to_string(),
2904                language: "*".to_string(),
2905                sw_edition: "*".to_string(),
2906                target_hw: "*".to_string(),
2907                target_sw: "*".to_string(),
2908                other: "*".to_string(),
2909            }
2910        }
2911
2912        /// Returns a string representation of the instance. This is a convenience method for
2913        /// formats the instance into a string using the `Display` trait implementation
2914        /// of the type of `self`.
2915        ///
2916        /// # Returns
2917        ///
2918        /// A `String` containing the formatted representation of the instance.
2919        ///
2920        /// # Notes:
2921        ///
2922        /// - the output format is : cpe:2.3:&lt;part&gt;:&lt;vendor&gt;:&lt;product&gt;:&lt;version&gt;:&lt;update&gt;:&lt;edition&gt;
2923        /// - the output format is limited with elements only used with these tools:
2924        ///     - &lt;part&gt;: support only o (for operating system, a for application),
2925        ///     - the &lt;sw_edition&gt;:&lt;target_sw&gt;:&lt;target_hw&gt;:&lt;other&gt; of the CPE norm is not supported.
2926        ///
2927        pub fn name(&self) -> String {
2928            format!("{}", self)
2929        }
2930
2931        /// Sets the vendor field for this `cpe` instance.
2932        ///
2933        /// This method updates the vendor field with the provided name, converting it to lowercase.
2934        ///
2935        /// # Arguments
2936        ///
2937        /// * `name` - A string slice that holds the name of the vendor.
2938        ///
2939        /// # Example
2940        ///
2941        /// ```
2942        /// use fenir::cpe::{Cpe, CpeType};
2943        /// let mut cpe = Cpe::new(CpeType::Application);
2944        ///
2945        /// cpe.vendor("NewVendor");
2946        /// assert_eq!(cpe.vendor, "newvendor");
2947        /// ```
2948        pub fn vendor(&mut self, name: &str) {
2949            self.vendor = String::from(name).to_lowercase();
2950        }
2951
2952        /// Sets the product name for the CPE identifier.
2953        ///
2954        /// This method takes a string slice representing the product name,
2955        /// converts it to lowercase, and assigns it to the `product` field of the `cpe` struct.
2956        ///
2957        /// # Parameters
2958        ///
2959        /// * `name` - A string slice that holds the name of the product. This name will be converted to lowercase.
2960        ///
2961        /// # Example
2962        ///
2963        /// ```
2964        /// use fenir::cpe::{Cpe, CpeType};
2965        /// let mut cpe = Cpe::new(CpeType::Application);
2966        ///
2967        /// cpe.product("CoolProduct");
2968        /// assert_eq!(cpe.product, "coolproduct");
2969        /// ```
2970        pub fn product(&mut self, name: &str) {
2971            self.product = String::from(name).to_lowercase();
2972        }
2973
2974        /// Sets the version for the `cpe` instance.
2975        ///
2976        /// This method updates the `version` field of the `cpe` struct, converting the provided
2977        /// version string to lowercase to ensure consistency.
2978        ///
2979        /// # Parameters
2980        ///
2981        /// * `value` - A reference to a string slice representing the new version to be set.
2982        ///
2983        /// # Example
2984        ///
2985        /// ```
2986        /// use fenir::cpe::{Cpe, CpeType};
2987        /// let mut cpe = Cpe::new(CpeType::Application);
2988        ///
2989        /// cpe.version("2.0.0");
2990        /// assert_eq!(cpe.version, "2.0.0");
2991        /// ```
2992        ///
2993        /// # Note
2994        ///
2995        /// The input version string will be converted to lowercase.
2996        ///
2997        /// # Errors
2998        ///
2999        /// This method does not return an error but mutates the `cpe` instance in place.
3000        pub fn version(&mut self, value: &str) {
3001            self.version = String::from(value).to_lowercase();
3002        }
3003
3004        /// Updates the `update` field of the `cpe` struct with the given value, converting it to lowercase.
3005        ///
3006        /// # Arguments
3007        ///
3008        /// * `value` - A string slice representing the new update/version details of the product.
3009        ///
3010        /// # Examples
3011        ///
3012        /// ```
3013        /// use fenir::cpe::{Cpe, CpeType};
3014        /// let mut cpe = Cpe::new(CpeType::Application);
3015        ///
3016        /// cpe.update("Update2");
3017        /// assert_eq!(cpe.update, "update2");
3018        /// ```
3019        pub fn update(&mut self, value: &str) {
3020            self.update = String::from(value).to_lowercase();
3021        }
3022
3023        /// Sets the edition field of the `cpe` struct.
3024        ///
3025        /// This method takes a string slice, converts it to a `String`, transforms it to lowercase,
3026        /// and assigns it to the inner field.
3027        ///
3028        /// # Arguments
3029        ///
3030        /// * `value` - A string slice that holds the new value for this field.
3031        ///
3032        /// # Example
3033        ///
3034        /// ```rust
3035        /// use fenir::cpe::{Cpe, CpeType};
3036        /// let mut cpe = Cpe::new(CpeType::Application);
3037        ///
3038        /// cpe.edition("Premium");
3039        /// assert_eq!(cpe.edition, "premium");
3040        /// ```
3041        pub fn edition(&mut self, value: &str) {
3042            self.edition = String::from(value).to_lowercase();
3043        }
3044
3045        /// Sets the language of the `cpe` struct.
3046        ///
3047        /// This method takes a string slice, converts it to a `String`, transforms it to lowercase,
3048        /// and assigns it to the inner field.
3049        ///
3050        /// # Arguments
3051        ///
3052        /// * `value` - A string slice that holds the new value for this field.
3053        ///
3054        /// # Example
3055        ///
3056        /// ```rust
3057        /// use fenir::cpe::{Cpe, CpeType};
3058        /// let mut cpe = Cpe::new(CpeType::Application);
3059        ///
3060        /// cpe.language("EN");
3061        /// assert_eq!(cpe.language, "en");
3062        /// ```
3063        pub fn language(&mut self, value: &str) {
3064            self.language = String::from(value).to_lowercase();
3065        }
3066
3067        /// Sets the software edition of the `cpe` struct.
3068        ///
3069        /// This method takes a string slice, converts it to a `String`, transforms it to lowercase,
3070        /// and assigns it to the inner field.
3071        ///
3072        /// # Arguments
3073        ///
3074        /// * `value` - A string slice that holds the new value for this field.
3075        ///
3076        /// # Example
3077        ///
3078        /// ```rust
3079        /// use fenir::cpe::{Cpe, CpeType};
3080        /// let mut cpe = Cpe::new(CpeType::Application);
3081        ///
3082        /// cpe.sw_edition("SoftEdition");
3083        /// assert_eq!(cpe.sw_edition, "softedition");
3084        /// ```
3085        pub fn sw_edition(&mut self, value: &str) {
3086            self.sw_edition = String::from(value).to_lowercase();
3087        }
3088
3089        /// Sets the target software of the `cpe` struct.
3090        ///
3091        /// This method takes a string slice, converts it to a `String`, transforms it to lowercase,
3092        /// and assigns it to the inner field.
3093        ///
3094        /// # Arguments
3095        ///
3096        /// * `value` - A string slice that holds the new value for this field.
3097        ///
3098        /// # Example
3099        ///
3100        /// ```rust
3101        /// use fenir::cpe::{Cpe, CpeType};
3102        /// let mut cpe = Cpe::new(CpeType::Application);
3103        ///
3104        /// cpe.target_sw("TargetSoft");
3105        /// assert_eq!(cpe.target_sw, "targetsoft");
3106        /// ```
3107        pub fn target_sw(&mut self, value: &str) {
3108            self.target_sw = String::from(value).to_lowercase();
3109        }
3110
3111        /// Sets the target hardware of the `cpe` struct.
3112        ///
3113        /// This method takes a string slice, converts it to a `String`, transforms it to lowercase,
3114        /// and assigns it to the inner field.
3115        ///
3116        /// # Arguments
3117        ///
3118        /// * `value` - A string slice that holds the new value for this field.
3119        ///
3120        /// # Example
3121        ///
3122        /// ```rust
3123        /// use fenir::cpe::{Cpe, CpeType};
3124        /// let mut cpe = Cpe::new(CpeType::Application);
3125        ///
3126        /// cpe.target_hw("Raspberry");
3127        /// assert_eq!(cpe.target_hw, "raspberry");
3128        /// ```
3129        pub fn target_hw(&mut self, value: &str) {
3130            self.target_hw = String::from(value).to_lowercase();
3131        }
3132
3133        /// Sets the other value of the `cpe` struct.
3134        ///
3135        /// This method takes a string slice, converts it to a `String`, transforms it to lowercase,
3136        /// and assigns it to the inner field.
3137        ///
3138        /// # Arguments
3139        ///
3140        /// * `value` - A string slice that holds the new value for this field.
3141        ///
3142        /// # Example
3143        ///
3144        /// ```rust
3145        /// use fenir::cpe::{Cpe, CpeType};
3146        /// let mut cpe = Cpe::new(CpeType::Application);
3147        ///
3148        /// cpe.other("Other");
3149        /// assert_eq!(cpe.other, "other");
3150        /// ```
3151        pub fn other(&mut self, value: &str) {
3152            self.other = String::from(value).to_lowercase();
3153        }
3154
3155        /// Parses a CPE (Common Platform Enumeration) from a given string. Support only cpe version 2.3.
3156        ///
3157        /// # Arguments
3158        /// * `cpe_string` - A string in the CPE format to be parsed
3159        ///
3160        /// # Returns
3161        /// * `cpe` - A struct containing the parsed components of the CPE string
3162        ///
3163        /// # Notes:
3164        ///
3165        /// - the output format is : cpe:2.3:&lt;part&gt;:&lt;vendor&gt;:&lt;product&gt;:&lt;version&gt;:&lt;update&gt;:&lt;edition&gt;
3166        /// - the output format is limited with elements only used with these tools:
3167        ///     - &lt;part&gt;: support only o (for operating system, a for application),
3168        ///     - the &lt;sw_edition&gt;:&lt;target_sw&gt;:&lt;target_hw&gt;:&lt;other&gt; of the CPE norm is not supported.
3169        ///
3170        pub fn from(cpe_string: &str) -> Cpe {
3171            if !cpe_string.contains(CPE_HEADER) {
3172                write_error_message_and_exit("CPE string header not found: cpe:2.3", None);
3173            }
3174
3175            let elements = extract_elements(cpe_string);
3176            let mut cpe = match elements[2].as_str() {
3177                "o" => Cpe::new(CpeType::OperatingSystem),
3178                "a" => Cpe::new(CpeType::Application),
3179                "h" => Cpe::new(CpeType::Hardware),
3180                _ => Cpe::new(CpeType::All),
3181            };
3182
3183            cpe.vendor(&elements[3]);
3184            cpe.product(&elements[4]);
3185            cpe.version(&elements[5]);
3186            cpe.update(&elements[6]);
3187            cpe.edition(&elements[7]);
3188            cpe.language(&elements[8]);
3189            cpe.sw_edition(&elements[9]);
3190            cpe.target_sw(&elements[10]);
3191            cpe.target_hw(&elements[11]);
3192            cpe.other(&elements[12]);
3193            cpe
3194        }
3195
3196        /// Describes the CPE (Common Platform Enumeration) attributes.
3197        ///
3198        /// This method prints out a human-readable description of the CPE,
3199        /// detailing its type, vendor, product, version, edition, and update.
3200        ///
3201        /// The type is either "soft" for applications or "os" for operating systems,
3202        /// inferred from the `cpe_type` field.
3203        ///
3204        /// # Examples
3205        ///
3206        /// ```rust
3207        /// use fenir::cpe::{Cpe, CpeType};
3208        ///
3209        /// let cpe = Cpe::from("cpe:2.3:a:lostvip:ruoyi-go:*:*:*:*:*:*:*:*");
3210        ///
3211        /// cpe.explains();
3212        /// ```
3213        /// Will produce the output:
3214        /// ```shell
3215        ///   - Id: cpe:2.3:a:lostvip:ruoyi-go:*:*:*:*:*:*:*:*
3216        ///   - Type: soft
3217        ///   - Vendor: lostvip
3218        ///   - Product: ruoyi-go
3219        ///   - Version: *
3220        ///   - Edition: *
3221        ///   - Update: *
3222        ///   - Language: *
3223        ///   - Sw_edition: *
3224        ///   - Target sw : *
3225        ///   - Target hw: *
3226        ///   - Other: *
3227        /// ```
3228        pub fn explains(&self) {
3229            stamp!("id", &self.name());
3230            stamp!("type", self.cpe_type());
3231            stamp!("vendor", &self.vendor);
3232            stamp!("product", &self.product);
3233            stamp!("version", &self.version);
3234            stamp!("edition", &self.edition);
3235            stamp!("update", &self.update);
3236            stamp!("language", &self.language);
3237            stamp!("sw edition", &self.sw_edition);
3238            stamp!("target sw", &self.target_sw);
3239            stamp!("target hw", &self.target_hw);
3240            stamp!("other", &self.other);
3241        }
3242
3243        /// Returns the CPE type description
3244        ///
3245        /// # Returns
3246        ///
3247        ///  * "soft" - if type is application (a)
3248        ///  * "hardware" -  if type is hardware (h)
3249        ///  * "os" - if type is operating system (o)
3250        ///  * "all" - if type is all (*)
3251        ///
3252        /// # Example
3253        ///
3254        /// ```rust
3255        ///  use fenir::cpe::{Cpe, CpeType};
3256        ///
3257        /// let cpe = Cpe::new(CpeType::OperatingSystem);
3258        /// assert_eq!("os", cpe.cpe_type());
3259        /// ```
3260        pub fn cpe_type(&self) -> &str {
3261            match self.cpe_type {
3262                CpeType::Application => "soft",
3263                CpeType::OperatingSystem => "os",
3264                CpeType::Hardware => "hard",
3265                CpeType::All => "all",
3266            }
3267        }
3268    }
3269
3270    /// CpeLink used for CPE request.
3271    pub struct CpeLink;
3272    impl MitreDefinition for CpeLink {
3273        fn define() -> MitreData {
3274            MitreData {
3275                name: "Cpe",
3276                url: "https://services.nvd.nist.gov/rest/json/cpes/2.0",
3277                files: vec![],
3278            }
3279        }
3280    }
3281
3282    /// CpeMatchLink used for cpe link complement
3283    pub struct CpeMatchLink;
3284    impl MitreDefinition for CpeMatchLink {
3285        fn define() -> MitreData {
3286            MitreData {
3287                name: "Cpe match",
3288                url: "https://services.nvd.nist.gov/rest/json/cpematch/2.0",
3289                files: vec![],
3290            }
3291        }
3292    }
3293
3294    /// Operating system type
3295    pub const CPE_OS_LABEL: &str = "os";
3296    // Hardware type
3297    pub const CPE_HARDWARE_LABEL: &str = "h";
3298    /// Application/software type
3299    pub const CPE_SOFT_LABEL: &str = "soft";
3300    /// All type
3301    pub const CPE_ALL_LABEL: &str = "*";
3302
3303    /// Creates a `cpe` instance based on the provided application type.
3304    ///
3305    /// This function checks if the `apps_type` matches the constant value `OS_TYPE`.
3306    /// If it does, it creates a `cpe` instance of type `OperatingSystem`, otherwise it creates a
3307    /// `cpe` instance of type `Application`.
3308    ///
3309    /// # Arguments
3310    ///
3311    /// * `apps_type` - A string slice representing the type of the application. It can be either
3312    ///   `"os"` for an operating system or any other value for an application.
3313    ///
3314    /// # Returns
3315    ///
3316    /// A `cpe` instance with the appropriate `CpeType`.
3317    ///
3318    /// # Examples
3319    ///
3320    /// ```
3321    /// use fenir::cpe::{create_cpe_from_type, Cpe, CpeType};
3322    ///
3323    /// let cpe_os = create_cpe_from_type("os");
3324    /// assert_eq!(cpe_os.cpe_type, CpeType::OperatingSystem);
3325    ///
3326    /// let cpe_app = create_cpe_from_type("soft");
3327    /// assert_eq!(cpe_app.cpe_type, CpeType::Application);
3328    /// ```
3329    pub fn create_cpe_from_type(apps_type: &str) -> Cpe {
3330        match apps_type {
3331            CPE_OS_LABEL => Cpe::new(CpeType::OperatingSystem),
3332            CPE_SOFT_LABEL => Cpe::new(CpeType::Application),
3333            CPE_HARDWARE_LABEL => Cpe::new(CpeType::Hardware),
3334            _ => Cpe::new(CpeType::All),
3335        }
3336    }
3337
3338    /// Returns a string representation of the instance.
3339    ///
3340    /// # Returns
3341    ///
3342    /// A `String` containing the formatted representation of the instance.
3343    ///
3344    /// # Notes:
3345    ///
3346    /// - the output format is : cpe:2.3:&lt;part&gt;:&lt;vendor&gt;:&lt;product&gt;:&lt;version&gt;:&lt;update&gt;:&lt;edition&gt;
3347    /// - the output format is limited with elements only used with these tools:
3348    ///     - &lt;part&gt;: support only o (for operating system, a for application),
3349    ///     - the &lt;sw_edition&gt;:&lt;target_sw&gt;:&lt;target_hw&gt;:&lt;other&gt; of the CPE norm is not supported.
3350    ///
3351    impl Display for Cpe {
3352        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3353            let soft_type = match self.cpe_type {
3354                CpeType::Application => "a",
3355                CpeType::OperatingSystem => "o",
3356                CpeType::Hardware => "h",
3357                CpeType::All => "*",
3358            };
3359
3360            write!(
3361                f,
3362                "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
3363                CPE_HEADER,
3364                soft_type,
3365                self.vendor,
3366                self.product,
3367                self.version,
3368                self.update,
3369                self.edition,
3370                self.language,
3371                self.sw_edition,
3372                self.target_sw,
3373                self.target_hw,
3374                self.other
3375            )
3376        }
3377    }
3378
3379    /// Represents different types of Common Platform Enumeration (CPE) identifiers.
3380    ///
3381    /// CPE is a standardized method of describing and identifying classes of
3382    /// applications, operating systems, and hardware devices present among an enterprise's computing assets.
3383    ///
3384    /// # Variants
3385    ///
3386    /// - `All`: Represents all type of products.
3387    /// - `Application`: Represents a software application.
3388    /// - `OperatingSystem`: Represents an operating system.
3389    ///
3390    #[derive(Eq, PartialEq, Clone, Debug, Default)]
3391    pub enum CpeType {
3392        #[default]
3393        All,
3394        Application,
3395        OperatingSystem,
3396        Hardware,
3397    }
3398
3399    impl Display for CpeType {
3400        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3401            let value = match self {
3402                CpeType::All => "*",
3403                CpeType::Application => "application",
3404                CpeType::OperatingSystem => "Os",
3405                CpeType::Hardware => "hardware",
3406            };
3407
3408            f.write_str(value)
3409        }
3410    }
3411
3412    /// CPE request header that's identifying the release used (cpe v 2.3)
3413    const CPE_HEADER: &str = "cpe:2.3";
3414
3415    /// Extracts elements from a Common Platform Enumeration (CPE) string by splitting it on colons (`:`)
3416    /// and converting each element to lowercase.
3417    ///
3418    /// If the number of elements in the CPE string is less than or equal to 8, this function will write an
3419    /// error message and exit the process with a status code of 1.
3420    ///
3421    /// # Arguments
3422    ///
3423    /// * `cpe_string` - A string slice representing the CPE string to be parsed.
3424    ///
3425    /// # Returns
3426    ///
3427    /// A vector of strings, each representing an element of the CPE string.
3428    ///
3429    /// # Panics
3430    ///
3431    /// This function will exit the process if the number of elements is less than or equal to 8.
3432    ///
3433    /// # Examples
3434    ///
3435    /// ```
3436    /// use fenir::cpe::extract_elements;
3437    ///
3438    /// let cpe = "cpe:2.3:o:vendor:product:version:update:edition:language:*:*:*";
3439    /// let elements = extract_elements(cpe);
3440    /// assert!(elements.len() > 8);
3441    /// ```
3442    pub fn extract_elements(cpe_string: &str) -> Vec<String> {
3443        let elements: Vec<String> = cpe_string.split(':').map(|s| s.to_lowercase()).collect();
3444        if elements.len() <= 8 {
3445            write_error_message_and_exit(NOT_ENOUGH_ELEMENTS_ERROR, None);
3446        }
3447        elements
3448    }
3449}
3450
3451/// Shows the version given as argument
3452///
3453/// # Arguments
3454///
3455/// * `version` - A string that's describing the version.
3456///
3457pub fn show_version(version: &str) {
3458    println!("Version: {}", version);
3459    println!("(fenir lib version: {})", env!("CARGO_PKG_VERSION"));
3460}
3461
3462/// Module for CVE structure definition and its associated functions
3463pub mod cve {
3464    use crate::cwe::join_cwe_list;
3465    use crate::database::{MitreData, MitreDefinition};
3466    use crate::facilities::Cleaning;
3467    use serde_json::Value;
3468    use std::fmt::{Display, Formatter};
3469
3470    /// The CVE score value. The score value is corresponding to the standard CVE NVD score.
3471    #[derive(Default, PartialEq, Clone, PartialOrd, Ord, Eq)]
3472    pub struct Score {
3473        score: String,
3474    }
3475
3476    impl Score {
3477        /// Create a new score instance.
3478        ///
3479        /// # Argument:
3480        ///
3481        /// * `value` - string score value. This value must be a numeric forms that is conforming to the NVD standard.
3482        pub fn new(value: String) -> Score {
3483            Self { score: value }
3484        }
3485
3486        /// Float value of the CVE score.
3487        ///
3488        ///
3489        /// # Return
3490        ///
3491        /// Score float value.
3492        pub fn value(&self) -> f64 {
3493            self.score.parse::<f64>().unwrap()
3494        }
3495
3496        /// Score's label. This label shown is depending on the score value as the NVD standard definition.
3497        ///
3498        /// # Return
3499        ///
3500        /// The score label.
3501        ///
3502        /// # Example
3503        ///
3504        /// ```rust
3505        /// use fenir::cve::Score;
3506        ///
3507        /// let score = Score::new("5.2".to_string());
3508        ///
3509        /// assert_eq!("MEDIUM", score.label());
3510        /// ```
3511        pub fn label(&self) -> &str {
3512            match self.value() {
3513                0.0 => "None",
3514                value if value > 0.0 && value < 4.0 => "LOW",
3515                value if (4.0..7.0).contains(&value) => "MEDIUM",
3516                value if (7.0..9.0).contains(&value) => "HIGH",
3517                _ => "CRITICAL",
3518            }
3519        }
3520    }
3521
3522    /// Represents a CVE with a reference.
3523    #[derive(PartialEq, PartialOrd, Ord, Eq, Default)]
3524    pub struct Cve {
3525        /// The CVE reference like: CVE-2024-12021
3526        pub reference: String,
3527
3528        /// Optional description. Some new CVE can have no description.
3529        pub description: Option<String>,
3530
3531        /// Optional score CVSS3. Some new CVE can have no score.
3532        pub score_v3: Option<Score>,
3533
3534        /// Optional score CVSS4. Some new CVE can have no score.
3535        pub score_v4: Option<Score>,
3536
3537        /// Optional CPE identification links with this CVE. Some new CVE can have no cpe id.
3538        pub cpe_id: Option<String>,
3539
3540        /// Optional weaknesses links with this CVE. Some new CVE can have no weaknesses (cwe).
3541        pub weaknesses: Option<String>,
3542    }
3543
3544    impl Cve {
3545        /// Create a new CVE.
3546        ///
3547        /// # Argument
3548        ///
3549        /// * `reference`- The CVE reference
3550        ///
3551        /// # Return
3552        ///
3553        /// A new instance.
3554        ///
3555        /// # Example
3556        ///
3557        /// ```rust
3558        /// use fenir::cve::Cve;
3559        /// let cve = Cve::new("CVE-2025-12040".to_string());
3560        ///
3561        /// assert_eq!("CVE-2025-12040", cve.reference);
3562        /// ```
3563        pub fn new(reference: String) -> Self {
3564            Self {
3565                reference,
3566                ..Default::default()
3567            }
3568        }
3569    }
3570
3571    impl Display for Cve {
3572        fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
3573            write!(formatter, "{}", String::cleaning(self.reference.clone()))
3574        }
3575    }
3576
3577    /// NVD url link Rest API for CVE queries.
3578    impl MitreDefinition for Cve {
3579        fn define() -> MitreData {
3580            MitreData {
3581                name: "Cve",
3582                url: "https://services.nvd.nist.gov/rest/json/cves/2.0",
3583                files: vec![],
3584            }
3585        }
3586    }
3587
3588    /// Create a CVE from the raw value
3589    ///
3590    /// # Arguments
3591    ///
3592    /// * `value`- Raw value
3593    ///
3594    /// # Returns
3595    ///
3596    /// The CVE
3597    pub fn create_cve_from(value: &Value) -> Cve {
3598        let mut cve = Cve::new(String::cleaning(value["cve"]["id"].to_string()));
3599        cve.description = Some(String::cleaning(
3600            value["cve"]["descriptions"][0]["value"].to_string(),
3601        ));
3602
3603        let score_value =
3604            value["cve"]["metrics"]["cvssMetricV31"][0]["cvssData"]["baseScore"].to_string();
3605        cve.score_v3 = match score_value.as_str() {
3606            "null" | "None" => Some(Score::new("0.0".to_string())),
3607            _ => Some(Score::new(score_value)),
3608        };
3609
3610        let score_value =
3611            value["cve"]["metrics"]["cvssMetricV40"][0]["cvssData"]["baseScore"].to_string();
3612        cve.score_v4 = match score_value.as_str() {
3613            "null" | "None" => Some(Score::new("0.0".to_string())),
3614            _ => Some(Score::new(score_value)),
3615        };
3616
3617        let cwe = value["cve"]["weaknesses"][0]["description"].as_array();
3618        cve.weaknesses = match cwe {
3619            Some(_) => Some(String::cleaning(join_cwe_list(cwe.unwrap()))),
3620            None => Some(String::from("None")),
3621        };
3622
3623        let optional_cpe = value["cve"]["configurations"][0]["nodes"][0]["cpeMatch"].as_array();
3624        if let Some(cpe_id) = optional_cpe {
3625            cve.cpe_id = Some(String::cleaning(
3626                cpe_id.first().unwrap()["criteria"].to_string(),
3627            ));
3628        } else {
3629            cve.cpe_id = Some(String::from("None"));
3630        }
3631
3632        cve
3633    }
3634
3635    /// Extracts a list of CVE (Common Vulnerabilities and Exposures) references
3636    /// from a given JSON value that contains CWE (Common Weakness Enumeration) data.
3637    ///
3638    /// # Arguments
3639    ///
3640    /// * `cwe_list` - A JSON value that contains a list of vulnerabilities under the key `"vulnerabilities"`.
3641    ///
3642    /// # Returns
3643    ///
3644    /// This function returns a vector of `Cve` structs, each containing the reference (`id`) of a CVE.
3645    ///
3646    /// # Example
3647    ///
3648    /// ```rust
3649    /// use serde_json::json;
3650    /// use fenir::cve::cve_from_cwe;
3651    ///
3652    /// let cwe_json = json!({
3653    ///     "vulnerabilities": [
3654    ///         { "cve": { "id": "CVE-2021-1234" }},
3655    ///         { "cve": { "id": "CVE-2021-5678" }}
3656    ///     ]
3657    /// });
3658    ///
3659    /// let cves = cve_from_cwe(&cwe_json);
3660    /// assert_eq!(cves.len(), 2);
3661    /// assert_eq!(cves[0].reference, "CVE-2021-1234");
3662    /// assert_eq!(cves[1].reference, "CVE-2021-5678");
3663    /// ```
3664    ///
3665    /// # Note
3666    ///
3667    /// It is assumed that the `Cve` struct is defined elsewhere in your codebase.
3668    /// Below is an example of how `Cve` might be defined:
3669    ///
3670    /// ```rust
3671    /// #[derive(PartialEq, PartialOrd, Ord, Eq)]
3672    /// pub struct Cve {
3673    ///     pub reference: String,
3674    /// }
3675    /// ```
3676    pub fn cve_from_cwe(cwe_list: &Value) -> Vec<Cve> {
3677        let mut result: Vec<Cve> = vec![];
3678        let binding = cwe_list["vulnerabilities"].as_array();
3679        if let Some(_item) = binding {
3680            let lines = binding.unwrap();
3681            lines
3682                .iter()
3683                .for_each(|line| result.push(create_cve_from(line)))
3684        }
3685
3686        result
3687    }
3688}
3689
3690/// Module for CAPEC structure definition and its associated functions.
3691pub mod capec {
3692    use crate::database::{MitreData, MitreDefinition, find_capec_by_id, parse_id_to_u32};
3693    use crate::facilities::{
3694        FIELD_SEPARATOR_STRING, FIELD_SEPARATOR_STRING_LONG, Standardize, Uppercase,
3695    };
3696    use crate::{header, section};
3697    use std::fmt::{Display, Formatter};
3698    use termint::enums::Color;
3699    use termint::widgets::ToSpan;
3700
3701    /// Structure for a CAPEC record
3702    #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
3703    pub struct CapecRecord {
3704        /// Capec's id
3705        pub id: u32,
3706
3707        /// Capec's name
3708        pub name: String,
3709
3710        /// Capec's abstraction
3711        pub abstraction: String,
3712
3713        /// Capec's status
3714        pub status: String,
3715
3716        /// Capec's description
3717        pub description: String,
3718
3719        /// Capec's Likelihood
3720        pub likelihood: String,
3721
3722        /// Capec's alternate terms
3723        pub alternates: String,
3724
3725        /// Capec's severity
3726        pub severity: String,
3727
3728        /// Capec's flow
3729        pub flow: String,
3730
3731        /// Capec's related attack terms
3732        pub attacks: String,
3733
3734        /// Capec prerequisites
3735        pub prerequisites: String,
3736
3737        /// Capec's consequences
3738        pub consequences: String,
3739
3740        /// Capec's skills
3741        pub skills: String,
3742
3743        /// Capec's resources
3744        pub resources: String,
3745
3746        /// Capec's indicators
3747        pub indicators: String,
3748
3749        /// Capec mitigations
3750        pub mitigations: String,
3751
3752        /// Capec weaknesses
3753        pub weaknesses: String,
3754
3755        /// Capec's instances
3756        pub instances: String,
3757
3758        /// Capec's taxonomy
3759        pub taxonomy: String,
3760    }
3761
3762    impl Display for CapecRecord {
3763        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3764            write!(
3765                f,
3766                "{}\n{} {} (Abstraction: {}. Status: {}.)",
3767                header!(format!("CAPEC-{}", self.id)),
3768                format!("{:>4} {}", "︎-", "Name:").fg(Color::DarkBlue),
3769                self.name,
3770                self.abstraction,
3771                self.status
3772            )
3773        }
3774    }
3775
3776    /// Capec's url to get all .csv.zip files to load into the internal database.
3777    impl MitreDefinition for CapecRecord {
3778        fn define() -> MitreData {
3779            MitreData {
3780                name: "Capec",
3781                url: "https://capec.mitre.org/data/csv",
3782                files: vec![
3783                    "282.csv.zip",
3784                    "283.csv.zip",
3785                    "284.csv.zip",
3786                    "483.csv.zip",
3787                    "553.csv.zip",
3788                    "683.csv.zip",
3789                    "703.csv.zip",
3790                    "1000.csv.zip",
3791                    "2000.csv.zip",
3792                ],
3793            }
3794        }
3795    }
3796
3797    /// Shows the list of attacks describing into the string value. Each id value is separating from
3798    /// each value with a `:` characters, as: `:<id_1>:<id_2>:...:<id_n>:`. The `id` value is the
3799    /// CAPEC's id.
3800    ///
3801    /// # Arguments
3802    /// * `attacks` - List of CAPEC's ids.
3803    ///
3804    pub fn show_attacks(attacks: String) {
3805        let capec_list = parse_id_to_u32(attacks);
3806        if capec_list.is_empty() {
3807            println!("{:>4} None", "-");
3808            return;
3809        }
3810
3811        for capec_id in capec_list {
3812            let result = find_capec_by_id(capec_id);
3813            if let Some(record) = result {
3814                println!("{:>4} CAPEC-{} - {}", "-", capec_id, record.name);
3815            } else {
3816                println!("{:>4} CAPEC-{} - ???", "-", capec_id);
3817            }
3818        }
3819    }
3820
3821    /// Return the list of Mitre Att&ck' ids from the given taxonomy.
3822    ///
3823    /// # Arguments
3824    /// * `taxonomy`- CAPEC's taxonomy.
3825    ///
3826    /// # Returns
3827    /// The list of Mitre Att&ck's ids from the taxonomy.
3828    pub fn extract_mitre_attack_ids(taxonomy: String) -> Vec<String> {
3829        let normalized_taxonomy =
3830            taxonomy.standardize(vec!["TAXONOMY NAME", "ENTRY ID", "ENTRY NAME"]);
3831
3832        let mut result = Vec::new();
3833
3834        if !normalized_taxonomy.is_empty() {
3835            let sections = normalized_taxonomy.split(FIELD_SEPARATOR_STRING_LONG);
3836            sections.for_each(|section| {
3837                let cells = section
3838                    .split(FIELD_SEPARATOR_STRING)
3839                    .filter(|cell| !cell.is_empty())
3840                    .collect::<Vec<&str>>();
3841
3842                let is_mitre = cells.contains(&"TAXONOMY NAME") && cells.contains(&"attack");
3843                if is_mitre {
3844                    result.push(cells[3].to_owned());
3845                }
3846            });
3847        }
3848
3849        result
3850    }
3851}
3852
3853/// Error message for error on not enough element during parsing
3854const NOT_ENOUGH_ELEMENTS_ERROR: &str = "Not enough elements for parsing";
3855
3856/// Module for Mitre Att&ck structure definition and its associated functions.
3857pub mod mitre {
3858
3859    /// Describes a mitre record. If some values are not existing into the database, the corresponding field is empty.
3860    pub struct MitreRecord {
3861        /// The Mitre's id
3862        pub id: String,
3863
3864        /// The name of this mitre record
3865        pub name: String,
3866
3867        /// The description of this mitre record
3868        pub description: String,
3869
3870        /// The URL associated with this record. This URL allows to get more information for example.
3871        pub url: String,
3872
3873        /// The creation date.
3874        pub created: String,
3875
3876        /// The last updating date.
3877        pub modified: String,
3878
3879        /// The domain for this mitre record. If several domains are existing, this string contains all these separated by a comma.
3880        pub domain: String,
3881
3882        /// The version of this record.
3883        pub version: String,
3884
3885        /// The associated tactics for this mitre record. If several tactics are existing, this string contains all these separated by a comma.
3886        pub tactics: String,
3887
3888        /// The list of platforms. If several platforms are existing, this string contains all these separated by a comma.
3889        pub platforms: String,
3890
3891        /// Associated sub-techniques.
3892        pub sub_techniques: String,
3893
3894        /// The list of contributors. If several contributors are existing, this string contains all these separated by a comma.
3895        pub contributors: String,
3896
3897        /// Status that indicate if this Mitre record support the remote mode or not.
3898        pub support_remote: String,
3899
3900        /// Type of impact of this attack
3901        pub impact_type: String,
3902
3903        /// The list of citations that are using to document this Mitre record. If several contributors are existing, this string contains all these separated by a comma.
3904        pub citations: String,
3905
3906        /// Type of tactic used with this Mitre record
3907        pub tactic_type: String,
3908
3909        /// MTC's id used for the `Mobile` attack domain.
3910        pub mtc_id: String,
3911    }
3912}
3913
3914#[cfg(test)]
3915mod tests {
3916    use std::thread::sleep;
3917    use std::time::Duration;
3918
3919    fn setup() {
3920        let waiting = Duration::from_millis(30);
3921        sleep(waiting);
3922    }
3923
3924    mod query {
3925        use crate::network::validate_connection;
3926        use crate::tests::setup;
3927        use crate::{NVD_CVE_LINK, NVD_KEY_NAME};
3928        use std::env;
3929
3930        #[test]
3931        fn nvd_api_key_valid() {
3932            setup();
3933
3934            let env_var = env::var_os(NVD_KEY_NAME);
3935            if env_var.is_some() {
3936                let result = validate_connection(NVD_CVE_LINK);
3937                assert_eq!(result, true);
3938            } else {
3939                panic!("No NVD_API_KEY found");
3940            }
3941        }
3942
3943        #[test]
3944        fn is_connection_valid_url_validate() {
3945            setup();
3946
3947            assert!(validate_connection(NVD_CVE_LINK));
3948        }
3949
3950        #[test]
3951        fn is_connection_invalid_url_invalidate() {
3952            assert!(!validate_connection("http://foobar.local"));
3953        }
3954
3955        mod build_query {
3956            use crate::NVD_CVE_LINK;
3957            use crate::facilities::get_current_date;
3958            use crate::query::QueryType::{ByCve, ByCveNew, ByCveUpdated, ByCwe};
3959            use crate::query::build_query;
3960            use crate::tests::setup;
3961
3962            #[test]
3963            fn build_query_cve_valid() {
3964                setup();
3965
3966                let result = format!("{NVD_CVE_LINK}?cveId=CVE-2020-2102");
3967
3968                assert_eq!(result, build_query(ByCve, "CVE-2020-2102").unwrap());
3969            }
3970
3971            #[test]
3972            fn build_query_cve_lowercase_valid() {
3973                setup();
3974
3975                let result = format!("{NVD_CVE_LINK}?cveId=CVE-2020-2102");
3976
3977                assert_eq!(result, build_query(ByCve, "cve-2020-2102").unwrap());
3978            }
3979
3980            #[test]
3981            fn build_query_cwe_valid() {
3982                setup();
3983
3984                let result = format!("{NVD_CVE_LINK}?cweId=CWE-287");
3985
3986                assert_eq!(result, build_query(ByCwe, "CWE-287").unwrap());
3987            }
3988
3989            #[test]
3990            fn build_query_cwe_lowercase_valid() {
3991                setup();
3992
3993                let result = format!("{NVD_CVE_LINK}?cweId=CWE-287");
3994
3995                assert_eq!(result, build_query(ByCwe, "cwe-287").unwrap());
3996            }
3997
3998            #[test]
3999            fn build_query_new_dates_valid() {
4000                let result = build_query(ByCveNew, "2024-02-10;2024-03-10");
4001
4002                assert_eq!(
4003                    "https://services.nvd.nist.gov/rest/json/cves/2.0?pubStartDate=2024-02-10T00:00:00.000&pubEndDate=2024-03-10T00:00:00.000",
4004                    result.unwrap().as_str()
4005                );
4006            }
4007
4008            #[test]
4009            fn build_query_new_dates_default() {
4010                setup();
4011
4012                let result = build_query(ByCveNew, "");
4013                let ref_date = format!(
4014                    "{NVD_CVE_LINK}?pubStartDate={}T00:00:00.000&pubEndDate={}T00:00:00.000",
4015                    get_current_date(),
4016                    get_current_date()
4017                );
4018
4019                assert_eq!(ref_date, result.unwrap());
4020            }
4021
4022            #[test]
4023            fn build_query_partial_date_first_default() {
4024                setup();
4025
4026                let result = build_query(ByCveNew, ";2024-01-01");
4027                let ref_date = format!(
4028                    "{NVD_CVE_LINK}?pubStartDate={}T00:00:00.000&pubEndDate=2024-01-01T00:00:00.000",
4029                    get_current_date()
4030                );
4031
4032                assert_eq!(ref_date, result.unwrap());
4033            }
4034
4035            #[test]
4036            fn build_query_partial_date_last_default() {
4037                setup();
4038
4039                let result = build_query(ByCveNew, "2024-01-01;");
4040                let ref_date = format!(
4041                    "{NVD_CVE_LINK}?pubStartDate=2024-01-01T00:00:00.000&pubEndDate={}T00:00:00.000",
4042                    get_current_date()
4043                );
4044
4045                assert_eq!(ref_date, result.unwrap());
4046            }
4047
4048            #[test]
4049            fn build_query_updated_valid() {
4050                setup();
4051
4052                let result = build_query(ByCveUpdated, "2024-01-01;2024-02-01");
4053
4054                assert_eq!(
4055                    format!(
4056                        "{NVD_CVE_LINK}?lastModStartDate=2024-01-01T00:00:00.000&lastModEndDate=2024-02-01T00:00:00.000"
4057                    ),
4058                    result.unwrap()
4059                );
4060            }
4061
4062            #[test]
4063            fn build_query_updated_default_valid() {
4064                setup();
4065
4066                let result = build_query(ByCveUpdated, "");
4067
4068                assert_eq!(
4069                    format!(
4070                        "{NVD_CVE_LINK}?lastModStartDate={}T00:00:00.000&lastModEndDate={}T00:00:00.000",
4071                        get_current_date(),
4072                        get_current_date()
4073                    ),
4074                    result.unwrap()
4075                );
4076            }
4077
4078            #[test]
4079            fn build_query_updated_first_valid() {
4080                setup();
4081
4082                let result = build_query(ByCveUpdated, "2023-01-02;");
4083
4084                assert_eq!(
4085                    format!(
4086                        "{NVD_CVE_LINK}?lastModStartDate=2023-01-02T00:00:00.000&lastModEndDate={}T00:00:00.000",
4087                        get_current_date()
4088                    ),
4089                    result.unwrap()
4090                );
4091            }
4092
4093            #[test]
4094            fn build_query_updated_last_valid() {
4095                setup();
4096
4097                let result = build_query(ByCveUpdated, ";2032-12-12");
4098
4099                assert_eq!(
4100                    format!(
4101                        "{NVD_CVE_LINK}?lastModStartDate={}T00:00:00.000&lastModEndDate=2032-12-12T00:00:00.000",
4102                        get_current_date()
4103                    ),
4104                    result.unwrap()
4105                );
4106            }
4107        }
4108
4109        mod execute_query {
4110            use crate::database::execute_query;
4111            use crate::query::QueryType::{ByCve, ByCveNew, ByCveUpdated};
4112            use crate::query::build_query;
4113            use crate::tests::setup;
4114            use serde_json::Value::Null;
4115
4116            #[test]
4117            fn execute_query_valid_cve_found() {
4118                setup();
4119
4120                let result = execute_query(build_query(ByCve, "CVE-2019-1010218"));
4121
4122                assert_ne!(Null, result["resultsPerPage"]);
4123            }
4124
4125            #[test]
4126            fn execute_query_invalid_cve_not_found() {
4127                setup();
4128
4129                let result = execute_query(build_query(ByCve, "CVE-1980-0000"));
4130
4131                assert_eq!(0, result["resultsPerPage"]);
4132            }
4133
4134            #[test]
4135            fn execute_query_new_dates_valid() {
4136                setup();
4137
4138                let result = execute_query(build_query(ByCveNew, "2024-01-01;2024-02-01"));
4139
4140                assert_ne!(0, result["resultsPerPage"]);
4141            }
4142
4143            #[test]
4144            fn execute_query_new_dates_swapped_not_valid() {
4145                setup();
4146
4147                let result = execute_query(build_query(ByCveNew, "2024-02-01;2024-01-01"));
4148
4149                assert_eq!(Null, result["resultsPerPage"]);
4150            }
4151
4152            #[test]
4153            fn execute_query_new_dates_malformed_no_valid() {
4154                setup();
4155
4156                let result = execute_query(build_query(ByCveNew, "202402-01;2024-03-01"));
4157
4158                assert_eq!(Null, result["resultPerPage"]);
4159            }
4160
4161            #[test]
4162            fn execute_query_update_dates_valid() {
4163                setup();
4164
4165                let result = execute_query(build_query(ByCveUpdated, "2024-01-01;2024-02-01"));
4166
4167                assert_ne!(0, result["resultsPerPage"]);
4168            }
4169
4170            #[test]
4171            fn execute_query_updated_dates_swapped_not_valid() {
4172                setup();
4173
4174                let result = execute_query(build_query(ByCveUpdated, "2024-02-01;2024-01-01"));
4175
4176                assert_eq!(Null, result["resultsPerPage"]);
4177            }
4178
4179            #[test]
4180            fn execute_query_updated_dates_malformed_no_valid() {
4181                setup();
4182
4183                let result = execute_query(build_query(ByCveUpdated, "202402-01;2024-03-01"));
4184
4185                assert_eq!(Null, result["resultPerPage"]);
4186            }
4187        }
4188    }
4189
4190    mod string {
4191        use crate::facilities::{Categorize, Cleaning, Standardize, Transform, Uppercase, Wording};
4192
4193        #[test]
4194        fn cleaning_ok() {
4195            let result = String::cleaning(String::from("\"value\""));
4196
4197            assert_eq!("value", result);
4198        }
4199
4200        #[test]
4201        fn cleaning_dots_ok() {
4202            let result = String::cleaning(String::from(":value:"));
4203
4204            assert_eq!("value", result);
4205        }
4206
4207        #[test]
4208        fn cleaning_no_cleaning() {
4209            let result = String::cleaning(String::from("value"));
4210
4211            assert_eq!("value", result);
4212        }
4213
4214        #[test]
4215        fn cleaning_empty_string_not_updated() {
4216            let result = String::cleaning(String::from(""));
4217
4218            assert_eq!("", result);
4219        }
4220
4221        #[test]
4222        fn only_uppercases_valid() {
4223            assert!(String::only_uppercases(String::from("BJDKE")));
4224        }
4225
4226        #[test]
4227        fn only_uppercases_invalid() {
4228            assert_eq!(false, String::only_uppercases(String::from("ABDdD")));
4229        }
4230
4231        #[test]
4232        fn only_uppercases_empty_invalid() {
4233            assert!(!String::only_uppercases(String::from("")));
4234        }
4235
4236        #[test]
4237        fn only_uppercases_with_space_valid() {
4238            assert!(String::only_uppercases(String::from("TECHNICAL PART")));
4239        }
4240
4241        #[test]
4242        fn smoothing_valid() {
4243            let original = String::from("::KKKK:fgte::::DFDF:fff::FFF:E::");
4244            let result = original.standardize(vec!["KKKK", "DFDF"]);
4245
4246            assert_eq!("::KKKK:fgte::DFDF:fff:fff:e::", result);
4247        }
4248
4249        #[test]
4250        fn smoothing_not_updated() {
4251            let original = String::from("::DKDKDKD::");
4252            let result = original.standardize(vec![""]);
4253
4254            assert_eq!("::dkdkdkd::", result);
4255        }
4256
4257        #[test]
4258        fn transform_valid() {
4259            let original = String::from("KKKKK fgte. DFDF");
4260            let result = original.transform(vec!["KKKKK", "DFDF"]);
4261
4262            assert_eq!("::KKKKK: fgte. ::DFDF::", result);
4263        }
4264
4265        #[test]
4266        fn wording_empty_string_valid() {
4267            let original = String::from("");
4268            let result = original.wording();
4269
4270            assert_eq!("", result);
4271        }
4272
4273        #[test]
4274        fn wording_associated_valid() {
4275            let original = String::from("wordAssociated");
4276            let result = original.wording();
4277
4278            assert_eq!("Word associated", result);
4279        }
4280
4281        #[test]
4282        fn wording_associated_inverse_valid() {
4283            let original = String::from("Wordassociation");
4284            let result = original.wording();
4285
4286            assert_eq!("Wordassociation", result);
4287        }
4288
4289        #[test]
4290        fn wording_not_associated_no_transformation() {
4291            let original = String::from("wordassociated");
4292            let result = original.wording();
4293
4294            assert_eq!("Wordassociated", result);
4295        }
4296
4297        #[test]
4298        fn wording_with_multi_words() {
4299            let original = String::from("OneWordAssociatedWithAnother");
4300            let result = original.wording();
4301
4302            assert_eq!("One word associated with another", result);
4303        }
4304
4305        #[test]
4306        fn wording_uppercases_lowercases_valid() {
4307            let original = String::from("WORDassociated");
4308            let result = original.wording();
4309
4310            assert_eq!("Wordassociated", result);
4311        }
4312
4313        #[test]
4314        fn wording_right_form_valid() {
4315            let original = String::from("Space");
4316            let result = original.wording();
4317
4318            assert_eq!("Space", result);
4319        }
4320
4321        #[test]
4322        fn wording_with_spaces_valid() {
4323            let original = String::from("Space From another World");
4324            let result = original.wording();
4325
4326            assert_eq!("Space from another world", result);
4327        }
4328
4329        #[test]
4330        fn first_uppercase_valid() {
4331            let original = String::from("first");
4332            let result = original.first_uppercase();
4333
4334            assert_eq!("First", result);
4335        }
4336
4337        #[test]
4338        fn first_already_uppercase_valid() {
4339            let original = String::from("First");
4340            let result = original.first_uppercase();
4341
4342            assert_eq!("First", result);
4343        }
4344    }
4345
4346    mod parse_id {
4347        use crate::os::parse_id;
4348
4349        #[test]
4350        fn cwe_id_valid_id_parsed() {
4351            assert_eq!(Some(15), parse_id("CWE-15", "cwe"));
4352        }
4353
4354        #[test]
4355        fn cwe_id_valid_lowercase_id_parsed() {
4356            assert_eq!(Some(118), parse_id("cwe-118", "cwe"));
4357        }
4358
4359        #[test]
4360        fn cwe_id_invalid_id_not_parsed() {
4361            assert_eq!(None, parse_id("cwe116", "cwe"));
4362        }
4363
4364        #[test]
4365        fn cwe_id_invalid_id_num_not_parsed() {
4366            assert_eq!(None, parse_id("CWE-A1", "cwe"));
4367        }
4368
4369        #[test]
4370        fn cwe_id_invalid_id_prefix_not_parsed() {
4371            assert_eq!(None, parse_id("DFDF-12", "cwe"));
4372        }
4373
4374        #[test]
4375        fn cwe_id_incomplete_id_not_parsed() {
4376            assert_eq!(None, parse_id("CWE-", "cwe"));
4377        }
4378
4379        #[test]
4380        fn cwe_id_no_separator_not_parsed() {
4381            assert_eq!(None, parse_id("CWE", "cwe"));
4382        }
4383    }
4384
4385    mod criteria {
4386        use crate::database::criteria_analysis;
4387        use utmt::{assert_match_option, assert_not_match_option};
4388
4389        #[test]
4390        fn simple_no_error() -> Result<(), &'static str> {
4391            let args = String::from("Masquerading");
4392            let result = criteria_analysis(args);
4393
4394            assert_match_option!(result)
4395        }
4396
4397        #[test]
4398        fn simple_right_content() -> Result<(), &'static str> {
4399            let args = String::from("Masquerading");
4400            let result = criteria_analysis(args);
4401
4402            match result.unwrap().0.contains(&String::from("Masquerading")) {
4403                true => Ok(()),
4404                false => Err("Simple criteria analysis failed: content not found"),
4405            }
4406        }
4407
4408        #[test]
4409        fn analysis_empty_string() -> Result<(), &'static str> {
4410            let result = criteria_analysis(String::from(""));
4411
4412            assert_not_match_option!(result)
4413        }
4414
4415        #[test]
4416        fn analysis_only_or_operator() -> Result<(), &'static str> {
4417            let args = String::from("Masquerading or Hijack");
4418            let result = criteria_analysis(args);
4419
4420            assert_match_option!(result)
4421        }
4422
4423        #[test]
4424        fn analysis_or_operator_right_content() -> Result<(), &'static str> {
4425            let args = String::from("Masquerading or Hijack");
4426            let result = criteria_analysis(args);
4427
4428            let mut count = 0;
4429            result.clone().unwrap().0.iter().for_each(|x| {
4430                if !x.trim().is_empty() {
4431                    if x.trim() == "Masquerading" || x.trim() == "Hijack" {
4432                        count += 1;
4433                    } else {
4434                        count -= 1;
4435                    }
4436                }
4437            });
4438
4439            result.unwrap().1.iter().for_each(|x| {
4440                if x.trim() == "or" {
4441                    count += 1;
4442                } else {
4443                    count -= 1;
4444                }
4445            });
4446
4447            match count {
4448                3 => Ok(()),
4449                _ => Err("Error with or operator"),
4450            }
4451        }
4452
4453        #[test]
4454        fn analysis_only_and_operator() -> Result<(), &'static str> {
4455            let args = String::from("Process and Hijacking");
4456            let result = criteria_analysis(args);
4457
4458            assert_match_option!(result)
4459        }
4460
4461        #[test]
4462        fn analysis_and_operator_right_content() -> Result<(), &'static str> {
4463            let args = String::from("Process and Hijacking");
4464            let result = criteria_analysis(args);
4465
4466            let mut count = 0;
4467            result.clone().unwrap().0.iter().for_each(|x| {
4468                if !x.is_empty() {
4469                    if x.trim() == "Process" || x.trim() == "Hijacking" {
4470                        count += 1;
4471                    } else {
4472                        count -= 1;
4473                    }
4474                }
4475            });
4476
4477            result.unwrap().1.iter().for_each(|x| {
4478                if x.trim() == "and" {
4479                    count += 1;
4480                } else {
4481                    count -= 1;
4482                }
4483            });
4484
4485            match count {
4486                3 => Ok(()),
4487                _ => Err("Error with and operator"),
4488            }
4489        }
4490
4491        #[test]
4492        fn analysis_only_not_operator() -> Result<(), &'static str> {
4493            let args = String::from("not Masquerading");
4494            let result = criteria_analysis(args);
4495
4496            assert_match_option!(result)
4497        }
4498
4499        #[test]
4500        fn analysis_not_operator_right_content() -> Result<(), &'static str> {
4501            let args = String::from("not Masquerading");
4502            let result = criteria_analysis(args);
4503
4504            let mut count = 0;
4505            result.clone().unwrap().0.iter().for_each(|x| {
4506                if x.trim() == "Masquerading" || x.trim() == "" {
4507                    count += 1;
4508                } else {
4509                    count -= 1;
4510                }
4511            });
4512
4513            result.unwrap().1.iter().for_each(|x| {
4514                if x.trim() == "not" {
4515                    count += 1;
4516                } else {
4517                    count -= 1;
4518                }
4519            });
4520
4521            match count {
4522                3 => Ok(()),
4523                _ => Err("Error with not operator"),
4524            }
4525        }
4526
4527        #[test]
4528        fn analysis_with_and_or_operators() -> Result<(), &'static str> {
4529            let args = String::from("Phishing or Process and Hijacking");
4530            let result = criteria_analysis(args);
4531
4532            assert_match_option!(result)
4533        }
4534
4535        #[test]
4536        fn analysis_and_operators_right_content() -> Result<(), &'static str> {
4537            let args = String::from("Phishing or Process and Hijacking");
4538            let result = criteria_analysis(args);
4539
4540            let mut count = 0;
4541            result.clone().unwrap().0.iter().for_each(|x| {
4542                if !x.is_empty() {
4543                    if x.trim() == "Phishing" || x.trim() == "Process" || x.trim() == "Hijacking" {
4544                        count += 1;
4545                    } else {
4546                        count -= 1;
4547                    }
4548                }
4549            });
4550
4551            result.unwrap().1.iter().for_each(|x| {
4552                if x.trim() == "and" || x.trim() == "or" {
4553                    count += 1;
4554                } else {
4555                    count -= 1;
4556                }
4557            });
4558
4559            match count {
4560                5 => Ok(()),
4561                _ => Err("Error with or/and operators"),
4562            }
4563        }
4564
4565        #[test]
4566        fn analysis_and_or_not_operators() -> Result<(), &'static str> {
4567            let args = String::from("Phishing or Process and Hijacking and not Masquerading");
4568            let result = criteria_analysis(args);
4569
4570            assert_match_option!(result)
4571        }
4572
4573        #[test]
4574        fn analysis_and_or_not_operators_right_content() -> Result<(), &'static str> {
4575            let args = String::from("Phishing or Process and Hijacking and not Masquerading");
4576            let result = criteria_analysis(args);
4577
4578            let mut count = 0;
4579            result.clone().unwrap().0.iter().for_each(|x| {
4580                if !x.trim().is_empty() {
4581                    if x.trim() == "Phishing"
4582                        || x.trim() == "Process"
4583                        || x.trim() == "Hijacking"
4584                        || x.trim() == "Masquerading"
4585                    {
4586                        count += 1;
4587                    } else {
4588                        count -= 1;
4589                    }
4590                }
4591            });
4592
4593            result.unwrap().1.iter().for_each(|x| {
4594                if x.trim() == "and" || x.trim() == "or" || x.trim() == "not" {
4595                    count += 1;
4596                } else {
4597                    count -= 1;
4598                }
4599            });
4600
4601            match count {
4602                8 => Ok(()),
4603                _ => Err("Error with and/or/not operators"),
4604            }
4605        }
4606    }
4607
4608    mod find_domain {
4609        use crate::database::find_domain_and_criteria;
4610        use utmt::{assert_all, assert_match_option, assert_not_match_option};
4611
4612        #[test]
4613        fn criteria_simple_no_error() -> Result<(), &'static str> {
4614            let args = &[String::from("taxonomy = Hijack")];
4615            let values = find_domain_and_criteria(args);
4616
4617            assert_match_option!(values)
4618        }
4619
4620        #[test]
4621        fn criteria_simple_with_error() -> Result<(), &'static str> {
4622            let args = &[String::from("taxonomy")];
4623            let values = find_domain_and_criteria(args);
4624
4625            assert_not_match_option!(values)
4626        }
4627
4628        #[test]
4629        fn criteria_simple_valid() {
4630            let args = &[String::from("taxonomy = Masquerading")];
4631            let values = find_domain_and_criteria(args);
4632
4633            let (domain, argument) = values.unwrap();
4634
4635            assert_all!(domain == "taxonomy", argument == "Masquerading");
4636        }
4637
4638        #[test]
4639        fn criteria_complex() {
4640            let args = &[String::from(
4641                "taxonomy = Masquerading and Hijack or impair defense",
4642            )];
4643            let values = find_domain_and_criteria(args);
4644            let (domain, argument) = values.unwrap();
4645
4646            assert_all!(
4647                domain == "taxonomy",
4648                argument == "Masquerading and Hijack or impair defense"
4649            );
4650        }
4651
4652        #[test]
4653        fn criteria_malformed_error() -> Result<(), &'static str> {
4654            let args = &[String::from("taxonomy = ")];
4655            let values = find_domain_and_criteria(args);
4656
4657            assert_not_match_option!(values)
4658        }
4659
4660        #[test]
4661        fn criteria_malformed_domain_error() -> Result<(), &'static str> {
4662            let args = &[String::from("= Masquerading ")];
4663            let values = find_domain_and_criteria(args);
4664
4665            assert_not_match_option!(
4666                values,
4667                "The malformed expression ' = Masquerading ' would be invalid"
4668            )
4669        }
4670
4671        #[test]
4672        fn criteria_malformed_equal_only_error() -> Result<(), &'static str> {
4673            let args = &[String::from("=")];
4674            let values = find_domain_and_criteria(args);
4675
4676            assert_not_match_option!(values, "'=' alone would not be supported")
4677        }
4678
4679        #[test]
4680        fn criteria_malformed_triple_equals_error() -> Result<(), &'static str> {
4681            let args = &[String::from("===")];
4682            let values = find_domain_and_criteria(args);
4683
4684            assert_not_match_option!(values, "'===' would not be supported")
4685        }
4686    }
4687
4688    mod mitre {
4689        use crate::database::find_mitre_by_id;
4690
4691        #[test]
4692        fn find_mitre_id() -> Result<(), &'static str> {
4693            if let Some(_) = find_mitre_by_id("1562.003") {
4694                Ok(())
4695            } else {
4696                Err("Mitre data not found")
4697            }
4698        }
4699
4700        #[test]
4701        fn find_invalid_mitre_id() -> Result<(), &'static str> {
4702            if let Some(_) = find_mitre_by_id("") {
4703                Err("Mitre data would be not found")
4704            } else {
4705                Ok(())
4706            }
4707        }
4708    }
4709}