Skip to main content

get_cpe/
lib.rs

1use fenir::cpe::{create_cpe_from_type, Cpe, CPE_ALL_LABEL, CPE_OS_LABEL, CPE_SOFT_LABEL};
2use fenir::database::execute_query;
3use fenir::facilities::Cleaning;
4use fenir::facilities::Uppercase;
5use fenir::package::errors::write_error_message_and_exit;
6use fenir::query::QueryType::ByCpeMatch;
7use fenir::query::{build_query, QueryType};
8use fenir::{header, section, section_level};
9use serde_json::Value;
10use termint::enums::Color;
11use termint::enums::Modifier;
12use termint::widgets::ToSpan;
13
14/// Show an explanation details of a CPE according to the cpe string given as argument.
15///
16/// # Arguments
17/// * `cpe_str_id` - cpe string to for explaining
18///
19/// # Example
20///
21/// ```rust
22/// use get_cpe::explain_cpe;
23///
24/// explain_cpe("cpe:2.3:a:lostvip:ruoyi-go:*:*:*:*:*:*:*:*");
25/// ```
26pub fn explain_cpe(cpe_str_id: &str) {
27    let cpe = Cpe::from(cpe_str_id);
28    println!("{}", header!("CPE"));
29    section_level!(2, "Explanation");
30    cpe.explains();
31}
32
33/// Search the CPE that are corresponding to the given searching arguments. These arguments are embedded
34/// into a string vectors with the following sample form type: `vec!["vmware".to_string(), "broadcom".to_string()]`.
35/// This search method is corresponding to the CPE NVD REST API by searching by keyword. Each argument is considering has
36/// with a `and` operator.
37///
38/// # Argument
39/// * `criteria`- Searching criteria
40/// * `mode`- QueryType to applied for the searching.
41///
42/// # Returns
43/// The list of CPE found. An empty list if not found.
44///
45/// # Example
46///
47/// ```rust
48/// use fenir::query::QueryType::ByCpeSearchWords;
49/// use get_cpe::{search_with_keywords, show_cpe_values};
50///
51/// let args = vec!["vmware".to_string(), "broadcom".to_string()];
52/// let result = search_with_keywords(args,ByCpeSearchWords);
53///
54/// show_cpe_values(&result);
55/// ```
56pub fn search_with_keywords(criteria: Vec<String>, mode: QueryType) -> Vec<Cpe> {
57    let words = criteria.join(" ");
58    let result = execute_query(build_query(mode, words.as_str()));
59    collect_result(result)
60}
61
62/// Error message on cpe type
63const INVALID_APPS_TYPE_MSG: &str = "Invalid apps type";
64
65/// Error message on while a CPE not matched given arguments
66const CPE_NOT_MATCHED_MSG: &str = "CPE not matched";
67
68/// Create the CPE from the list of arguments.
69///
70/// # Arguments
71///
72/// * `args` - A vector of strings representing the command-line arguments.
73///
74/// # Process
75///
76/// 1. Identifies the type of CPE to create (application or operating system) based on the `--type` argument.
77/// 2. Validates the specified CPE type.
78/// 3. Creates a CPE instance of the specified type.
79/// 4. Applies additional CPE options (like vendor, product, version, update, and edition) from other arguments.
80///
81/// # Returns
82/// The cpe identification string.
83///
84/// # Errors
85///
86/// If the `--type` argument is missing, the function writes an error message and exits.
87/// If the CPE type provided with `--type` is invalid, it writes an error message and exits.
88///
89/// # Example
90///
91/// ```rust
92/// use get_cpe::create_cpe;
93///
94/// let args = vec![
95///  "--type".to_string(),
96///  "soft".to_string(),
97///  "--vendor".to_string(),
98///  " microsoft".to_string(),
99///  " --product".to_string(),
100///  "office".to_string(),
101///  "--version".to_string(),
102///  "2013".to_string(),
103///  "--update sp1".to_string(),
104///  "--update".to_string(),
105///  "sp2".to_string(),
106///  ];
107///
108/// let _ = create_cpe(args);
109/// ```
110pub fn create_cpe(args: Vec<String>) -> Cpe {
111    let type_option = args
112        .iter()
113        .position(|a| a == "--type" || a == "-t")
114        .and_then(|pos| args.get(pos + 1));
115
116    let effective_type = match type_option {
117        Some(value) => value.as_str(),
118        _ => "*",
119    };
120
121    validate_apps_type(effective_type);
122
123    let mut cpe = create_cpe_from_type(effective_type);
124    apply_cpe_options(&mut cpe, &args);
125    cpe
126}
127
128/// Validates the type of application specified by the `apps_type` argument.
129///
130/// This function checks whether the given `apps_type` is either "os" or "soft".
131/// If the `apps_type` is invalid, it writes an error message and exits the process
132/// with a status code of 1.
133///
134/// # Arguments
135///
136/// * `apps_type` - A string slice representing the type of application to be validated.
137///
138/// # Example
139///
140/// `
141/// validate_apps_type("os"); // Valid apps type
142///
143/// validate_apps_type("soft"); // Valid apps type
144///
145/// validate_apps_type("unknown"); // Invalid, will write error message and exit
146/// `
147///
148/// # Behavior
149///
150/// - If `apps_type` is "os" or "soft", the function does nothing.
151/// - If `apps_type` is neither "os" nor "soft", the function writes an error message
152///   "Invalid apps type" followed by the invalid `apps_type`, and then exits the process.
153///
154fn validate_apps_type(apps_type: &str) {
155    if ![CPE_ALL_LABEL, CPE_SOFT_LABEL, CPE_OS_LABEL].contains(&apps_type) {
156        write_error_message_and_exit(INVALID_APPS_TYPE_MSG, Some(apps_type));
157    }
158}
159
160/// Applies various CPE (Common Platform Enumeration) options to the given `Cpe` instance.
161///
162/// This function updates the fields of the provided `Cpe` instance based on the command-line arguments
163/// passed within `args`. It retrieves the values for specific CPE options (e.g., `--vendor`, `--product`,
164/// `--version`, `--update`, `--edition`) and assigns them to the corresponding fields of the `Cpe` struct.
165///
166/// # Arguments
167///
168/// * `cpe` - A mutable reference to a `Cpe` instance that will be updated with the option values.
169/// * `args` - A slice of `String`s representing the command-line arguments. This slice is used to fetch
170///   the values for the CPE options.
171///
172/// # Implementation Details
173///
174/// The function internally calls `get_cpe_option_value` to extract each option's value from the command-line arguments.
175/// The extracted values are then set to the corresponding fields of the `Cpe` struct using its methods.
176fn apply_cpe_options(cpe: &mut Cpe, args: &[String]) {
177    cpe.vendor(&get_cpe_option_value("--vendor", "-V", args));
178    cpe.product(&get_cpe_option_value("--product", "-p", args));
179    cpe.version(&get_cpe_option_value("--version", "-v", args));
180    cpe.update(&get_cpe_option_value("--update", "-u", args));
181    cpe.edition(&get_cpe_option_value("--edition", "-e", args));
182    cpe.language(&get_cpe_option_value("--lang", "-l", args));
183    cpe.sw_edition(&get_cpe_option_value("--sw-edition", "-s", args));
184    cpe.target_hw(&get_cpe_option_value("--target-hw", "-h", args));
185    cpe.target_sw(&get_cpe_option_value("--target-sw", "-S", args));
186    cpe.other(&get_cpe_option_value("--other", "-o", args));
187}
188
189/// Matches a given Common Platform Enumeration (CPE) identifier against a query result
190/// and processes the matched products list.
191///
192/// This function builds a query based on the specified CPE identifier using the `CpeMatch`
193/// query type and executes this query. If no results are found (the result is `null`),
194/// an error message is written and the process exits. If products are found, they are cleaned,
195/// parsed, deduplicated, and described.
196///
197/// # Arguments
198///
199/// * `cpe` - An instance of `Cpe` representing the CPE identifier to match.
200///
201/// # Panics
202///
203/// This function will exit the process with a status code of `1` if no matching results are found.
204///
205/// # Examples
206///
207/// ```rust
208/// use fenir::cpe::Cpe;
209/// use get_cpe::{match_cpe, show_cpe_values};
210///
211/// let cpe_instance = Cpe::from("cpe:2.3:a:adobe:commerce:*:*:*:*:*:*:*:*");
212///
213/// let result = match_cpe(cpe_instance);
214/// show_cpe_values(&result);
215/// ```
216pub fn match_cpe(cpe: Cpe) -> Vec<Cpe> {
217    let result = execute_query(build_query(ByCpeMatch, cpe.name().as_str()));
218    if result.is_null() {
219        return vec![];
220    }
221
222    collect_result(result)
223}
224
225fn collect_result(result: Value) -> Vec<Cpe> {
226    if let Some(products_list) = result["products"].as_array() {
227        let mut cpe_list: Vec<Cpe> = products_list
228            .iter()
229            .map(|product| {
230                let raw = String::cleaning(product["cpe"]["cpeName"].to_string());
231                Cpe::from(raw.as_str())
232            })
233            .collect();
234        cpe_list.dedup();
235        cpe_list
236    } else {
237        Vec::new()
238    }
239}
240
241/// Shows the values for a list of cpe.
242///
243/// # Argument
244///
245/// * `cpe_list`- List of CPE
246///
247/// # Example
248///
249/// ```rust
250/// use fenir::cpe::Cpe;
251/// use get_cpe::{match_cpe, show_cpe_values};
252///
253/// let cpe = Cpe::from("cpe:2.3:a:adobe:commerce:*:*:*:*:*:*:*:*");
254///
255/// show_cpe_values(&match_cpe(cpe));
256/// ```
257pub fn show_cpe_values(cpe_list: &[Cpe]) {
258    if cpe_list.is_empty() {
259        write_error_message_and_exit(CPE_NOT_MATCHED_MSG, None);
260    }
261
262    cpe_list.iter().for_each(|cpe| {
263        section_level!("CPE");
264        cpe.explains();
265        println!();
266    });
267}
268
269/// Retrieve the value of a command-line option, if present.
270///
271/// This function searches through a list of command-line arguments (`args`)
272/// for a specific argument (`arg`). If the argument is found, it retrieves
273/// the value immediately following it in the list. If the argument or the
274/// subsequent value is not found, it returns a default value of `*`.
275///
276/// # Arguments
277///
278/// * `name` - A string slice that holds the name of the argument to search for.
279/// * `short_arg`- Short string argument.
280/// * `args` - A slice of `String` that holds the list of command-line arguments.
281///
282/// # Returns
283///
284/// * `String` - The value of the specified argument if found, otherwise `*`.
285fn get_cpe_option_value(name: &str, short_arg: &str, args: &[String]) -> String {
286    if let Some(option) = args.iter().position(|a| a == name || a == short_arg) {
287        if let Some(option_value) = args.get(option + 1) {
288            if option_value.len() > 1 && option_value.starts_with('-') {
289                eprintln!("Invalid option value for: {}. Replace it by: '*'.", name);
290                String::from("*")
291            } else {
292                option_value.to_string()
293            }
294        } else {
295            String::from("*")
296        }
297    } else {
298        String::from("*")
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use fenir::cpe::NVD_CPE_LINK;
305    use fenir::cpe::{Cpe, CpeType};
306    use fenir::database::execute_query;
307    use fenir::query::build_query;
308    use fenir::query::QueryType::{ByCpe, ByCpeSearchWords};
309    use serde_json::Value::Null;
310    use std::thread::sleep;
311    use std::time::Duration;
312
313    fn setup() {
314        let waiting = Duration::from_millis(30);
315        sleep(waiting);
316    }
317
318    #[test]
319    fn cpe_new_default() {
320        for t in [
321            CpeType::Application,
322            CpeType::All,
323            CpeType::OperatingSystem,
324            CpeType::Hardware,
325        ] {
326            let result = Cpe::new(t.clone());
327            let ref_value = Cpe {
328                cpe_type: t,
329                vendor: "*".to_string(),
330                product: "*".to_string(),
331                version: "*".to_string(),
332                update: "*".to_string(),
333                edition: "*".to_string(),
334                language: "*".to_string(),
335                sw_edition: "*".to_string(),
336                target_hw: "*".to_string(),
337                target_sw: "*".to_string(),
338                other: "*".to_string(),
339            };
340
341            assert_eq!(ref_value, result);
342        }
343    }
344
345    #[test]
346    fn cpe_new_application_representation() {
347        let result = Cpe::new(CpeType::Application);
348        let representation = format!("{}", result);
349
350        assert_eq!("cpe:2.3:a:*:*:*:*:*:*:*:*:*:*", representation);
351    }
352
353    #[test]
354    fn cpe_new_os_representation() {
355        let result = Cpe::new(CpeType::OperatingSystem);
356        let representation = format!("{}", result);
357
358        assert_eq!("cpe:2.3:o:*:*:*:*:*:*:*:*:*:*", representation);
359    }
360
361    #[test]
362    fn cpe_new_hardware_representation() {
363        let result = Cpe::new(CpeType::Hardware);
364        let representation = format!("{}", result);
365
366        assert_eq!("cpe:2.3:h:*:*:*:*:*:*:*:*:*:*", representation);
367    }
368
369    #[test]
370    fn cpe_new_application_name_valid() {
371        let result = Cpe::new(CpeType::Application);
372
373        assert_eq!("cpe:2.3:a:*:*:*:*:*:*:*:*:*:*", result.name());
374    }
375
376    #[test]
377    fn cpe_os_name_valid() {
378        let result = Cpe::new(CpeType::OperatingSystem);
379
380        assert_eq!("cpe:2.3:o:*:*:*:*:*:*:*:*:*:*", result.name());
381    }
382
383    #[test]
384    fn cpe_vendor_name_valid() {
385        for s in ["microsoft", "MICROSOFT"] {
386            let mut result = Cpe::new(CpeType::OperatingSystem);
387            result.vendor(s);
388
389            assert_eq!("cpe:2.3:o:microsoft:*:*:*:*:*:*:*:*:*", result.name());
390        }
391    }
392
393    #[test]
394    fn cpe_product_name_valid() {
395        for s in ["ntp", "NTP"] {
396            let mut result = Cpe::new(CpeType::Application);
397            result.product(s);
398
399            assert_eq!("cpe:2.3:a:*:ntp:*:*:*:*:*:*:*:*", result.name());
400        }
401    }
402
403    #[test]
404    fn cpe_version_value_valid() {
405        for s in ["2.b5", "2.B5"] {
406            let mut result = Cpe::new(CpeType::Application);
407            result.version(s);
408
409            assert_eq!("cpe:2.3:a:*:*:2.b5:*:*:*:*:*:*:*", result.name());
410        }
411    }
412
413    #[test]
414    fn cpe_update_value_valid() {
415        for s in ["sp2", "SP2"] {
416            let mut result = Cpe::new(CpeType::Application);
417            result.update(s);
418
419            assert_eq!("cpe:2.3:a:*:*:*:sp2:*:*:*:*:*:*", result.name());
420        }
421    }
422
423    #[test]
424    fn cpe_edition_value_valid() {
425        for s in ["beta", "BETA"] {
426            let mut result = Cpe::new(CpeType::Application);
427            result.edition(s);
428
429            assert_eq!("cpe:2.3:a:*:*:*:*:beta:*:*:*:*:*", result.name());
430        }
431    }
432
433    #[test]
434    fn cpe_language_value_valid() {
435        for s in ["alpha", "ALPHA"] {
436            let mut result = Cpe::new(CpeType::Application);
437            result.language(s);
438
439            assert_eq!("cpe:2.3:a:*:*:*:*:*:alpha:*:*:*:*", result.name());
440        }
441    }
442
443    #[test]
444    fn cpe_sw_edition_value_valid() {
445        for s in ["Ed1", "ED1"] {
446            let mut result = Cpe::new(CpeType::Application);
447            result.sw_edition(s);
448
449            assert_eq!("cpe:2.3:a:*:*:*:*:*:*:ed1:*:*:*", result.name());
450        }
451    }
452
453    #[test]
454    fn cpe_target_sw_valid() {
455        for s in ["target_sw", "TARGET_SW"] {
456            let mut result = Cpe::new(CpeType::Application);
457            result.target_sw(s);
458
459            assert_eq!("cpe:2.3:a:*:*:*:*:*:*:*:target_sw:*:*", result.name());
460        }
461    }
462
463    #[test]
464    fn cpe_target_hw_valid() {
465        for s in ["target_hw", "TARGET_HW"] {
466            let mut result = Cpe::new(CpeType::Application);
467            result.target_hw(s);
468
469            assert_eq!("cpe:2.3:a:*:*:*:*:*:*:*:*:target_hw:*", result.name());
470        }
471    }
472
473    #[test]
474    fn cpe_other_valid() {
475        for s in ["other", "OTHER"] {
476            let mut result = Cpe::new(CpeType::Application);
477            result.other(s);
478
479            assert_eq!("cpe:2.3:a:*:*:*:*:*:*:*:*:*:other", result.name());
480        }
481    }
482
483    #[test]
484    fn cpe_query_cpe_name() {
485        setup();
486
487        let mut cpe = Cpe::new(CpeType::Application);
488        cpe.vendor("debian");
489        cpe.product("debian_linux");
490
491        let result = build_query(ByCpe, cpe.name().as_str());
492
493        assert_eq!(format!("{NVD_CPE_LINK}?cpeName={}", cpe), result.unwrap());
494    }
495
496    #[test]
497    fn cpe_query_execute_valid() {
498        setup();
499
500        let cpe = create_cpe_test();
501
502        let result = execute_query(build_query(ByCpe, cpe.name().as_str()));
503
504        assert_ne!(0, result["resultsPerPage"]);
505    }
506
507    fn create_cpe_test() -> Cpe {
508        let mut cpe = Cpe::new(CpeType::OperatingSystem);
509        cpe.vendor("microsoft");
510        cpe.product("windows_10");
511        cpe.version("1607");
512        cpe
513    }
514
515    #[test]
516    fn cpe_query_bad_values_invalid() {
517        setup();
518
519        let mut cpe = Cpe::new(CpeType::Application);
520        cpe.product("foo");
521        cpe.vendor("bar");
522
523        let result = execute_query(build_query(ByCpe, cpe.name().as_str()));
524
525        assert_eq!(Null, result["resultsPerPage"]);
526    }
527
528    #[test]
529    fn cpe_parse_from_string_valid() {
530        let cpe_ref = create_cpe_test();
531        let cpe_result = Cpe::from(cpe_ref.name().as_str());
532
533        assert_eq!(cpe_ref, cpe_result);
534    }
535
536    #[test]
537    fn cpe_parse_from_simple_string_valid() {
538        let mut result = Cpe::new(CpeType::Application);
539        result.edition("BETA");
540        let cpe_result = Cpe::from(result.name().as_str());
541
542        assert_eq!(result, cpe_result);
543    }
544
545    #[test]
546    fn cpe_search_with_one_word_only_valid() {
547        setup();
548
549        let result = execute_query(build_query(ByCpeSearchWords, "Java"));
550
551        assert_ne!(Null, result["resultsPerPage"]);
552    }
553
554    #[test]
555    fn cpe_search_with_multiple_words_valid() {
556        setup();
557
558        let result = execute_query(build_query(ByCpeSearchWords, "Red Hat"));
559
560        assert_ne!(Null, result["resultsPerPage"]);
561    }
562
563    #[test]
564    fn cpe_search_with_invalid_words_not_valid() {
565        setup();
566
567        let result = execute_query(build_query(ByCpeSearchWords, "!!! รงรงรงรง"));
568
569        assert_eq!(Null, result["resultsPerPage"]);
570    }
571}