Skip to main content

get_cve/
lib.rs

1#![deny(clippy::mem_forget)]
2
3use ascii_table_rs::{AsciiTable, CellValue};
4use fenir::cpe::Cpe;
5use fenir::cve::{Cve, Score, create_cve_from, cve_from_cwe};
6use fenir::cwe::show_weaknesses;
7use fenir::database::{
8    MitreDefinition, execute_query, find_capec_by_id, find_cwe_by_id, parse_id_to_u32,
9};
10use fenir::facilities::{Cleaning, Uppercase, Wording};
11use fenir::facilities::{
12    FIELD_SEPARATOR_STRING, FIELD_SEPARATOR_STRING_LONG, Reduce, build_dates_range, print_values,
13};
14use fenir::network::{check_mitre_data, check_nvd_api_key, check_nvd_api_key_warning};
15use fenir::package::concepts::{filtering_cve, print_cve, simplify_cve_list_content};
16use fenir::package::errors::{write_error_message, write_error_message_and_exit};
17use fenir::query::QueryType::{
18    ByCpeVulnerable, ByCve, ByCveCpeName, ByCveExploited, ByCveNew, ByCveUpdated, ByCwe,
19};
20use fenir::query::{QueryType, build_query, create_query_dates};
21use fenir::{header, no_argument_and_exit, section, section_level, show_version};
22use serde_json::Value;
23use serde_json::Value::Null;
24use std::io::stdout;
25use std::process::exit;
26use termint::enums::Color;
27use termint::widgets::ToSpan;
28use treelog::builder::TreeBuilder;
29
30#[cfg(target_os = "linux")]
31use crate::package::{get_changelog_for, get_package};
32
33#[cfg(target_os = "linux")]
34use fenir::os::{OSFamily, SupportedOs};
35
36#[cfg(target_os = "linux")]
37use fenir::package::concepts::show_cve_list;
38
39#[cfg(target_os = "linux")]
40use fenir::package::concepts::Changelog;
41
42#[cfg(target_os = "linux")]
43use fenir::package::errors::stop_if_invalid_package;
44
45/// Packages management according ot supported os.
46#[cfg(target_os = "linux")]
47mod package {
48    use crate::package::os::{Debian, RedHat};
49    use core::str;
50    use fenir::facilities::{restore_current_dir_and_remove_temp_dir, show_command_result};
51    use fenir::os::{OSFamily, SupportedOs};
52    use fenir::package::concepts::{
53        Package, PackageStatus, Repository, Request, changelog_result, default_package,
54    };
55    use fenir::package::errors::{write_error_message, write_error_message_and_exit};
56    use std::env;
57    use std::fs::File;
58    use std::io::{BufReader, Read};
59    use std::path::Path;
60    use std::process::{Child, Output, Stdio};
61    use std::string::FromUtf8Error;
62
63    /// Module for OS detection and representation.
64    pub mod os {
65        /// Represents a Debian-based system
66        pub struct Debian;
67
68        /// Represents a RedHat-based system.
69        pub struct RedHat;
70    }
71
72    impl Request for Debian {
73        fn ask_package(&self, package: &str, version: &str) -> Package {
74            let command_output: Result<String, FromUtf8Error> = if version.is_empty() {
75                let command_result = std::process::Command::new("/usr/bin/dpkg-query")
76                    .arg("-W")
77                    .arg(package)
78                    .stderr(Stdio::null())
79                    .output()
80                    .expect("Fail to run dpkg-query command");
81
82                show_command_result(command_result)
83            } else if version.eq("next") {
84                let command_result = std::process::Command::new("/usr/bin/apt")
85                    .arg("list")
86                    .arg("--upgradable")
87                    .arg(package)
88                    .arg("-qq")
89                    .stderr(Stdio::null())
90                    .output()
91                    .expect("Fail to run apt-get list");
92
93                show_command_result(command_result)
94            } else {
95                let cmd_package = std::process::Command::new("/usr/bin/dpkg-query")
96                    .arg("-W")
97                    .arg(package)
98                    .stderr(Stdio::null())
99                    .stdout(Stdio::piped())
100                    .spawn()
101                    .unwrap();
102
103                let cmd_version = grep_version(version, cmd_package);
104
105                show_command_result(cmd_version)
106            };
107
108            extract_package_result(command_output)
109        }
110
111        fn ask_changelog_package(&self, package: Package) -> String {
112            let changelog_command = std::process::Command::new("/usr/bin/apt")
113                .args([
114                    "changelog",
115                    format!("{}={}", package.name, package.version).as_str(),
116                ])
117                .stderr(Stdio::null())
118                .output()
119                .expect("Fail to run apt command");
120
121            changelog_result(
122                package.clone(),
123                changelog_command,
124                self.read_changelog_files(package.clone()),
125            )
126        }
127
128        fn read_changelog_files(&self, package: Package) -> String {
129            let changelog_file = match get_debian_changelog_for("/usr/share/doc", &package) {
130                Ok(file) => file,
131                Err(err) => {
132                    write_error_message(package.name.as_str(), Some(err));
133                    return String::new();
134                }
135            };
136
137            let result = decode_changelog(changelog_file);
138            result.unwrap_or_else(|_| String::new())
139        }
140    }
141
142    /// Decodes a gzipped Debian changelog file and returns its content as a string.
143    ///
144    /// # Arguments
145    ///
146    /// * `file` - A `File` handle to the gzipped changelog file.
147    ///
148    /// # Returns
149    ///
150    /// This function returns a `Result` which is:
151    /// * `Ok(String)` containing the changelog content if the file was successfully
152    ///   decoded and read.
153    /// * `Err(&'static str)` with an error message if the file could not be read.
154    ///
155    /// # Errors
156    ///
157    /// This function will return an error if:
158    /// * The gzipped changelog file cannot be decompressed.
159    /// * The changelog file cannot be read or parsed.
160    ///
161    /// # Side Effects
162    ///
163    /// * Attempts to remove the directory at `/var/tmp/get-cve`.
164    ///
165    fn decode_changelog(file: File) -> Result<String, &'static str> {
166        let gz_decoder = flate2::read::GzDecoder::new(BufReader::new(file));
167        match debian_changelog::ChangeLog::read(BufReader::new(gz_decoder)) {
168            Ok(changelog) => {
169                let _ = std::fs::remove_dir_all("/var/tmp/get-cve");
170                Ok(changelog.to_string())
171            }
172            _ => Err("Failed for changelog reading"),
173        }
174    }
175
176    impl Request for RedHat {
177        fn ask_package(&self, package: &str, version: &str) -> Package {
178            let command_output: Result<String, FromUtf8Error> = if version.is_empty() {
179                let command_result = std::process::Command::new("/usr/bin/rpm")
180                    .arg("-qa")
181                    .arg("--qf")
182                    .arg("%{NAME}.%{ARCH}\t%{VERSION}-%{RELEASE}")
183                    .arg(package)
184                    .stderr(Stdio::null())
185                    .output()
186                    .expect("Error on rpm command running");
187
188                show_command_result(command_result)
189            } else {
190                let command_result = std::process::Command::new("/usr/bin/rpm")
191                    .arg("-qa")
192                    .arg("--qf")
193                    .arg("%{NAME}.%{ARCH}\t%{VERSION}-%{RELEASE}")
194                    .arg(package)
195                    .stderr(Stdio::null())
196                    .stdout(Stdio::piped())
197                    .spawn()
198                    .expect("Error on rpm command running");
199
200                let grep_result = grep_version(version, command_result);
201                show_command_result(grep_result)
202            };
203
204            extract_package_result(command_output)
205        }
206
207        fn ask_changelog_package(&self, package: Package) -> String {
208            let changelog_command = std::process::Command::new("/usr/bin/rpm")
209                .arg("-q")
210                .arg("--changelog")
211                .arg(package.name.as_str())
212                .stderr(Stdio::null())
213                .output()
214                .expect("Impossible to run rpm command for changelog");
215
216            changelog_result(
217                package.clone(),
218                changelog_command,
219                self.read_changelog_files(package.clone()),
220            )
221        }
222
223        fn read_changelog_files(&self, package: Package) -> String {
224            let package_name = extract_package_name(&package.name);
225            let changelog_path = format!("/usr/share/doc/{}", package_name);
226
227            match get_changelog_files(changelog_path) {
228                Some(changelog_files) => merge_files_content(changelog_files),
229                _ => String::new(),
230            }
231        }
232    }
233
234    /// Extracts the base package name from a given package name string.
235    ///
236    /// This function takes a package name string and returns the base package name.
237    /// If the input package name contains a '.', the function will split the string
238    /// at the first '.' and return the portion before the '.'; otherwise, it returns
239    /// the entire input package name.
240    ///
241    /// # Arguments
242    ///
243    /// * `pkg_name` - A string slice that holds the package name.
244    ///
245    /// # Returns
246    ///
247    /// A `String` containing the base package name.
248    ///
249    fn extract_package_name(pkg_name: &str) -> String {
250        if pkg_name.contains('.') {
251            pkg_name.split('.').next().unwrap().to_string()
252        } else {
253            pkg_name.to_string()
254        }
255    }
256
257    /// Retrieves a list of changelog files from the specified directory.
258    ///
259    /// # Arguments
260    ///
261    /// * `directory` - A string slice that holds the path to the directory containing changelog files.
262    ///
263    /// # Returns
264    ///
265    /// * `Result<Vec<String>, std::io::Error>` - A Result containing either:
266    ///   - A Vec of strings, where each string is the path to a changelog file
267    ///   - Or an `std::io::Error` if an error occurred during file reading
268    ///
269    fn get_changelog_files<P: AsRef<Path>>(path: P) -> Option<Vec<std::fs::DirEntry>> {
270        let dir = std::fs::read_dir(path).ok()?;
271        Some(dir.flatten().collect())
272    }
273
274    /// Merges the content of multiple files into a single string.
275    ///
276    /// This function takes a vector of `std::fs::DirEntry` objects representing file entries, reads
277    /// their content, and concatenates it into a single string. Files that cannot be read are
278    /// skipped.
279    ///
280    /// # Arguments
281    ///
282    /// * `files` - A vector of `std::fs::DirEntry` objects representing the files to be read.
283    ///
284    /// # Returns
285    ///
286    /// A `String` containing the concatenated content of all the files that were successfully read.
287    ///
288    ///
289    /// Note: The function assumes that the files contain UTF-8 encoded text.
290    fn merge_files_content(files: Vec<std::fs::DirEntry>) -> String {
291        files
292            .iter()
293            .filter_map(|file| {
294                let path = file.path();
295                if path.is_file() {
296                    let file = File::open(&path).ok()?;
297                    let mut buffer = BufReader::new(file);
298                    let mut content = String::new();
299                    buffer.read_to_string(&mut content).ok()?;
300                    Some(content)
301                } else {
302                    None
303                }
304            })
305            .collect::<String>()
306    }
307
308    /// Retrieves a software package with an optional version.
309    ///
310    /// # Arguments
311    ///
312    /// * `argument` - A string representing the package name.
313    /// * `version` - An optional string representing the version of the package.
314    ///
315    /// # Returns
316    ///
317    /// * `Package` - A struct representing the retrieved package, including its name, version, and status.
318    pub fn get_package(argument: String, version: Option<String>) -> Package {
319        if SupportedOs::supported_os() == OSFamily::Debian {
320            let repository = Repository { repos: Debian };
321            Request::ask_package(
322                &repository.repos,
323                argument.as_str(),
324                version.unwrap_or_default().as_str(),
325            )
326        } else if SupportedOs::supported_os() == OSFamily::RedHat {
327            let repository = Repository { repos: RedHat };
328            Request::ask_package(
329                &repository.repos,
330                argument.as_str(),
331                version.unwrap_or_default().as_str(),
332            )
333        } else {
334            default_package()
335        }
336    }
337
338    /// Retrieves the changelog content for the specified package.
339    ///
340    /// # Arguments
341    ///
342    /// * `package` - A reference to a `Package` struct representing the package for which the changelog is being requested.
343    ///
344    /// # Returns
345    ///
346    /// Returns a `String` containing the changelog content for the specified package.
347    pub fn get_changelog_for(package: &Package) -> String {
348        if SupportedOs::supported_os() == OSFamily::Debian {
349            let repository = Repository { repos: Debian };
350            Request::ask_changelog_package(&repository.repos, package.clone())
351        } else {
352            let repository = Repository { repos: RedHat };
353            Request::ask_changelog_package(&repository.repos, package.clone())
354        }
355    }
356
357    /// Retrieves the Debian changelog for a given package.
358    ///
359    /// This function fetches the changelog of a Debian package, parses it, and
360    /// returns it in a readable format.
361    ///
362    /// # Arguments
363    ///
364    /// * `path` - A `str` that corresponding to the changelog file to explore.
365    /// * `package` - A `Package` that holds the name of the Debian package.
366    ///
367    /// # Returns
368    ///
369    /// This function returns a `Result`:
370    /// * `Ok(String)` - The contents of the Debian changelog.
371    /// * `Err(String)` - An error message if the changelog could not be retrieved.
372    ///
373    /// # Errors
374    ///
375    /// This function returns an error if the package name is not valid,
376    /// the changelog is not available, or if there is a network failure.
377    fn get_debian_changelog_for(path: &str, package: &Package) -> Result<File, &'static str> {
378        match package.status {
379            PackageStatus::Uninstalled => get_next_changelog_file(&mut package.clone()),
380            _ => {
381                let changelogs = [
382                    format!("{}/{}/changelog.Debian.gz", path, package.name),
383                    format!("{}/{}/changelog.gz", path, package.name),
384                ];
385
386                let mut idx = 0usize;
387                let mut result = Err("No alternative changelog");
388                while idx < changelogs.len() && result.is_err() {
389                    let changelog = changelogs[idx].clone();
390                    idx += 1;
391                    result = match File::open(changelog) {
392                        Ok(file) => Ok(file),
393                        _ => Err("No alternative changelog"),
394                    };
395                }
396
397                result
398            }
399        }
400    }
401
402    /// Retrieves the next changelog file for the given package.
403    ///
404    /// This function attempts to locate the next changelog file associated with
405    /// the specified package. It queries the system for the appropriate file paths
406    /// depending on the package's properties and its installation status. The
407    /// resulting changelog file, if found, is returned as a `std::fs::File` object.
408    ///
409    /// With this function, the package will be downloaded and explore into an internal temp directory.
410    /// So, according to package weight, the result can take a while during download.
411    ///
412    /// # Arguments
413    ///
414    /// * `package` - A reference to the `Package` struct, which contains information
415    ///   about the software package, such as its name, version, and current status.
416    ///
417    /// # Returns
418    ///
419    /// This function returns a `Result<File, &'static str>` where:
420    ///
421    /// * `Ok(File)` - Contains the `File` object representing the changelog if found.
422    /// * `Err(&'static str)` - Contains an error message if the changelog file could not be located.
423    ///
424    /// # Errors
425    ///
426    /// This function will return an error if the changelog file cannot be found for the given package.
427    ///
428    fn get_next_changelog_file(package: &mut Package) -> Result<File, &'static str> {
429        let current_dir = env::current_dir();
430        let current_dir_clone = env::current_dir();
431        let _ = std::fs::create_dir("/var/tmp/get-cve");
432        env::set_current_dir("/var/tmp/get-cve").expect("Error setting tmp directory");
433
434        println!("Download {package}");
435
436        let command_download = std::process::Command::new("/usr/bin/apt-get")
437            .arg("download")
438            .arg(package.name.as_str())
439            .stderr(Stdio::null())
440            .current_dir("/var/tmp/get-cve")
441            .output()
442            .expect("Error on apt-get command running");
443
444        if command_download.status.success() {
445            println!("Decompress {package}");
446            let package_deb = glob::glob(format!("{}*.deb", package.name).as_str());
447            if let Err(_item) = package_deb {
448                restore_current_dir_and_remove_temp_dir(current_dir, "get-cve");
449                write_error_message_and_exit(
450                    "Error, problem on packages into temp \
451                    directory",
452                    None,
453                );
454            } else {
455                let package_name = package_deb.unwrap().next().unwrap();
456                let command_decompress = std::process::Command::new("/usr/bin/dpkg")
457                    .current_dir("/var/tmp/get-cve/")
458                    .arg("-x")
459                    .arg(package_name.unwrap())
460                    .arg(".")
461                    .output()
462                    .expect("Error on decompress command running");
463
464                if !command_decompress.status.success() {
465                    restore_current_dir_and_remove_temp_dir(current_dir_clone, "get-cve");
466                    write_error_message_and_exit(
467                        "Decompression error",
468                        Option::from(
469                            String::from_utf8(command_decompress.stderr)
470                                .unwrap()
471                                .as_str(),
472                        ),
473                    );
474                } else {
475                    println!("Search changelog for {package}");
476                    package.status = PackageStatus::Installed;
477                    return get_debian_changelog_for("/var/tmp/get-cve/usr/share/doc", package);
478                }
479            }
480        }
481        Err("Error on package download")
482    }
483
484    /// Extracts a `Package` instance from the output of a command.
485    ///
486    /// # Arguments
487    ///
488    /// * `command_output` - A `Result` containing the command output as a `String` or
489    ///   a `FromUtf8Error` if the conversion from bytes to a string failed.
490    ///
491    /// # Returns
492    ///
493    /// * A `Package` instance that encapsulates information about a software package,
494    ///   such as the package's name, version, and status.
495    ///
496    /// # Errors
497    ///
498    /// If the `command_output` is an `Err` variant, this function may handle it
499    /// internally, possibly creating a `Package` with a default or error state.
500    fn extract_package_result(command_output: Result<String, FromUtf8Error>) -> Package {
501        let output_string = command_output.unwrap();
502        if output_string.contains("upgradable") {
503            let elements = output_string.split("/").collect::<Vec<&str>>();
504            let version_elements = elements[1].split(' ').collect::<Vec<&str>>();
505
506            Package {
507                name: String::from(elements[0]),
508                version: String::from(version_elements[1].trim()),
509                status: PackageStatus::Uninstalled,
510            }
511        } else {
512            let elements: Vec<&str> = output_string.as_str().split('\t').collect();
513            if elements.len() < 2 {
514                write_error_message_and_exit("Package not found", None);
515                default_package()
516            } else {
517                Package {
518                    name: String::from(elements[0]),
519                    version: String::from(elements[1].trim()),
520                    status: PackageStatus::Installed,
521                }
522            }
523        }
524    }
525
526    /// Executes the `grep` command to search for an exact match of the given version string
527    /// from the standard output of a previously executed command (pipe_package).
528    ///
529    /// # Arguments
530    ///
531    /// * `version` - A string slice that holds the version to search for.
532    /// * `pipe_package` - A `Child` process whose standard output is used as the input for `grep`.
533    ///
534    /// # Returns
535    ///
536    /// * `Output` - The result of executing the `grep` command, containing the standard output,
537    ///   standard error and the exit status of the process.
538    ///
539    /// # Panics
540    ///
541    /// This function will panic if the `grep` command fails to execute or if there is an error
542    /// converting the `pipe_package`'s standard output into the standard input of the `grep` command.
543    ///
544    /// # Notes
545    ///
546    /// The `-w` flag in the `grep` command specifies that the search term must appear as a
547    /// whole word. The `stderr` is silenced by redirecting to `/dev/null`.
548    fn grep_version(version: &str, pipe_package: Child) -> Output {
549        std::process::Command::new("/usr/bin/grep")
550            .arg("-w")
551            .arg(version)
552            .stdin(Stdio::from(pipe_package.stdout.unwrap()))
553            .stderr(Stdio::null())
554            .output()
555            .expect("Error on grep on version")
556    }
557}
558
559trait CvssColor {
560    fn cvss_color(&self) -> (Color, Color);
561}
562
563impl CvssColor for f64 {
564    fn cvss_color(&self) -> (Color, Color) {
565        match *self {
566            0.0 => (Color::Default, Color::Default),
567            value if value > 0.0 && value < 4.0 => {
568                (Color::Rgb(255, 255, 255), Color::Rgb(95, 177, 88))
569            }
570            value if (4.0..7.0).contains(&value) => (Color::Black, Color::Rgb(249, 238, 86)),
571            value if (7.0..9.0).contains(&value) => {
572                (Color::Rgb(255, 255, 255), Color::Rgb(232, 152, 63))
573            }
574            _ => (Color::Rgb(255, 255, 255), Color::Rgb(176, 54, 52)),
575        }
576    }
577}
578
579#[cfg(target_os = "linux")]
580pub fn special_options(args: &[String]) {
581    let mut filter: Option<&String> = None;
582    if let Some(idx_filter) = args.iter().position(|a| a == "--filter") {
583        filter = args.get(idx_filter + 1);
584    }
585
586    let (package_name, mut package_version) = extract_argument(args[0].clone());
587
588    if SupportedOs::supported_os() == OSFamily::Debian && args.iter().any(|a| a == "--next") {
589        package_version = Option::from(String::from("next"));
590    }
591
592    let package = get_package(package_name, package_version);
593    stop_if_invalid_package(&package);
594
595    let changelog_content = get_changelog_for(&package);
596    let changelog_cve_list = filtering_cve(package.cve_list(&changelog_content), filter);
597    if args.iter().any(|a| a == "--long" || a == "-L") {
598        if check_nvd_api_key().is_none() {
599            check_nvd_api_key_warning();
600        }
601
602        let mut cve_list = Vec::new();
603        for cve_value in changelog_cve_list {
604            eprint!(
605                "Search CVE list for package: {package}. It will take a while... {}\r",
606                cve_value.reference
607            );
608            let result = execute_query(build_query(ByCve, cve_value.reference.as_str()));
609            if result != Null {
610                let cve = create_cve_from(&result["vulnerabilities"][0]);
611                cve_list.push(cve);
612            }
613        }
614        show_cves(args, &mut cve_list);
615    } else {
616        show_cve_list(changelog_cve_list, package);
617    }
618}
619
620pub fn common_options(args: &mut [String]) {
621    if args.iter().any(|a| a == "version" || a == "v") {
622        show_version(env!("CARGO_PKG_VERSION"));
623        exit(0);
624    }
625
626    #[cfg(not(target_os = "linux"))]
627    if check_nvd_api_key().is_none() {
628        check_nvd_api_key_warning();
629    }
630
631    if args.iter().any(|a| a == "check" || a == "c") {
632        check_mitre_data(Cve::define());
633        exit(0);
634    }
635
636    if args.iter().any(|a| a == "e" || a == "exploited") {
637        let mut cve_list = standard_or_filtered_list(args, extract_cve_list("", ByCveExploited));
638        show_cves(args, &mut cve_list);
639        exit(0);
640    }
641
642    if let Some(cpe_string) = args
643        .iter()
644        .position(|a| a == "for-cpe" || a == "f")
645        .and_then(|i| args.get(i + 1))
646    {
647        let cpe = Cpe::from(cpe_string);
648        let mut result = run_search_cpe(cpe, args);
649        show_cves(args, &mut result);
650
651        exit(0);
652    } else {
653        no_argument_and_exit!();
654    }
655
656    if let Some(new_option) = args.iter().position(|a| a == "new" || a == "n") {
657        let dates_range = build_dates_range(Some(new_option), args);
658        let mut cve_list =
659            standard_or_filtered_list(args, extract_cve_list(dates_range.as_str(), ByCveNew));
660        show_cves(args, &mut cve_list);
661        exit(0);
662    }
663
664    if let Some(updated_option) = args.iter().position(|a| a == "updated" || a == "u") {
665        let dates_range = build_dates_range(Some(updated_option), args);
666        let mut cve_list =
667            standard_or_filtered_list(args, extract_cve_list(dates_range.as_str(), ByCveUpdated));
668        show_cves(args, &mut cve_list);
669        exit(0);
670    }
671
672    if let Some(cwe_option) = args.iter().position(|a| a == "cwe" || a == "w") {
673        run_search_cwe(args, Some(cwe_option));
674        exit(0);
675    }
676
677    if let Some(search_option) = args.iter().position(|a| a == "search" || a == "s") {
678        let result = run_search_cve_string(args, Some(search_option));
679        let mut values = standard_or_filtered_list(args, result);
680        show_cves(args, &mut values);
681        exit(0);
682    }
683
684    let key_cve = args
685        .iter()
686        .position(|a| a.to_ascii_lowercase().contains("cve"));
687    if let Some(pos) = key_cve
688        && pos == 0
689    {
690        run_search_cve(args, key_cve);
691        exit(0);
692    }
693}
694
695fn standard_or_filtered_list(args: &mut [String], cve_list: Vec<Cve>) -> Vec<Cve> {
696    match args.iter().position(|a| a == "--filter") {
697        Some(pos) => filtering_cve(cve_list, args.get(pos + 1)),
698        _ => cve_list,
699    }
700}
701
702fn show_cves(args: &[String], cve_list: &mut [Cve]) {
703    println!();
704    if !args.iter().any(|a| a == "--long" || a == "-L") {
705        show_cve_values(cve_list);
706    } else {
707        if let Some(pos) = args.iter().position(|a| a == "--sort_by")
708            && let Some(criteria) = args.get(pos + 1)
709        {
710            let mut sort_score_by = String::new();
711            if let Some(sorting_criteria) = args.get(pos + 2) {
712                sort_score_by = sorting_criteria.clone();
713            }
714
715            match criteria.as_str() {
716                "score" => match sort_score_by.as_str() {
717                    "v4" => cve_list.sort_by(|a, b| {
718                        b.score_v4
719                            .clone()
720                            .unwrap()
721                            .value()
722                            .total_cmp(&a.score_v4.clone().unwrap().value())
723                    }),
724
725                    _ => cve_list.sort_by(|a, b| {
726                        b.score_v3
727                            .clone()
728                            .unwrap()
729                            .value()
730                            .total_cmp(&a.score_v3.clone().unwrap().value())
731                    }),
732                },
733
734                "description" => cve_list.sort_by(|a, b| {
735                    a.description
736                        .clone()
737                        .unwrap()
738                        .cmp(&b.description.clone().unwrap())
739                }),
740
741                "cpe" => cve_list
742                    .sort_by(|a, b| a.cpe_id.clone().unwrap().cmp(&b.cpe_id.clone().unwrap())),
743                "weaknesses" => cve_list.sort_by(|a, b| {
744                    b.weaknesses
745                        .clone()
746                        .unwrap()
747                        .cmp(&a.weaknesses.clone().unwrap())
748                }),
749
750                _ => (),
751            };
752        }
753
754        if args.iter().any(|a| a == "--to_csv") {
755            show_as_csv(cve_list, args.iter().any(|a| a == "with_headers"));
756        } else {
757            show_as_table(cve_list);
758        }
759    }
760}
761
762fn show_as_csv(cve_list: &mut [Cve], with_headers: bool) {
763    if !cve_list.is_empty() {
764        let mut writer = csv::Writer::from_writer(stdout());
765        if with_headers {
766            writer
767                .write_record([
768                    "cve",
769                    "description",
770                    "score v3",
771                    "level v3",
772                    "score v4",
773                    "level v4",
774                    "cpe",
775                    "weaknesses",
776                ])
777                .expect("Failed to write CSV headers");
778        }
779
780        cve_list.iter_mut().for_each(|cve| {
781            writer
782                .write_record([
783                    cve.reference.clone(),
784                    cve.description.clone().unwrap(),
785                    cve.score_v3.clone().unwrap().value().to_string(),
786                    cve.score_v3.clone().unwrap().label().to_string(),
787                    cve.score_v4.clone().unwrap().value().to_string(),
788                    cve.score_v4.clone().unwrap().label().to_string(),
789                    cve.cpe_id.clone().unwrap(),
790                    cve.weaknesses.clone().unwrap(),
791                ])
792                .expect("Failed to write CSV content");
793        });
794
795        writer.flush().expect("Failed to flush CSV writer");
796    }
797}
798
799fn show_cve_values(cve_list: &[Cve]) {
800    cve_list.iter().for_each(|c| println!("{c}"));
801}
802
803fn run_search_cve_string(args: &mut [String], position: Option<usize>) -> Vec<Cve> {
804    let result = match args.iter().position(|a| a == "--strict" || a == "-S") {
805        Some(pos) => {
806            let values = args.get(pos + 1);
807            if let Some(item) = values {
808                execute_query(build_query(QueryType::ByCveSearchStrict, item.as_str()))
809            } else {
810                write_error_message_and_exit("Missing criteria", None);
811                Null
812            }
813        }
814
815        _ => match args.get(position.unwrap() + 1) {
816            Some(values) => {
817                execute_query(build_query(QueryType::ByCveSearchString, values.as_str()))
818            }
819            _ => {
820                write_error_message_and_exit("Missing criteria", None);
821                Null
822            }
823        },
824    };
825    let mut cves = cve_from_new_query(&result);
826    simplify_cve_list_content(&mut cves);
827    cves
828}
829
830/// Extracts the package name and optional version from a given argument string.
831///
832/// This function takes a string argument which may or may not contain a version
833/// separated by an '=' character. If the argument contains '=', it splits the
834/// argument into two parts: the package name and the package version. Otherwise,
835/// it considers the entire argument as the package name.
836///
837/// # Arguments
838///
839/// * `argument` - A `String` containing the package name and optionally, the version.
840///
841/// # Returns
842///
843/// A tuple containing:
844/// * `String` - The package name.
845/// * `Option<String>` - The optional package version, `None` if the version is not provided.
846///
847#[cfg(target_os = "linux")]
848fn extract_argument(argument: String) -> (String, Option<String>) {
849    let package_name: String;
850    let mut package_version: Option<String> = None;
851
852    if argument.contains("=") {
853        let component: Vec<&str> = argument.split("=").collect();
854        package_name = String::from(component[0]);
855        package_version = Some(String::from(component[1]));
856    } else {
857        package_name = argument.clone();
858    }
859
860    (package_name, package_version)
861}
862
863/// Extracts a list of CVEs (Common Vulnerabilities and Exposures) based on a given criteria and query type.
864///
865/// This function performs iterative queries to gather all relevant CVEs from an API.
866/// It starts by fetching an initial batch of CVEs, and continues fetching subsequent batches until
867/// all CVEs are retrieved. Finally, it simplifies and prints out the retrieved CVEs.
868///
869/// # Arguments
870///
871/// * `criteria` - A string slice containing the criteria used for the CVE query.
872/// * `cve_type` - A variant of the `QueryType` enum specifying the type of CVE query.
873///
874/// # Returns
875///
876///  The list of found CVE.
877///
878/// # Panics
879///
880/// This function will panic if the initial result is null.
881fn extract_cve_list(criteria: &str, cve_type: QueryType) -> Vec<Cve> {
882    let result = execute_query(build_query(cve_type.clone(), criteria));
883    if !result.is_null() && result["resultsPerPage"] != 0 {
884        let mut start_index = result["startIndex"].clone().as_u64().unwrap();
885        let total_results = result["totalResults"].clone().as_u64().unwrap();
886
887        let mut cve_list = cve_from_new_query(&result);
888        start_index += cve_list.len() as u64;
889        while cve_list.len() < (total_results as usize) {
890            let query = format!(
891                "{}&startIndex={}",
892                build_query(cve_type.clone(), criteria).unwrap(),
893                start_index
894            );
895            let result = execute_query(Ok(query));
896            let mut cves = cve_from_new_query(&result);
897            start_index += cves.len() as u64;
898            cve_list.append(&mut cves);
899        }
900
901        simplify_cve_list_content(&mut cve_list);
902        return cve_list;
903    } else {
904        write_error_message_and_exit("No result found", None)
905    }
906
907    Vec::new()
908}
909
910fn show_as_table(cve_list: &[Cve]) {
911    let mut table = AsciiTable::new("CVE list");
912
913    table.set_headers(vec![
914        "CVE",
915        "Description",
916        "Score v3",
917        "Score v4",
918        "CPE",
919        "Weaknesses",
920    ]);
921
922    cve_list.iter().for_each(|cve| {
923        let description = cve.description.clone().unwrap_or(String::from("None"));
924        let cpe_id = cve.cpe_id.clone().unwrap_or(String::from("None"));
925        let weaknesses = cve.weaknesses.clone().unwrap_or(String::from("None"));
926
927        let score_v3 = format_score(cve.score_v3.clone().unwrap());
928        let score_v4 = format_score(cve.score_v4.clone().unwrap());
929
930        table.add_row(vec![
931            CellValue::Str(cve.reference.clone()),
932            CellValue::Str(description.replace("\n", " ").reduce(70)),
933            CellValue::Str(score_v3.to_string()),
934            CellValue::Str(score_v4.to_string()),
935            CellValue::Str(cpe_id.reduce(40)),
936            CellValue::Str(weaknesses.reduce(15)),
937        ])
938    });
939
940    table.render()
941}
942
943fn format_score(cve_score: Score) -> String {
944    let (color_fg, color_bg) = cve_score.value().cvss_color();
945    format!(
946        "{:^18}",
947        format!(
948            " {} - {} ",
949            format!("{0:.1}", cve_score.value()),
950            cve_score.label()
951        )
952    )
953    .fg(color_fg)
954    .bg(color_bg)
955    .to_string()
956}
957
958/// Handles updates for Common Vulnerabilities and Exposures (CVE) based on the given
959/// Common Platform Enumeration (CPE) identifier and command-line arguments.
960///
961/// This function determines the type of update based on the provided arguments and constructs
962/// the necessary queries to retrieve updated CVE information.
963///
964/// # Arguments
965///
966/// * `cpe` - A `Cpe` struct representing the CPE identifier.
967/// * `args` - A slice of strings representing command-line arguments.
968///
969/// # Return
970///
971/// The CVE list corresponding to the CPE definition.
972///
973fn run_search_cpe(cpe: Cpe, args: &mut [String]) -> Vec<Cve> {
974    if let Some(new_date_option) = args.iter().position(|a| a == "--new") {
975        let dates = build_dates_range(Some(new_date_option), args);
976        let query_dates = create_query_dates(ByCveNew, dates.as_str());
977        let cpe_query = build_query(ByCveCpeName, cpe.name().as_str());
978        standard_or_filtered_list(
979            args,
980            extract_cve_list(
981                format!("{}&{}", cpe_query.unwrap(), query_dates).as_str(),
982                ByCveCpeName,
983            ),
984        )
985    } else if let Some(updated_date_option) = args.iter().position(|a| a == "--updated") {
986        let dates = build_dates_range(Some(updated_date_option), args);
987        let query_dates = create_query_dates(ByCveUpdated, dates.as_str());
988        let cpe_query = build_query(ByCveCpeName, cpe.name().as_str());
989        standard_or_filtered_list(
990            args,
991            extract_cve_list(
992                format!("{}&{}", cpe_query.unwrap(), query_dates).as_str(),
993                ByCveCpeName,
994            ),
995        )
996    } else if args.iter().any(|a| a == "--vul" || a == "-v") {
997        standard_or_filtered_list(args, extract_cve_list(cpe.name().as_str(), ByCpeVulnerable))
998    } else {
999        standard_or_filtered_list(args, extract_cve_list(cpe.name().as_str(), ByCveCpeName))
1000    }
1001}
1002
1003/// Executes a search for a specific CVE (Common Vulnerability and Exposure) and processes the results
1004/// based on the provided command-line arguments.
1005///
1006/// This function extracts the value for the CVE identifier from the provided command-line arguments and
1007/// builds a query to fetch the associated CVE data. It supports additional arguments to display specific
1008/// details such as scores, CWE (Common Weakness Enumeration) values, known affected languages, and descriptions.
1009///
1010/// # Arguments
1011///
1012/// * `args` - A mutable reference to a slice containing the command-line argument strings.
1013/// * `key_cve` - An optional index indicating the position of the CVE key within the `args` slice.
1014///
1015/// # Behaviour
1016///
1017/// - If the CVE value is found in the `args`, the query is executed to retrieve the CVE data.
1018/// - If no results are found for the given CVE, an error message is written, and the process exits.
1019/// - The function looks for additional command-line arguments to determine which specific details to display:
1020///   - `--score` to display CVSS scores.
1021///   - `--cwe` to display CWE values.
1022///   - `--lang` to display known affected languages.
1023///   - `--desc` to display a description of the CVE.
1024/// - If no specific detail arguments are provided, the function prints the full CVE data in a pretty JSON format.
1025///
1026fn run_search_cve(args: &mut [String], key_cve: Option<usize>) {
1027    if let Some(cve_value) = args.get(key_cve.unwrap()) {
1028        let result = if cve_value == &String::from("--cpe") {
1029            println!("Searching...");
1030            execute_query(build_query(ByCveCpeName, args[2].as_str()))
1031        } else {
1032            execute_query(build_query(ByCve, cve_value.as_str()))
1033        };
1034
1035        if args.iter().any(|a| a == "--long" || a == "-L") {
1036            let mut cve = vec![create_cve_from(&result["vulnerabilities"][0])];
1037            if args.iter().any(|a| a == "--to_csv") {
1038                show_as_csv(&mut cve, args.iter().any(|a| a == "with_headers"));
1039            } else {
1040                show_as_table(&cve);
1041            }
1042            return;
1043        }
1044
1045        if result["resultsPerPage"] == 0 || result == Null {
1046            write_error_message_and_exit("CVE not found", None);
1047        }
1048
1049        if args.iter().any(|a| a == "--schema" || a == "-S") {
1050            show_schema(&result);
1051            return;
1052        }
1053
1054        println!("{}", header!(cve_value));
1055        if cve_value == &String::from("--cpe") {
1056            cve_from_new_query(&result).iter().for_each(print_cve);
1057            return;
1058        }
1059
1060        let mut cumulative_args = 0;
1061
1062        if args.iter().any(|a| a == "--score" || a == "-s") {
1063            let include_data = args.iter().any(|a| a == "--data" || a == "-D");
1064            if include_data {
1065                cumulative_args += 1;
1066            }
1067            if args.iter().any(|a| a == "v3") {
1068                CveDisplayType::ByScores.show(&result, Some("v3"), Some(include_data));
1069                cumulative_args += 1;
1070            } else if args.iter().any(|a| a == "v4") {
1071                CveDisplayType::ByScores.show(&result, Some("v4"), Some(include_data));
1072                cumulative_args += 1;
1073            } else {
1074                CveDisplayType::ByScores.show(&result, Some("all"), Some(include_data));
1075                cumulative_args += 1;
1076            }
1077        }
1078
1079        if args.iter().any(|a| a == "--cwe" || a == "-w") {
1080            CveDisplayType::ByCwe.show(&result, None, None);
1081            cumulative_args += 1;
1082        }
1083
1084        if args.iter().any(|a| a == "--lang" || a == "-l") {
1085            CveDisplayType::ByLanguages.show(&result, None, None);
1086            cumulative_args += 1;
1087        }
1088
1089        if let Some(key_description) = args.iter().position(|a| a == "--desc" || a == "-d") {
1090            let key_lang = args.get(key_description + 1).map(|lang| lang.as_str());
1091
1092            CveDisplayType::ByDescription.show(&result, key_lang, None);
1093            cumulative_args += 1;
1094        }
1095
1096        if args.iter().any(|a| a == "--doc" || a == "-K") {
1097            CveDisplayType::ByDocumentations.show(&result, None, None);
1098            cumulative_args += 1;
1099        }
1100
1101        if args.iter().any(|a| a == "--cpe" || a == "-f") {
1102            CveDisplayType::ByCpe.show(&result, None, None);
1103            cumulative_args += 1;
1104        }
1105
1106        if cumulative_args == 0 {
1107            CveDisplayType::All.show(&result, Some("all"), Some(true));
1108        }
1109    }
1110}
1111
1112fn show_schema(result: &Value) {
1113    let cve = create_cve_from(&result["vulnerabilities"][0]);
1114
1115    let mut builder = TreeBuilder::new();
1116    let cve_node = builder.node(section!(cve.reference, Color::Green).to_string());
1117
1118    if let Some(cwe_list) = cve.weaknesses {
1119        let cwe_id = parse_id_to_u32(
1120            cwe_list
1121                .replace("CWE-", "")
1122                .replace(", ", FIELD_SEPARATOR_STRING),
1123        );
1124        cwe_id.iter().for_each(|id| {
1125            if let Some(cwe) = find_cwe_by_id(*id) {
1126                let cwe_node = cve_node.node(
1127                    format!("CWE-{} - {}", cwe.id, cwe.name)
1128                        .fg(Color::Red)
1129                        .to_string(),
1130                );
1131
1132                let capec_id = parse_id_to_u32(
1133                    cwe.clone()
1134                        .attacks
1135                        .replace("CAPEC-", "")
1136                        .replace(", ", FIELD_SEPARATOR_STRING),
1137                );
1138                if !capec_id.is_empty() {
1139                    capec_id.iter().for_each(|ca_id| {
1140                        if let Some(capec) = find_capec_by_id(*ca_id) {
1141                            cwe_node.leaf(
1142                                format!("CAPEC-{} - {}", capec.id, capec.name)
1143                                    .fg(Color::DarkCyan)
1144                                    .to_string(),
1145                            );
1146                        }
1147                    });
1148                }
1149                cwe_node.end();
1150            }
1151        });
1152    } else {
1153        write_error_message_and_exit("No schemas found", None);
1154    }
1155
1156    let tree = builder.build();
1157    println!("{}", tree.render_to_string());
1158}
1159
1160fn show_data(result: &Value, level: &str) {
1161    let cvss_data = match level {
1162        "v3" => get_cvss3_data(result),
1163        "v4" => get_cvss4_data(result),
1164        _ => Null,
1165    };
1166
1167    if !cvss_data.is_null() {
1168        let map = cvss_data.as_object().unwrap();
1169        let filtered = map
1170            .iter()
1171            .filter(|(k, _)| **k != "baseScore" && **k != "baseSeverity" && **k != "version");
1172        filtered.for_each(|(k, v)| {
1173            let str_value = match k.to_string().contains("vectorString") {
1174                false => v.to_string().wording().replace("_", " "),
1175                true => v.to_string(),
1176            };
1177            println!(
1178                "{:>7} {}: {}",
1179                "-",
1180                k.wording(),
1181                String::cleaning(str_value)
1182            );
1183        });
1184    } else {
1185        write_error_message("CVSS data not found", Some(level));
1186    }
1187}
1188
1189/// Displays the languages from the JSON result.
1190///
1191/// This function extracts the descriptions section from the JSON result and
1192/// prints out the cleaned language codes. Each language code is cleaned to
1193/// remove any surrounding quotes.
1194///
1195/// # Arguments
1196///
1197/// * `result` - A reference to a `serde_json::Value` that contains the JSON data.
1198///
1199/// # Example
1200///
1201/// ```rust
1202/// use get_cve::show_languages;
1203///
1204/// let json_data = serde_json::json!({
1205///     "vulnerabilities": [
1206///         {
1207///             "cve": {
1208///                 "descriptions": [
1209///                     {"lang": "\"en\""},
1210///                     {"lang": "\"fr\""}
1211///                 ]
1212///             }
1213///         }
1214///     ]
1215/// });
1216///
1217/// show_languages(&json_data);
1218/// // Will show:
1219/// // en
1220/// // fr
1221/// ```
1222pub fn show_languages(result: &Value) {
1223    if let Some(descriptions) = get_descriptions_section(result) {
1224        section_level!(2, "Languages");
1225        for l in descriptions {
1226            print_values(&[l["lang"].clone()]);
1227        }
1228    }
1229}
1230
1231/// Displays the description of a result based on the provided optional language key.
1232///
1233/// This function attempts to extract a description from a JSON `Value`
1234/// based on an optional language key. If a language key is provided, it is used
1235/// to try to fetch the description in that specific language. If no language key
1236/// is provided or if the description for the given language key is not found, it
1237/// defaults to extracting the description without any language specification.
1238///
1239/// The extracted description is then cleaned by removing surrounding quotation marks
1240/// and printed to the console.
1241///
1242/// # Parameters
1243/// - `result`: A reference to a `serde_json::Value` which holds the JSON data.
1244/// - `key_lang`: An optional reference to a `String` which specifies the language key
1245///   for fetching the description.
1246///
1247/// # Examples
1248///
1249/// ```
1250/// use serde_json::json;
1251/// use get_cve::show_description;
1252///
1253/// let data = json!({
1254///     "vulnerabilities": [
1255///         {
1256///             "cve": {
1257///                 "descriptions": [
1258///                     { "lang": "en", "value": "\"This is an English description.\"" },
1259///                     { "lang": "fr", "value": "\"Ceci est une description en français.\"" }
1260///                 ]
1261///             }
1262///         }
1263///     ]
1264/// });
1265///
1266/// show_description(&data, Some(&String::from("en"))); // prints: This is an English description.
1267/// show_description(&data, None); // If no default, it prints the first available description
1268/// ```
1269pub fn show_description(result: &Value, key_lang: Option<&String>) {
1270    section_level!(2, "Description");
1271
1272    let description = if let Some(lang) = key_lang {
1273        extract_description(result, Some(lang.as_str()))
1274    } else {
1275        extract_description(result, None)
1276    };
1277    print_values(&[description]);
1278    println!();
1279}
1280
1281fn show_documentations(result: &Value) {
1282    if let Some(documentations) = get_documentation_section(result) {
1283        section_level!(2, "Documentations");
1284        documentations.iter().for_each(|doc| {
1285            print_values(std::slice::from_ref(doc));
1286            if doc != documentations.last().unwrap() {
1287                println!();
1288            }
1289        });
1290    } else {
1291        write_error_message_and_exit("Documentation not found", None);
1292    }
1293}
1294
1295/// Extracts the description from the given `value`, depending on the provided `lang` option.
1296///
1297/// If a language is specified, the function attempts to find a description matching the given language.
1298/// If no description matches the specified language, or if no language is specified,
1299/// the function falls back to extracting the default description.
1300///
1301/// # Arguments
1302///
1303/// * `value` - A reference to a `Value` type holding the JSON data.
1304/// * `lang` - An `Option` holding a reference to a string slice representing the desired language.
1305///
1306/// # Returns
1307///
1308/// A `Value` containing the extracted description.
1309fn extract_description(value: &Value, lang: Option<&str>) -> Value {
1310    if let Some(lang) = lang
1311        && let Some(languages) = get_descriptions_section(value)
1312    {
1313        for l in languages {
1314            if l["lang"].as_str() == Some(lang) {
1315                return l["value"].clone();
1316            }
1317        }
1318    }
1319    extract_default_description(value)
1320}
1321
1322/// Extracts the default description from the given `value`.
1323///
1324/// It navigates the JSON structure to find and return the default description.
1325///
1326/// # Arguments
1327///
1328/// * `value` - A reference to a `Value` type holding the JSON data.
1329///
1330/// # Returns
1331///
1332/// A `Value` containing the default description.
1333fn extract_default_description(value: &Value) -> Value {
1334    value["vulnerabilities"][0]["cve"]["descriptions"][0]["value"].clone()
1335}
1336
1337/// Retrieves the list of descriptions from the given `value`.
1338///
1339/// It navigates the JSON structure to return a reference to an array of descriptions if it exists.
1340///
1341/// # Arguments
1342///
1343/// * `value` - A reference to a `Value` type holding the JSON data.
1344///
1345/// # Returns
1346///
1347/// An `Option` containing a reference to a `&Vec<Value>` with descriptions or `None` if it does not exist.
1348fn get_descriptions_section(value: &Value) -> Option<&Vec<Value>> {
1349    value["vulnerabilities"][0]["cve"]["descriptions"].as_array()
1350}
1351
1352/// Retrieves the list of documentations from the given `value`.
1353///
1354/// # Arguments
1355/// * `value`- A reference to a `Value` type holding the JSON data.
1356///
1357/// # Returns
1358///
1359/// An `Option` containing a reference to a `&Vec<Value>` with list of documentations or `None` if it does not exist.
1360fn get_documentation_section(value: &Value) -> Option<&Vec<Value>> {
1361    value["vulnerabilities"][0]["cve"]["references"].as_array()
1362}
1363
1364/// Executes a search for CVEs associated with a specified CWE and prints the results.
1365///
1366/// This function extracts a CWE identifier from the `args` array based on the index provided
1367/// by `cwe_option`, builds a query URL using the CWE identifier, executes the query to fetch
1368/// the CVE data, processes the CVE data to remove duplicates and sort it, and then prints
1369/// each CVE entry.
1370///
1371/// # Arguments
1372///
1373/// * `args` - A mutable slice of `String` containing command line arguments. The CWE identifier
1374///   is expected to be at the position indicated by `cwe_option` + 1.
1375/// * `cwe_option` - An `Option` containing the index of the CWE identifier in the `args` array.
1376///   If `None`, the function immediately returns without performing any action.
1377///
1378/// # Example
1379///
1380/// ```rust
1381/// use get_cve::run_search_cwe;
1382///
1383/// let mut args = vec![String::from("program_name"), String::from("CWE-79")];
1384/// run_search_cwe(&mut args, Some(1));
1385/// ```
1386///
1387/// # Panics
1388///
1389/// This function will panic if `cwe_option` does not contain a valid index that points to a
1390/// CWE identifier inside the `args` array.
1391///
1392/// # Details
1393///
1394/// - The function first checks whether `cwe_option` contains a value. If it does, the function
1395///   attempts to retrieve the CWE identifier from the `args` array using
1396///   `args.get(cwe_option.unwrap() + 1)`.
1397/// - A query URL is then built using the retrieved CWE identifier by calling `build_query` with
1398///   `QueryType::Cwe` and the CWE identifier.
1399/// - The query is executed by calling `execute_query`, which returns a `Value` (expected to be a JSON
1400///   response).
1401/// - The `Value` is passed to the `cve_from_cwe` function, which extracts a list of CVE objects.
1402/// - The list is then simplified using `simplify_cve_list_content`, which sorts and removes
1403///   duplicates.
1404/// - Finally, the function prints each CVE entry using an iterator.
1405pub fn run_search_cwe(args: &mut [String], cwe_option: Option<usize>) {
1406    if let Some(cwe_value) = args.get(cwe_option.unwrap() + 1) {
1407        let result = execute_query(build_query(ByCwe, cwe_value.as_str()));
1408        let mut cve_list = standard_or_filtered_list(args, cve_from_cwe(&result));
1409        simplify_cve_list_content(&mut cve_list);
1410        if cve_list.is_empty() {
1411            write_error_message_and_exit("CVE not found", None);
1412        }
1413        show_cves(args, &mut cve_list);
1414    }
1415}
1416
1417enum CveDisplayType {
1418    All,
1419    ByCpe,
1420    ByCwe,
1421    ByData,
1422    ByDescription,
1423    ByDocumentations,
1424    ByLanguages,
1425    ByScores,
1426}
1427
1428impl CveDisplayType {
1429    fn show(&self, value: &Value, argument: Option<&str>, flag: Option<bool>) {
1430        match self {
1431            CveDisplayType::All => {
1432                show_description(value, None);
1433                show_scores(value, argument.unwrap().trim(), flag.unwrap_or(false));
1434                show_documentations(value);
1435                println!();
1436                show_cwe_values(value);
1437                println!();
1438                show_cpe_values(value);
1439            }
1440
1441            CveDisplayType::ByCpe => {
1442                show_cpe_values(value);
1443            }
1444
1445            CveDisplayType::ByCwe => {
1446                show_cwe_values(value);
1447            }
1448
1449            CveDisplayType::ByData => {
1450                show_data(value, argument.unwrap().trim());
1451            }
1452
1453            CveDisplayType::ByDescription => {
1454                show_description(value, None);
1455            }
1456
1457            CveDisplayType::ByDocumentations => {
1458                show_documentations(value);
1459            }
1460
1461            CveDisplayType::ByLanguages => {
1462                show_languages(value);
1463            }
1464
1465            CveDisplayType::ByScores => {
1466                show_scores(value, argument.unwrap().trim(), flag.unwrap_or(false));
1467            }
1468        }
1469    }
1470}
1471
1472fn show_cpe_values(value: &Value) {
1473    section_level!(2, "CPE");
1474    let values = extract_cpe_id_list(value);
1475    if let Some(values) = values {
1476        values.iter().for_each(|v| {
1477            let mut result = v.as_object().unwrap().clone();
1478            result.remove("matchCriteriaId");
1479            print_values(&[Value::Object(result)]);
1480            println!();
1481        })
1482    } else {
1483        println!("None");
1484    }
1485}
1486
1487fn extract_cpe_id_list(value: &Value) -> Option<&Vec<Value>> {
1488    value["vulnerabilities"][0]["cve"]["configurations"][0]["nodes"][0]["cpeMatch"].as_array()
1489}
1490
1491/// Displays the Common Weakness Enumeration (CWE) values extracted from a JSON result.
1492///
1493/// This function extracts CWE values from the given JSON result using the `extract_cwe` function.
1494/// If the extracted values are not empty, it prints the values using the `print_values` function.
1495/// Otherwise, it prints "None".
1496///
1497/// # Arguments
1498///
1499/// * `result` - A reference to a JSON Value from which CWE values should be extracted.
1500///
1501/// # Example
1502///
1503/// ```
1504/// use serde_json::json;
1505/// use get_cve::show_cwe_values;
1506///
1507/// let json_result = json!({
1508///     "vulnerabilities": [
1509///         {
1510///             "cve": {
1511///                 "weaknesses": [
1512///                     {
1513///                         "description": [
1514///                             {
1515///                                 "value": "CWE-89"
1516///                             }
1517///                         ]
1518///                     }
1519///                 ]
1520///             }
1521///         }
1522///     ]
1523/// });
1524///
1525/// show_cwe_values(&json_result);  // Should print "CWE-89"
1526/// ```
1527pub fn show_cwe_values(result: &Value) {
1528    let values = extract_cwe(result);
1529    if !values.is_empty() {
1530        let slice_values: Vec<_> = values
1531            .iter()
1532            .map(|v| String::cleaning(v.to_string()))
1533            .collect();
1534        let mut str_values = slice_values.join(FIELD_SEPARATOR_STRING_LONG);
1535        str_values = str_values.replace("CWE-", FIELD_SEPARATOR_STRING_LONG);
1536        show_weaknesses("weaknesses", str_values);
1537    } else {
1538        println!("None");
1539    }
1540}
1541
1542/// Extracts Common Weakness Enumeration (CWE) values from a given JSON object.
1543///
1544/// This function takes a JSON Value and attempts to parse and extract CWE descriptions
1545/// from it. The CWE descriptions are expected to be nested within the following structure:
1546/// `vulnerabilities -> cve -> weaknesses`.
1547///
1548/// # Arguments
1549///
1550/// * `value` - A reference to a JSON Value from which CWE descriptions should be extracted.
1551///
1552/// # Returns
1553///
1554/// A `Vec<Value>` containing unique CWE description values. If the expected structure is not
1555/// present or if there are no CWE descriptions, an empty vector is returned.
1556fn extract_cwe(value: &Value) -> Vec<Value> {
1557    let mut result: Vec<Value> = vec![];
1558    let binding = value["vulnerabilities"][0]["cve"]["weaknesses"].clone();
1559    let elements_option = binding.as_array();
1560    if let Some(elements) = elements_option {
1561        elements.iter().for_each(|value| {
1562            let vals = value["description"].as_array().unwrap();
1563            vals.iter()
1564                .for_each(|val| result.push(val["value"].clone()))
1565        });
1566    }
1567
1568    result.dedup();
1569
1570    result
1571}
1572
1573/// Displays CVSS (Common Vulnerability Scoring System) scores from the given JSON result.
1574///
1575/// This function prints out the CVSS v3 and, if available, the CVSS v4 base scores and severities.
1576///
1577/// # Arguments
1578///
1579/// * `result` - A reference to a `serde_json::Value` which contains the CVSS data.
1580/// * `level`- "v3" for showing CVSS v3 scoring, "v4" for showing CVSS v4 socring, "all" for all scoring levels
1581///
1582/// # Example
1583///
1584/// ```
1585/// use serde_json::Value;
1586/// use get_cve::show_scores;
1587///
1588/// let json_data = r#"
1589/// {
1590///     "vulnerabilities": [
1591///         {
1592///             "cve": {
1593///                 "metrics": {
1594///                     "cvssMetricV31": [
1595///                         {
1596///                             "cvssData": {
1597///                                 "baseScore": 7.5,
1598///                                 "baseSeverity": "HIGH"
1599///                             }
1600///                         }
1601///                     ],
1602///                     "cvssMetricV40": [
1603///                         {
1604///                             "cvssData": {
1605///                                 "baseScore": 9.8,
1606///                                 "baseSeverity": "CRITICAL"
1607///                             }
1608///                         }
1609///                     ]
1610///                 }
1611///             }
1612///         }
1613///     ]
1614/// }
1615/// "#;
1616///
1617/// let result: Value = serde_json::from_str(json_data).unwrap();
1618/// show_scores(&result, "v3", true);
1619/// ```
1620///
1621/// The expected output for the above example output would be:
1622/// ```plaintext
1623/// Score v3: 7.5 - HIGH
1624/// Score v4: 9.8 - CRITICAL
1625/// ```
1626pub fn show_scores(result: &Value, level: &str, include_data: bool) {
1627    if level != "all" {
1628        let cvss_data = match level {
1629            "v3" => get_cvss3_data(result),
1630            "v4" => get_cvss4_data(result),
1631            _ => Null,
1632        };
1633
1634        section_level!(2, "Scores");
1635        if !cvss_data.is_null() {
1636            let score = cvss_data["baseScore"].as_f64().unwrap();
1637            let (color_fg, color_bg) = score.cvss_color();
1638            println!(
1639                "{:>6} Score {}: {:.1} - {}",
1640                "-",
1641                level,
1642                score,
1643                format!(
1644                    " {} ",
1645                    String::cleaning(cvss_data["baseSeverity"].to_string())
1646                )
1647                .fg(color_fg)
1648                .bg(color_bg),
1649            );
1650            println!();
1651
1652            if include_data {
1653                section_level!(3, "Data");
1654                CveDisplayType::ByData.show(result, Some(level), None);
1655                println!();
1656            }
1657        } else {
1658            write_error_message("Score not found", Some(level));
1659            println!();
1660        }
1661    } else if level == "all" {
1662        show_scores(result, "v3", include_data);
1663        show_scores(result, "v4", include_data);
1664    } else {
1665        write_error_message("Score type not found", Some(level));
1666    }
1667}
1668
1669/// Retrieves the CVSSv4 data from a given JSON value.
1670///
1671/// This function attempts to extract the Common Vulnerability Scoring System (CVSS) version 4 data
1672/// from the nested structure of the provided JSON value. It specifically looks for the CVSS data
1673/// within the `result` JSON structure at the following path:
1674/// `result["vulnerabilities"][0]["cve"]["metrics"]["cvssMetricV40"][0]["cvssData"]`.
1675///
1676/// # Arguments
1677///
1678/// * `result` - A reference to a JSON `Value` from which the CVSSv4 data is to be extracted.
1679///
1680/// # Returns
1681///
1682/// * `Value` - `Value` that's containing the CVSSv4 data if it exists,
1683///   otherwise `Null`.
1684///
1685/// # Dependencies
1686///
1687/// Make sure to include `serde` and `serde_json` in your dependencies.
1688fn get_cvss4_data(result: &Value) -> Value {
1689    get_cvss_scoring(result, "V40")
1690}
1691
1692/// Extracts CVSS v3.1 data from a JSON value.
1693///
1694/// # Arguments
1695///
1696/// * `result` - A reference to a `serde_json::Value` which is expected to have a specific JSON structure.
1697///
1698/// # Returns
1699///
1700/// * A `serde_json::Value` containing the `cvssData` from the first CVSS v3.1 metric.
1701///
1702/// # Panics
1703///
1704/// This function will panic if the expected structure is not present in the `result`.
1705///
1706/// The expected structure within `result`:
1707/// ```json
1708/// {
1709///   "vulnerabilities": [
1710///     {
1711///       "cve": {
1712///         "metrics": {
1713///           "cvssMetricV31": [
1714///             {
1715///               "cvssData": {
1716///                 // The CVSS v3.1 data goes here
1717///               }
1718///             }
1719///           ]
1720///         }
1721///       }
1722///     }
1723///   ]
1724/// }
1725/// ```
1726///
1727///
1728/// # Dependencies
1729///
1730/// Make sure to include `serde` and `serde_json` in your dependencies.
1731/// ```
1732fn get_cvss3_data(result: &Value) -> Value {
1733    get_cvss_scoring(result, "V31")
1734}
1735
1736/// Get cvss data according to the level score type
1737///
1738/// # Arguments
1739/// - `result`- Result cve searching
1740/// - `level` - Scoring level. Await: "V31" for the v 3.1 score, "V40" for the v4.0 score.
1741///
1742/// # Returns
1743///
1744/// The Scoring data
1745fn get_cvss_scoring(result: &Value, level: &str) -> Value {
1746    result["vulnerabilities"][0]["cve"]["metrics"][format!("cvssMetric{}", level)][0]["cvssData"]
1747        .clone()
1748}
1749
1750/// Extracts a list of `Cve` structs from a given JSON `Value`.
1751///
1752/// This function processes a JSON `Value` to extract an array of vulnerabilities,
1753/// then it iterates over this array to create a `Vec<Cve>`. Each `Cve` contains
1754/// a reference string extracted from the respective vulnerability.
1755///
1756/// # Arguments
1757///
1758/// * `values` - A reference to a `Value` representing the JSON structure that
1759///   contains the vulnerabilities.
1760///
1761/// # Returns
1762///
1763/// * `Vec<Cve>` - A vector containing the extracted `Cve` structs. If the
1764///   vulnerabilities array is not present in the JSON `Value`,
1765///   it returns an empty vector.
1766///
1767/// # Example
1768///
1769/// ```rust
1770/// use serde_json::json;
1771/// use get_cve::cve_from_new_query;
1772/// use fenir::package::concepts::Cve;
1773///
1774/// let data = json!({
1775///     "vulnerabilities": [
1776///         {"cve": {"id": "CVE-2023-1234"}},
1777///         {"cve": {"id": "CVE-2023-5678"}}
1778///     ]
1779/// });
1780///
1781/// let cves = cve_from_new_query(&data);
1782/// assert_eq!(cves.len(), 2);
1783/// assert_eq!(cves[0].reference, "CVE-2023-1234");
1784/// assert_eq!(cves[1].reference, "CVE-2023-5678");
1785/// ```
1786pub fn cve_from_new_query(values: &Value) -> Vec<Cve> {
1787    if let Some(vulnerabilities) = values["vulnerabilities"].as_array() {
1788        let mut results: Vec<Cve> = vec![];
1789        vulnerabilities
1790            .iter()
1791            .for_each(|v| results.push(create_cve_from(v)));
1792
1793        results
1794    } else {
1795        vec![]
1796    }
1797}
1798
1799#[cfg(test)]
1800mod tests {
1801    use crate::{extract_cwe, extract_description};
1802    use fenir::cve::{create_cve_from, cve_from_cwe};
1803    use fenir::database::execute_query;
1804    use fenir::query::QueryType::{ByCve, ByCwe};
1805    use fenir::query::build_query;
1806    use serde_json::Value;
1807    use std::thread::sleep;
1808    use std::time::Duration;
1809    use utmt::assert_all;
1810
1811    fn setup() {
1812        let waiting = Duration::from_millis(30);
1813        sleep(waiting);
1814    }
1815
1816    #[test]
1817    fn extract_description_default_lang_valid() {
1818        setup();
1819
1820        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1821
1822        assert_default_description(&result, None);
1823    }
1824
1825    fn assert_default_description(result: &Value, option: Option<&str>) {
1826        assert!(
1827            extract_description(&result, option)
1828                .to_string()
1829                .contains("A security regression (CVE-2006-5051) was discovered ")
1830        );
1831    }
1832
1833    #[test]
1834    fn extract_description_invalid_default() {
1835        setup();
1836
1837        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1838
1839        assert_default_description(&result, Option::from("ff"));
1840    }
1841
1842    #[test]
1843    fn extract_description_es_lang_valid() {
1844        setup();
1845
1846        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1847
1848        assert!(extract_description(&result, Option::from("es")).to_string().contains("Se encontró una condición de ejecución del controlador de señales en el servidor de OpenSSH"));
1849    }
1850
1851    #[test]
1852    fn extract_cwe_valid_cve_non_empty() {
1853        setup();
1854
1855        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1856
1857        assert!(!extract_cwe(&result).is_empty());
1858    }
1859
1860    #[test]
1861    fn create_cve_from_value() {
1862        setup();
1863
1864        let cve = create_cve_from(&execute_query(build_query(ByCve, "CVE-2024-6387")));
1865
1866        assert_all!(
1867            cve.cpe_id.is_some(),
1868            cve.description.is_some(),
1869            cve.score_v3.is_some(),
1870            !cve.reference.is_empty(),
1871            cve.weaknesses.is_some()
1872        );
1873    }
1874
1875    #[test]
1876    fn extract_cwe_invalid_cve_empty() {
1877        setup();
1878
1879        let result = execute_query(build_query(ByCve, "CVE-1970-0000"));
1880
1881        assert!(extract_cwe(&result).is_empty());
1882    }
1883
1884    #[test]
1885    fn cve_from_cwe_result_valid_cwe_not_empty() {
1886        setup();
1887
1888        let result = execute_query(build_query(ByCwe, "CWE-287"));
1889
1890        assert!(!cve_from_cwe(&result).is_empty());
1891    }
1892}