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::{create_cve_from, cve_from_cwe, Cve, Score};
6use fenir::cwe::show_weaknesses;
7use fenir::database::{
8    execute_query, find_capec_by_id, find_cwe_by_id, parse_id_to_u32, MitreDefinition,
9};
10use fenir::facilities::{
11    build_dates_range, print_values, Reduce, FIELD_SEPARATOR_STRING, FIELD_SEPARATOR_STRING_LONG,
12};
13use fenir::facilities::{Cleaning, Uppercase, Wording};
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::{build_query, create_query_dates, QueryType};
21use fenir::{header, 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        changelog_result, default_package, Package, PackageStatus, Repository, Request,
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    }
653
654    if let Some(new_option) = args.iter().position(|a| a == "new" || a == "n") {
655        let dates_range = build_dates_range(Some(new_option), args);
656        let mut cve_list =
657            standard_or_filtered_list(args, extract_cve_list(dates_range.as_str(), ByCveNew));
658        show_cves(args, &mut cve_list);
659        exit(0);
660    }
661
662    if let Some(updated_option) = args.iter().position(|a| a == "updated" || a == "u") {
663        let dates_range = build_dates_range(Some(updated_option), args);
664        let mut cve_list =
665            standard_or_filtered_list(args, extract_cve_list(dates_range.as_str(), ByCveUpdated));
666        show_cves(args, &mut cve_list);
667        exit(0);
668    }
669
670    if let Some(cwe_option) = args.iter().position(|a| a == "cwe" || a == "w") {
671        run_search_cwe(args, Some(cwe_option));
672        exit(0);
673    }
674
675    if let Some(search_option) = args.iter().position(|a| a == "search" || a == "s") {
676        let result = run_search_cve_string(args, Some(search_option));
677        let mut values = standard_or_filtered_list(args, result);
678        show_cves(args, &mut values);
679        exit(0);
680    }
681
682    let key_cve = args
683        .iter()
684        .position(|a| a.to_ascii_lowercase().contains("cve"));
685    if let Some(pos) = key_cve
686        && pos == 0
687    {
688        run_search_cve(args, key_cve);
689        exit(0);
690    }
691}
692
693fn standard_or_filtered_list(args: &mut [String], cve_list: Vec<Cve>) -> Vec<Cve> {
694    match args.iter().position(|a| a == "--filter") {
695        Some(pos) => filtering_cve(cve_list, args.get(pos + 1)),
696        _ => cve_list,
697    }
698}
699
700fn show_cves(args: &[String], cve_list: &mut [Cve]) {
701    println!();
702    if !args.iter().any(|a| a == "--long" || a == "-L") {
703        show_cve_values(cve_list);
704    } else {
705        if let Some(pos) = args.iter().position(|a| a == "--sort_by")
706            && let Some(criteria) = args.get(pos + 1)
707        {
708            let mut sort_score_by = String::new();
709            if let Some(sorting_criteria) = args.get(pos + 2) {
710                sort_score_by = sorting_criteria.clone();
711            }
712
713            match criteria.as_str() {
714                "score" => match sort_score_by.as_str() {
715                    "v4" => cve_list.sort_by(|a, b| {
716                        b.score_v4
717                            .clone()
718                            .unwrap()
719                            .value()
720                            .total_cmp(&a.score_v4.clone().unwrap().value())
721                    }),
722
723                    _ => cve_list.sort_by(|a, b| {
724                        b.score_v3
725                            .clone()
726                            .unwrap()
727                            .value()
728                            .total_cmp(&a.score_v3.clone().unwrap().value())
729                    }),
730                },
731
732                "description" => cve_list.sort_by(|a, b| {
733                    a.description
734                        .clone()
735                        .unwrap()
736                        .cmp(&b.description.clone().unwrap())
737                }),
738
739                "cpe" => cve_list
740                    .sort_by(|a, b| a.cpe_id.clone().unwrap().cmp(&b.cpe_id.clone().unwrap())),
741                "weaknesses" => cve_list.sort_by(|a, b| {
742                    b.weaknesses
743                        .clone()
744                        .unwrap()
745                        .cmp(&a.weaknesses.clone().unwrap())
746                }),
747
748                _ => (),
749            };
750        }
751
752        if args.iter().any(|a| a == "--to_csv") {
753            show_as_csv(cve_list, args.iter().any(|a| a == "with_headers"));
754        } else {
755            show_as_table(cve_list);
756        }
757    }
758}
759
760fn show_as_csv(cve_list: &mut [Cve], with_headers: bool) {
761    if !cve_list.is_empty() {
762        let mut writer = csv::Writer::from_writer(stdout());
763        if with_headers {
764            writer
765                .write_record([
766                    "cve",
767                    "description",
768                    "score v3",
769                    "level v3",
770                    "score v4",
771                    "level v4",
772                    "cpe",
773                    "weaknesses",
774                ])
775                .expect("Failed to write CSV headers");
776        }
777
778        cve_list.iter_mut().for_each(|cve| {
779            writer
780                .write_record([
781                    cve.reference.clone(),
782                    cve.description.clone().unwrap(),
783                    cve.score_v3.clone().unwrap().value().to_string(),
784                    cve.score_v3.clone().unwrap().label().to_string(),
785                    cve.score_v4.clone().unwrap().value().to_string(),
786                    cve.score_v4.clone().unwrap().label().to_string(),
787                    cve.cpe_id.clone().unwrap(),
788                    cve.weaknesses.clone().unwrap(),
789                ])
790                .expect("Failed to write CSV content");
791        });
792
793        writer.flush().expect("Failed to flush CSV writer");
794    }
795}
796
797fn show_cve_values(cve_list: &[Cve]) {
798    cve_list.iter().for_each(|c| println!("{c}"));
799}
800
801fn run_search_cve_string(args: &mut [String], position: Option<usize>) -> Vec<Cve> {
802    let result = match args.iter().position(|a| a == "--strict" || a == "-S") {
803        Some(pos) => {
804            let values = args.get(pos + 1);
805            if let Some(item) = values {
806                execute_query(build_query(QueryType::ByCveSearchStrict, item.as_str()))
807            } else {
808                write_error_message_and_exit("Missing criteria", None);
809                Null
810            }
811        }
812
813        _ => match args.get(position.unwrap() + 1) {
814            Some(values) => {
815                execute_query(build_query(QueryType::ByCveSearchString, values.as_str()))
816            }
817            _ => {
818                write_error_message_and_exit("Missing criteria", None);
819                Null
820            }
821        },
822    };
823    let mut cves = cve_from_new_query(&result);
824    simplify_cve_list_content(&mut cves);
825    cves
826}
827
828/// Extracts the package name and optional version from a given argument string.
829///
830/// This function takes a string argument which may or may not contain a version
831/// separated by an '=' character. If the argument contains '=', it splits the
832/// argument into two parts: the package name and the package version. Otherwise,
833/// it considers the entire argument as the package name.
834///
835/// # Arguments
836///
837/// * `argument` - A `String` containing the package name and optionally, the version.
838///
839/// # Returns
840///
841/// A tuple containing:
842/// * `String` - The package name.
843/// * `Option<String>` - The optional package version, `None` if the version is not provided.
844///
845#[cfg(target_os = "linux")]
846fn extract_argument(argument: String) -> (String, Option<String>) {
847    let package_name: String;
848    let mut package_version: Option<String> = None;
849
850    if argument.contains("=") {
851        let component: Vec<&str> = argument.split("=").collect();
852        package_name = String::from(component[0]);
853        package_version = Some(String::from(component[1]));
854    } else {
855        package_name = argument.clone();
856    }
857
858    (package_name, package_version)
859}
860
861/// Extracts a list of CVEs (Common Vulnerabilities and Exposures) based on a given criteria and query type.
862///
863/// This function performs iterative queries to gather all relevant CVEs from an API.
864/// It starts by fetching an initial batch of CVEs, and continues fetching subsequent batches until
865/// all CVEs are retrieved. Finally, it simplifies and prints out the retrieved CVEs.
866///
867/// # Arguments
868///
869/// * `criteria` - A string slice containing the criteria used for the CVE query.
870/// * `cve_type` - A variant of the `QueryType` enum specifying the type of CVE query.
871///
872/// # Returns
873///
874///  The list of found CVE.
875///
876/// # Panics
877///
878/// This function will panic if the initial result is null.
879fn extract_cve_list(criteria: &str, cve_type: QueryType) -> Vec<Cve> {
880    let result = execute_query(build_query(cve_type.clone(), criteria));
881    if !result.is_null() && result["resultsPerPage"] != 0 {
882        let mut start_index = result["startIndex"].clone().as_u64().unwrap();
883        let total_results = result["totalResults"].clone().as_u64().unwrap();
884
885        let mut cve_list = cve_from_new_query(&result);
886        start_index += cve_list.len() as u64;
887        while cve_list.len() < (total_results as usize) {
888            let query = format!(
889                "{}&startIndex={}",
890                build_query(cve_type.clone(), criteria).unwrap(),
891                start_index
892            );
893            let result = execute_query(Ok(query));
894            let mut cves = cve_from_new_query(&result);
895            start_index += cves.len() as u64;
896            cve_list.append(&mut cves);
897        }
898
899        simplify_cve_list_content(&mut cve_list);
900        return cve_list;
901    } else {
902        write_error_message_and_exit("No result found", None)
903    }
904
905    Vec::new()
906}
907
908fn show_as_table(cve_list: &[Cve]) {
909    let mut table = AsciiTable::new("CVE list");
910
911    table.set_headers(vec![
912        "CVE",
913        "Description",
914        "Score v3",
915        "Score v4",
916        "CPE",
917        "Weaknesses",
918    ]);
919
920    cve_list.iter().for_each(|cve| {
921        let description = cve.description.clone().unwrap_or(String::from("None"));
922        let cpe_id = cve.cpe_id.clone().unwrap_or(String::from("None"));
923        let weaknesses = cve.weaknesses.clone().unwrap_or(String::from("None"));
924
925        let score_v3 = format_score(cve.score_v3.clone().unwrap());
926        let score_v4 = format_score(cve.score_v4.clone().unwrap());
927
928        table.add_row(vec![
929            CellValue::Str(cve.reference.clone()),
930            CellValue::Str(description.replace("\n", " ").reduce(70)),
931            CellValue::Str(score_v3.to_string()),
932            CellValue::Str(score_v4.to_string()),
933            CellValue::Str(cpe_id.reduce(40)),
934            CellValue::Str(weaknesses.reduce(15)),
935        ])
936    });
937
938    table.render()
939}
940
941fn format_score(cve_score: Score) -> String {
942    let (color_fg, color_bg) = cve_score.value().cvss_color();
943    format!(
944        "{:^18}",
945        format!(
946            " {} - {} ",
947            format!("{0:.1}", cve_score.value()),
948            cve_score.label()
949        )
950    )
951    .fg(color_fg)
952    .bg(color_bg)
953    .to_string()
954}
955
956/// Handles updates for Common Vulnerabilities and Exposures (CVE) based on the given
957/// Common Platform Enumeration (CPE) identifier and command-line arguments.
958///
959/// This function determines the type of update based on the provided arguments and constructs
960/// the necessary queries to retrieve updated CVE information.
961///
962/// # Arguments
963///
964/// * `cpe` - A `Cpe` struct representing the CPE identifier.
965/// * `args` - A slice of strings representing command-line arguments.
966///
967/// # Return
968///
969/// The CVE list corresponding to the CPE definition.
970///
971fn run_search_cpe(cpe: Cpe, args: &mut [String]) -> Vec<Cve> {
972    if let Some(new_date_option) = args.iter().position(|a| a == "--new") {
973        let dates = build_dates_range(Some(new_date_option), args);
974        let query_dates = create_query_dates(ByCveNew, dates.as_str());
975        let cpe_query = build_query(ByCveCpeName, cpe.name().as_str());
976        standard_or_filtered_list(
977            args,
978            extract_cve_list(
979                format!("{}&{}", cpe_query.unwrap(), query_dates).as_str(),
980                ByCveCpeName,
981            ),
982        )
983    } else if let Some(updated_date_option) = args.iter().position(|a| a == "--updated") {
984        let dates = build_dates_range(Some(updated_date_option), args);
985        let query_dates = create_query_dates(ByCveUpdated, dates.as_str());
986        let cpe_query = build_query(ByCveCpeName, cpe.name().as_str());
987        standard_or_filtered_list(
988            args,
989            extract_cve_list(
990                format!("{}&{}", cpe_query.unwrap(), query_dates).as_str(),
991                ByCveCpeName,
992            ),
993        )
994    } else if args.iter().any(|a| a == "--vul" || a == "-v") {
995        standard_or_filtered_list(args, extract_cve_list(cpe.name().as_str(), ByCpeVulnerable))
996    } else {
997        standard_or_filtered_list(args, extract_cve_list(cpe.name().as_str(), ByCveCpeName))
998    }
999}
1000
1001/// Executes a search for a specific CVE (Common Vulnerability and Exposure) and processes the results
1002/// based on the provided command-line arguments.
1003///
1004/// This function extracts the value for the CVE identifier from the provided command-line arguments and
1005/// builds a query to fetch the associated CVE data. It supports additional arguments to display specific
1006/// details such as scores, CWE (Common Weakness Enumeration) values, known affected languages, and descriptions.
1007///
1008/// # Arguments
1009///
1010/// * `args` - A mutable reference to a slice containing the command-line argument strings.
1011/// * `key_cve` - An optional index indicating the position of the CVE key within the `args` slice.
1012///
1013/// # Behaviour
1014///
1015/// - If the CVE value is found in the `args`, the query is executed to retrieve the CVE data.
1016/// - If no results are found for the given CVE, an error message is written, and the process exits.
1017/// - The function looks for additional command-line arguments to determine which specific details to display:
1018///   - `--score` to display CVSS scores.
1019///   - `--cwe` to display CWE values.
1020///   - `--lang` to display known affected languages.
1021///   - `--desc` to display a description of the CVE.
1022/// - If no specific detail arguments are provided, the function prints the full CVE data in a pretty JSON format.
1023///
1024fn run_search_cve(args: &mut [String], key_cve: Option<usize>) {
1025    if let Some(cve_value) = args.get(key_cve.unwrap()) {
1026        let result = if cve_value == &String::from("--cpe") {
1027            println!("Searching...");
1028            execute_query(build_query(ByCveCpeName, args[2].as_str()))
1029        } else {
1030            execute_query(build_query(ByCve, cve_value.as_str()))
1031        };
1032
1033        if args.iter().any(|a| a == "--long" || a == "-L") {
1034            let mut cve = vec![create_cve_from(&result["vulnerabilities"][0])];
1035            if args.iter().any(|a| a == "--to_csv") {
1036                show_as_csv(&mut cve, args.iter().any(|a| a == "with_headers"));
1037            } else {
1038                show_as_table(&cve);
1039            }
1040            return;
1041        }
1042
1043        if result["resultsPerPage"] == 0 || result == Null {
1044            write_error_message_and_exit("CVE not found", None);
1045        }
1046
1047        if args.iter().any(|a| a == "--schema" || a == "-S") {
1048            show_schema(&result);
1049            return;
1050        }
1051
1052        println!("{}", header!(cve_value));
1053        if cve_value == &String::from("--cpe") {
1054            cve_from_new_query(&result).iter().for_each(print_cve);
1055            return;
1056        }
1057
1058        let mut cumulative_args = 0;
1059
1060        if args.iter().any(|a| a == "--score" || a == "-s") {
1061            let include_data = args.iter().any(|a| a == "--data" || a == "-D");
1062            if include_data {
1063                cumulative_args += 1;
1064            }
1065            if args.iter().any(|a| a == "v3") {
1066                CveDisplayType::ByScores.show(&result, Some("v3"), Some(include_data));
1067                cumulative_args += 1;
1068            } else if args.iter().any(|a| a == "v4") {
1069                CveDisplayType::ByScores.show(&result, Some("v4"), Some(include_data));
1070                cumulative_args += 1;
1071            } else {
1072                CveDisplayType::ByScores.show(&result, Some("all"), Some(include_data));
1073                cumulative_args += 1;
1074            }
1075        }
1076
1077        if args.iter().any(|a| a == "--cwe" || a == "-w") {
1078            CveDisplayType::ByCwe.show(&result, None, None);
1079            cumulative_args += 1;
1080        }
1081
1082        if args.iter().any(|a| a == "--lang" || a == "-l") {
1083            CveDisplayType::ByLanguages.show(&result, None, None);
1084            cumulative_args += 1;
1085        }
1086
1087        if let Some(key_description) = args.iter().position(|a| a == "--desc" || a == "-d") {
1088            let key_lang = args.get(key_description + 1).map(|lang| lang.as_str());
1089
1090            CveDisplayType::ByDescription.show(&result, key_lang, None);
1091            cumulative_args += 1;
1092        }
1093
1094        if args.iter().any(|a| a == "--doc" || a == "-K") {
1095            CveDisplayType::ByDocumentations.show(&result, None, None);
1096            cumulative_args += 1;
1097        }
1098
1099        if args.iter().any(|a| a == "--cpe" || a == "-f") {
1100            CveDisplayType::ByCpe.show(&result, None, None);
1101            cumulative_args += 1;
1102        }
1103
1104        if cumulative_args == 0 {
1105            CveDisplayType::All.show(&result, Some("all"), Some(true));
1106        }
1107    }
1108}
1109
1110fn show_schema(result: &Value) {
1111    let cve = create_cve_from(&result["vulnerabilities"][0]);
1112
1113    let mut builder = TreeBuilder::new();
1114    let cve_node = builder.node(section!(cve.reference, Color::Green).to_string());
1115
1116    if let Some(cwe_list) = cve.weaknesses {
1117        let cwe_id = parse_id_to_u32(
1118            cwe_list
1119                .replace("CWE-", "")
1120                .replace(", ", FIELD_SEPARATOR_STRING),
1121        );
1122        cwe_id.iter().for_each(|id| {
1123            if let Some(cwe) = find_cwe_by_id(*id) {
1124                let cwe_node = cve_node.node(
1125                    format!("CWE-{} - {}", cwe.id, cwe.name)
1126                        .fg(Color::Red)
1127                        .to_string(),
1128                );
1129
1130                let capec_id = parse_id_to_u32(
1131                    cwe.clone()
1132                        .attacks
1133                        .replace("CAPEC-", "")
1134                        .replace(", ", FIELD_SEPARATOR_STRING),
1135                );
1136                if !capec_id.is_empty() {
1137                    capec_id.iter().for_each(|ca_id| {
1138                        if let Some(capec) = find_capec_by_id(*ca_id) {
1139                            cwe_node.leaf(
1140                                format!("CAPEC-{} - {}", capec.id, capec.name)
1141                                    .fg(Color::DarkCyan)
1142                                    .to_string(),
1143                            );
1144                        }
1145                    });
1146                }
1147                cwe_node.end();
1148            }
1149        });
1150    } else {
1151        write_error_message_and_exit("No schemas found", None);
1152    }
1153
1154    let tree = builder.build();
1155    println!("{}", tree.render_to_string());
1156}
1157
1158fn show_data(result: &Value, level: &str) {
1159    let cvss_data = match level {
1160        "v3" => get_cvss3_data(result),
1161        "v4" => get_cvss4_data(result),
1162        _ => Null,
1163    };
1164
1165    if !cvss_data.is_null() {
1166        let map = cvss_data.as_object().unwrap();
1167        let filtered = map
1168            .iter()
1169            .filter(|(k, _)| **k != "baseScore" && **k != "baseSeverity" && **k != "version");
1170        filtered.for_each(|(k, v)| {
1171            let str_value = match k.to_string().contains("vectorString") {
1172                false => v.to_string().wording().replace("_", " "),
1173                true => v.to_string(),
1174            };
1175            println!(
1176                "{:>7} {}: {}",
1177                "-",
1178                k.wording(),
1179                String::cleaning(str_value)
1180            );
1181        });
1182    } else {
1183        write_error_message("CVSS data not found", Some(level));
1184    }
1185}
1186
1187/// Displays the languages from the JSON result.
1188///
1189/// This function extracts the descriptions section from the JSON result and
1190/// prints out the cleaned language codes. Each language code is cleaned to
1191/// remove any surrounding quotes.
1192///
1193/// # Arguments
1194///
1195/// * `result` - A reference to a `serde_json::Value` that contains the JSON data.
1196///
1197/// # Example
1198///
1199/// ```rust
1200/// use get_cve::show_languages;
1201///
1202/// let json_data = serde_json::json!({
1203///     "vulnerabilities": [
1204///         {
1205///             "cve": {
1206///                 "descriptions": [
1207///                     {"lang": "\"en\""},
1208///                     {"lang": "\"fr\""}
1209///                 ]
1210///             }
1211///         }
1212///     ]
1213/// });
1214///
1215/// show_languages(&json_data);
1216/// // Will show:
1217/// // en
1218/// // fr
1219/// ```
1220pub fn show_languages(result: &Value) {
1221    if let Some(descriptions) = get_descriptions_section(result) {
1222        section_level!(2, "Languages");
1223        for l in descriptions {
1224            print_values(&[l["lang"].clone()]);
1225        }
1226    }
1227}
1228
1229/// Displays the description of a result based on the provided optional language key.
1230///
1231/// This function attempts to extract a description from a JSON `Value`
1232/// based on an optional language key. If a language key is provided, it is used
1233/// to try to fetch the description in that specific language. If no language key
1234/// is provided or if the description for the given language key is not found, it
1235/// defaults to extracting the description without any language specification.
1236///
1237/// The extracted description is then cleaned by removing surrounding quotation marks
1238/// and printed to the console.
1239///
1240/// # Parameters
1241/// - `result`: A reference to a `serde_json::Value` which holds the JSON data.
1242/// - `key_lang`: An optional reference to a `String` which specifies the language key
1243///   for fetching the description.
1244///
1245/// # Examples
1246///
1247/// ```
1248/// use serde_json::json;
1249/// use get_cve::show_description;
1250///
1251/// let data = json!({
1252///     "vulnerabilities": [
1253///         {
1254///             "cve": {
1255///                 "descriptions": [
1256///                     { "lang": "en", "value": "\"This is an English description.\"" },
1257///                     { "lang": "fr", "value": "\"Ceci est une description en français.\"" }
1258///                 ]
1259///             }
1260///         }
1261///     ]
1262/// });
1263///
1264/// show_description(&data, Some(&String::from("en"))); // prints: This is an English description.
1265/// show_description(&data, None); // If no default, it prints the first available description
1266/// ```
1267pub fn show_description(result: &Value, key_lang: Option<&String>) {
1268    section_level!(2, "Description");
1269
1270    let description = if let Some(lang) = key_lang {
1271        extract_description(result, Some(lang.as_str()))
1272    } else {
1273        extract_description(result, None)
1274    };
1275    print_values(&[description]);
1276    println!();
1277}
1278
1279fn show_documentations(result: &Value) {
1280    if let Some(documentations) = get_documentation_section(result) {
1281        section_level!(2, "Documentations");
1282        documentations.iter().for_each(|doc| {
1283            print_values(std::slice::from_ref(doc));
1284            if doc != documentations.last().unwrap() {
1285                println!();
1286            }
1287        });
1288    } else {
1289        write_error_message_and_exit("Documentation not found", None);
1290    }
1291}
1292
1293/// Extracts the description from the given `value`, depending on the provided `lang` option.
1294///
1295/// If a language is specified, the function attempts to find a description matching the given language.
1296/// If no description matches the specified language, or if no language is specified,
1297/// the function falls back to extracting the default description.
1298///
1299/// # Arguments
1300///
1301/// * `value` - A reference to a `Value` type holding the JSON data.
1302/// * `lang` - An `Option` holding a reference to a string slice representing the desired language.
1303///
1304/// # Returns
1305///
1306/// A `Value` containing the extracted description.
1307fn extract_description(value: &Value, lang: Option<&str>) -> Value {
1308    if let Some(lang) = lang
1309        && let Some(languages) = get_descriptions_section(value)
1310    {
1311        for l in languages {
1312            if l["lang"].as_str() == Some(lang) {
1313                return l["value"].clone();
1314            }
1315        }
1316    }
1317    extract_default_description(value)
1318}
1319
1320/// Extracts the default description from the given `value`.
1321///
1322/// It navigates the JSON structure to find and return the default description.
1323///
1324/// # Arguments
1325///
1326/// * `value` - A reference to a `Value` type holding the JSON data.
1327///
1328/// # Returns
1329///
1330/// A `Value` containing the default description.
1331fn extract_default_description(value: &Value) -> Value {
1332    value["vulnerabilities"][0]["cve"]["descriptions"][0]["value"].clone()
1333}
1334
1335/// Retrieves the list of descriptions from the given `value`.
1336///
1337/// It navigates the JSON structure to return a reference to an array of descriptions if it exists.
1338///
1339/// # Arguments
1340///
1341/// * `value` - A reference to a `Value` type holding the JSON data.
1342///
1343/// # Returns
1344///
1345/// An `Option` containing a reference to a `&Vec<Value>` with descriptions or `None` if it does not exist.
1346fn get_descriptions_section(value: &Value) -> Option<&Vec<Value>> {
1347    value["vulnerabilities"][0]["cve"]["descriptions"].as_array()
1348}
1349
1350/// Retrieves the list of documentations from the given `value`.
1351///
1352/// # Arguments
1353/// * `value`- A reference to a `Value` type holding the JSON data.
1354///
1355/// # Returns
1356///
1357/// An `Option` containing a reference to a `&Vec<Value>` with list of documentations or `None` if it does not exist.
1358fn get_documentation_section(value: &Value) -> Option<&Vec<Value>> {
1359    value["vulnerabilities"][0]["cve"]["references"].as_array()
1360}
1361
1362/// Executes a search for CVEs associated with a specified CWE and prints the results.
1363///
1364/// This function extracts a CWE identifier from the `args` array based on the index provided
1365/// by `cwe_option`, builds a query URL using the CWE identifier, executes the query to fetch
1366/// the CVE data, processes the CVE data to remove duplicates and sort it, and then prints
1367/// each CVE entry.
1368///
1369/// # Arguments
1370///
1371/// * `args` - A mutable slice of `String` containing command line arguments. The CWE identifier
1372///   is expected to be at the position indicated by `cwe_option` + 1.
1373/// * `cwe_option` - An `Option` containing the index of the CWE identifier in the `args` array.
1374///   If `None`, the function immediately returns without performing any action.
1375///
1376/// # Example
1377///
1378/// ```rust
1379/// use get_cve::run_search_cwe;
1380///
1381/// let mut args = vec![String::from("program_name"), String::from("CWE-79")];
1382/// run_search_cwe(&mut args, Some(1));
1383/// ```
1384///
1385/// # Panics
1386///
1387/// This function will panic if `cwe_option` does not contain a valid index that points to a
1388/// CWE identifier inside the `args` array.
1389///
1390/// # Details
1391///
1392/// - The function first checks whether `cwe_option` contains a value. If it does, the function
1393///   attempts to retrieve the CWE identifier from the `args` array using
1394///   `args.get(cwe_option.unwrap() + 1)`.
1395/// - A query URL is then built using the retrieved CWE identifier by calling `build_query` with
1396///   `QueryType::Cwe` and the CWE identifier.
1397/// - The query is executed by calling `execute_query`, which returns a `Value` (expected to be a JSON
1398///   response).
1399/// - The `Value` is passed to the `cve_from_cwe` function, which extracts a list of CVE objects.
1400/// - The list is then simplified using `simplify_cve_list_content`, which sorts and removes
1401///   duplicates.
1402/// - Finally, the function prints each CVE entry using an iterator.
1403pub fn run_search_cwe(args: &mut [String], cwe_option: Option<usize>) {
1404    if let Some(cwe_value) = args.get(cwe_option.unwrap() + 1) {
1405        let result = execute_query(build_query(ByCwe, cwe_value.as_str()));
1406        let mut cve_list = standard_or_filtered_list(args, cve_from_cwe(&result));
1407        simplify_cve_list_content(&mut cve_list);
1408        if cve_list.is_empty() {
1409            write_error_message_and_exit("CVE not found", None);
1410        }
1411        show_cves(args, &mut cve_list);
1412    }
1413}
1414
1415enum CveDisplayType {
1416    All,
1417    ByCpe,
1418    ByCwe,
1419    ByData,
1420    ByDescription,
1421    ByDocumentations,
1422    ByLanguages,
1423    ByScores,
1424}
1425
1426impl CveDisplayType {
1427    fn show(&self, value: &Value, argument: Option<&str>, flag: Option<bool>) {
1428        match self {
1429            CveDisplayType::All => {
1430                show_description(value, None);
1431                show_scores(value, argument.unwrap().trim(), flag.unwrap_or(false));
1432                show_documentations(value);
1433                println!();
1434                show_cwe_values(value);
1435                println!();
1436                show_cpe_values(value);
1437            }
1438
1439            CveDisplayType::ByCpe => {
1440                show_cpe_values(value);
1441            }
1442
1443            CveDisplayType::ByCwe => {
1444                show_cwe_values(value);
1445            }
1446
1447            CveDisplayType::ByData => {
1448                show_data(value, argument.unwrap().trim());
1449            }
1450
1451            CveDisplayType::ByDescription => {
1452                show_description(value, None);
1453            }
1454
1455            CveDisplayType::ByDocumentations => {
1456                show_documentations(value);
1457            }
1458
1459            CveDisplayType::ByLanguages => {
1460                show_languages(value);
1461            }
1462
1463            CveDisplayType::ByScores => {
1464                show_scores(value, argument.unwrap().trim(), flag.unwrap_or(false));
1465            }
1466        }
1467    }
1468}
1469
1470fn show_cpe_values(value: &Value) {
1471    section_level!(2, "CPE");
1472    let values = extract_cpe_id_list(value);
1473    if let Some(values) = values {
1474        values.iter().for_each(|v| {
1475            let mut result = v.as_object().unwrap().clone();
1476            result.remove("matchCriteriaId");
1477            print_values(&[Value::Object(result)]);
1478            println!();
1479        })
1480    } else {
1481        println!("None");
1482    }
1483}
1484
1485fn extract_cpe_id_list(value: &Value) -> Option<&Vec<Value>> {
1486    value["vulnerabilities"][0]["cve"]["configurations"][0]["nodes"][0]["cpeMatch"].as_array()
1487}
1488
1489/// Displays the Common Weakness Enumeration (CWE) values extracted from a JSON result.
1490///
1491/// This function extracts CWE values from the given JSON result using the `extract_cwe` function.
1492/// If the extracted values are not empty, it prints the values using the `print_values` function.
1493/// Otherwise, it prints "None".
1494///
1495/// # Arguments
1496///
1497/// * `result` - A reference to a JSON Value from which CWE values should be extracted.
1498///
1499/// # Example
1500///
1501/// ```
1502/// use serde_json::json;
1503/// use get_cve::show_cwe_values;
1504///
1505/// let json_result = json!({
1506///     "vulnerabilities": [
1507///         {
1508///             "cve": {
1509///                 "weaknesses": [
1510///                     {
1511///                         "description": [
1512///                             {
1513///                                 "value": "CWE-89"
1514///                             }
1515///                         ]
1516///                     }
1517///                 ]
1518///             }
1519///         }
1520///     ]
1521/// });
1522///
1523/// show_cwe_values(&json_result);  // Should print "CWE-89"
1524/// ```
1525pub fn show_cwe_values(result: &Value) {
1526    let values = extract_cwe(result);
1527    if !values.is_empty() {
1528        let slice_values: Vec<_> = values
1529            .iter()
1530            .map(|v| String::cleaning(v.to_string()))
1531            .collect();
1532        let mut str_values = slice_values.join(FIELD_SEPARATOR_STRING_LONG);
1533        str_values = str_values.replace("CWE-", FIELD_SEPARATOR_STRING_LONG);
1534        show_weaknesses("weaknesses", str_values);
1535    } else {
1536        println!("None");
1537    }
1538}
1539
1540/// Extracts Common Weakness Enumeration (CWE) values from a given JSON object.
1541///
1542/// This function takes a JSON Value and attempts to parse and extract CWE descriptions
1543/// from it. The CWE descriptions are expected to be nested within the following structure:
1544/// `vulnerabilities -> cve -> weaknesses`.
1545///
1546/// # Arguments
1547///
1548/// * `value` - A reference to a JSON Value from which CWE descriptions should be extracted.
1549///
1550/// # Returns
1551///
1552/// A `Vec<Value>` containing unique CWE description values. If the expected structure is not
1553/// present or if there are no CWE descriptions, an empty vector is returned.
1554fn extract_cwe(value: &Value) -> Vec<Value> {
1555    let mut result: Vec<Value> = vec![];
1556    let binding = value["vulnerabilities"][0]["cve"]["weaknesses"].clone();
1557    let elements_option = binding.as_array();
1558    if let Some(elements) = elements_option {
1559        elements.iter().for_each(|value| {
1560            let vals = value["description"].as_array().unwrap();
1561            vals.iter()
1562                .for_each(|val| result.push(val["value"].clone()))
1563        });
1564    }
1565
1566    result.dedup();
1567
1568    result
1569}
1570
1571/// Displays CVSS (Common Vulnerability Scoring System) scores from the given JSON result.
1572///
1573/// This function prints out the CVSS v3 and, if available, the CVSS v4 base scores and severities.
1574///
1575/// # Arguments
1576///
1577/// * `result` - A reference to a `serde_json::Value` which contains the CVSS data.
1578/// * `level`- "v3" for showing CVSS v3 scoring, "v4" for showing CVSS v4 socring, "all" for all scoring levels
1579///
1580/// # Example
1581///
1582/// ```
1583/// use serde_json::Value;
1584/// use get_cve::show_scores;
1585///
1586/// let json_data = r#"
1587/// {
1588///     "vulnerabilities": [
1589///         {
1590///             "cve": {
1591///                 "metrics": {
1592///                     "cvssMetricV31": [
1593///                         {
1594///                             "cvssData": {
1595///                                 "baseScore": 7.5,
1596///                                 "baseSeverity": "HIGH"
1597///                             }
1598///                         }
1599///                     ],
1600///                     "cvssMetricV40": [
1601///                         {
1602///                             "cvssData": {
1603///                                 "baseScore": 9.8,
1604///                                 "baseSeverity": "CRITICAL"
1605///                             }
1606///                         }
1607///                     ]
1608///                 }
1609///             }
1610///         }
1611///     ]
1612/// }
1613/// "#;
1614///
1615/// let result: Value = serde_json::from_str(json_data).unwrap();
1616/// show_scores(&result, "v3", true);
1617/// ```
1618///
1619/// The expected output for the above example output would be:
1620/// ```plaintext
1621/// Score v3: 7.5 - HIGH
1622/// Score v4: 9.8 - CRITICAL
1623/// ```
1624pub fn show_scores(result: &Value, level: &str, include_data: bool) {
1625    if level != "all" {
1626        let cvss_data = match level {
1627            "v3" => get_cvss3_data(result),
1628            "v4" => get_cvss4_data(result),
1629            _ => Null,
1630        };
1631
1632        section_level!(2, "Scores");
1633        if !cvss_data.is_null() {
1634            let score = cvss_data["baseScore"].as_f64().unwrap();
1635            let (color_fg, color_bg) = score.cvss_color();
1636            println!(
1637                "{:>6} Score {}: {:.1} - {}",
1638                "-",
1639                level,
1640                score,
1641                format!(
1642                    " {} ",
1643                    String::cleaning(cvss_data["baseSeverity"].to_string())
1644                )
1645                .fg(color_fg)
1646                .bg(color_bg),
1647            );
1648            println!();
1649
1650            if include_data {
1651                section_level!(3, "Data");
1652                CveDisplayType::ByData.show(result, Some(level), None);
1653                println!();
1654            }
1655        } else {
1656            write_error_message("Score not found", Some(level));
1657            println!();
1658        }
1659    } else if level == "all" {
1660        show_scores(result, "v3", include_data);
1661        show_scores(result, "v4", include_data);
1662    } else {
1663        write_error_message("Score type not found", Some(level));
1664    }
1665}
1666
1667/// Retrieves the CVSSv4 data from a given JSON value.
1668///
1669/// This function attempts to extract the Common Vulnerability Scoring System (CVSS) version 4 data
1670/// from the nested structure of the provided JSON value. It specifically looks for the CVSS data
1671/// within the `result` JSON structure at the following path:
1672/// `result["vulnerabilities"][0]["cve"]["metrics"]["cvssMetricV40"][0]["cvssData"]`.
1673///
1674/// # Arguments
1675///
1676/// * `result` - A reference to a JSON `Value` from which the CVSSv4 data is to be extracted.
1677///
1678/// # Returns
1679///
1680/// * `Value` - `Value` that's containing the CVSSv4 data if it exists,
1681///   otherwise `Null`.
1682///
1683/// # Dependencies
1684///
1685/// Make sure to include `serde` and `serde_json` in your dependencies.
1686fn get_cvss4_data(result: &Value) -> Value {
1687    get_cvss_scoring(result, "V40")
1688}
1689
1690/// Extracts CVSS v3.1 data from a JSON value.
1691///
1692/// # Arguments
1693///
1694/// * `result` - A reference to a `serde_json::Value` which is expected to have a specific JSON structure.
1695///
1696/// # Returns
1697///
1698/// * A `serde_json::Value` containing the `cvssData` from the first CVSS v3.1 metric.
1699///
1700/// # Panics
1701///
1702/// This function will panic if the expected structure is not present in the `result`.
1703///
1704/// The expected structure within `result`:
1705/// ```json
1706/// {
1707///   "vulnerabilities": [
1708///     {
1709///       "cve": {
1710///         "metrics": {
1711///           "cvssMetricV31": [
1712///             {
1713///               "cvssData": {
1714///                 // The CVSS v3.1 data goes here
1715///               }
1716///             }
1717///           ]
1718///         }
1719///       }
1720///     }
1721///   ]
1722/// }
1723/// ```
1724///
1725///
1726/// # Dependencies
1727///
1728/// Make sure to include `serde` and `serde_json` in your dependencies.
1729/// ```
1730fn get_cvss3_data(result: &Value) -> Value {
1731    get_cvss_scoring(result, "V31")
1732}
1733
1734/// Get cvss data according to the level score type
1735///
1736/// # Arguments
1737/// - `result`- Result cve searching
1738/// - `level` - Scoring level. Await: "V31" for the v 3.1 score, "V40" for the v4.0 score.
1739///
1740/// # Returns
1741///
1742/// The Scoring data
1743fn get_cvss_scoring(result: &Value, level: &str) -> Value {
1744    result["vulnerabilities"][0]["cve"]["metrics"][format!("cvssMetric{}", level)][0]["cvssData"]
1745        .clone()
1746}
1747
1748/// Extracts a list of `Cve` structs from a given JSON `Value`.
1749///
1750/// This function processes a JSON `Value` to extract an array of vulnerabilities,
1751/// then it iterates over this array to create a `Vec<Cve>`. Each `Cve` contains
1752/// a reference string extracted from the respective vulnerability.
1753///
1754/// # Arguments
1755///
1756/// * `values` - A reference to a `Value` representing the JSON structure that
1757///   contains the vulnerabilities.
1758///
1759/// # Returns
1760///
1761/// * `Vec<Cve>` - A vector containing the extracted `Cve` structs. If the
1762///   vulnerabilities array is not present in the JSON `Value`,
1763///   it returns an empty vector.
1764///
1765/// # Example
1766///
1767/// ```rust
1768/// use serde_json::json;
1769/// use get_cve::cve_from_new_query;
1770/// use fenir::package::concepts::Cve;
1771///
1772/// let data = json!({
1773///     "vulnerabilities": [
1774///         {"cve": {"id": "CVE-2023-1234"}},
1775///         {"cve": {"id": "CVE-2023-5678"}}
1776///     ]
1777/// });
1778///
1779/// let cves = cve_from_new_query(&data);
1780/// assert_eq!(cves.len(), 2);
1781/// assert_eq!(cves[0].reference, "CVE-2023-1234");
1782/// assert_eq!(cves[1].reference, "CVE-2023-5678");
1783/// ```
1784pub fn cve_from_new_query(values: &Value) -> Vec<Cve> {
1785    if let Some(vulnerabilities) = values["vulnerabilities"].as_array() {
1786        let mut results: Vec<Cve> = vec![];
1787        vulnerabilities
1788            .iter()
1789            .for_each(|v| results.push(create_cve_from(v)));
1790
1791        results
1792    } else {
1793        vec![]
1794    }
1795}
1796
1797#[cfg(test)]
1798mod tests {
1799    use crate::{extract_cwe, extract_description};
1800    use fenir::cve::{create_cve_from, cve_from_cwe};
1801    use fenir::database::execute_query;
1802    use fenir::query::build_query;
1803    use fenir::query::QueryType::{ByCve, ByCwe};
1804    use serde_json::Value;
1805    use std::thread::sleep;
1806    use std::time::Duration;
1807    use utmt::assert_all;
1808
1809    fn setup() {
1810        let waiting = Duration::from_millis(30);
1811        sleep(waiting);
1812    }
1813
1814    #[test]
1815    fn extract_description_default_lang_valid() {
1816        setup();
1817
1818        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1819
1820        assert_default_description(&result, None);
1821    }
1822
1823    fn assert_default_description(result: &Value, option: Option<&str>) {
1824        assert!(
1825            extract_description(&result, option)
1826                .to_string()
1827                .contains("A security regression (CVE-2006-5051) was discovered ")
1828        );
1829    }
1830
1831    #[test]
1832    fn extract_description_invalid_default() {
1833        setup();
1834
1835        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1836
1837        assert_default_description(&result, Option::from("ff"));
1838    }
1839
1840    #[test]
1841    fn extract_description_es_lang_valid() {
1842        setup();
1843
1844        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1845
1846        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"));
1847    }
1848
1849    #[test]
1850    fn extract_cwe_valid_cve_non_empty() {
1851        setup();
1852
1853        let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1854
1855        assert!(!extract_cwe(&result).is_empty());
1856    }
1857
1858    #[test]
1859    fn create_cve_from_value() {
1860        setup();
1861
1862        let cve = create_cve_from(&execute_query(build_query(ByCve, "CVE-2024-6387")));
1863
1864        assert_all!(
1865            cve.cpe_id.is_some(),
1866            cve.description.is_some(),
1867            cve.score_v3.is_some(),
1868            !cve.reference.is_empty(),
1869            cve.weaknesses.is_some()
1870        );
1871    }
1872
1873    #[test]
1874    fn extract_cwe_invalid_cve_empty() {
1875        setup();
1876
1877        let result = execute_query(build_query(ByCve, "CVE-1970-0000"));
1878
1879        assert!(extract_cwe(&result).is_empty());
1880    }
1881
1882    #[test]
1883    fn cve_from_cwe_result_valid_cwe_not_empty() {
1884        setup();
1885
1886        let result = execute_query(build_query(ByCwe, "CWE-287"));
1887
1888        assert!(!cve_from_cwe(&result).is_empty());
1889    }
1890}