diff --git a/rust/Cargo.lock b/rust/Cargo.lock index ad26e6432..40134830a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2105,6 +2105,17 @@ dependencies = [ "tokio-rustls 0.26.0", ] +[[package]] +name = "nasl-builtin-isotime" +version = "0.1.0" +dependencies = [ + "chrono", + "nasl-builtin-utils", + "nasl-function-proc-macro", + "nasl-interpreter", + "nasl-syntax", +] + [[package]] name = "nasl-builtin-knowledge-base" version = "0.1.0" @@ -2199,6 +2210,7 @@ dependencies = [ "nasl-builtin-description", "nasl-builtin-host", "nasl-builtin-http", + "nasl-builtin-isotime", "nasl-builtin-knowledge-base", "nasl-builtin-misc", "nasl-builtin-network", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9c3fea5fd..6aff1f507 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -4,6 +4,7 @@ members = [ "nasl-builtin-knowledge-base", "nasl-builtin-raw-ip", "nasl-builtin-cryptographic", + "nasl-builtin-isotime", "nasl-builtin-ssh", "nasl-builtin-http", "nasl-builtin-host", diff --git a/rust/nasl-builtin-isotime/Cargo.toml b/rust/nasl-builtin-isotime/Cargo.toml new file mode 100644 index 000000000..2fca2cc99 --- /dev/null +++ b/rust/nasl-builtin-isotime/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "nasl-builtin-isotime" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nasl-builtin-utils = { path = "../nasl-builtin-utils" } +nasl-function-proc-macro = { path = "../nasl-function-proc-macro" } +nasl-syntax = { path = "../nasl-syntax" } + +chrono = "0.4.23" + +[dev-dependencies] +nasl-interpreter = { path = "../nasl-interpreter" } diff --git a/rust/nasl-builtin-isotime/README.md b/rust/nasl-builtin-isotime/README.md new file mode 100644 index 000000000..9558df1db --- /dev/null +++ b/rust/nasl-builtin-isotime/README.md @@ -0,0 +1,7 @@ +## Implements + +- isotime_add +- isotime_is_valid +- isotime_now +- isotime_print +- isotime_scan diff --git a/rust/nasl-builtin-isotime/src/lib.rs b/rust/nasl-builtin-isotime/src/lib.rs new file mode 100644 index 000000000..a5d77a536 --- /dev/null +++ b/rust/nasl-builtin-isotime/src/lib.rs @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2023 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception + +//! Defines NASL functions regarding isotime. + +use chrono::{Datelike, Months, NaiveDate, NaiveDateTime, TimeDelta}; +use nasl_builtin_utils::{function_set, FunctionErrorKind}; +use nasl_function_proc_macro::nasl_function; + +const ISOFORMAT: &str = "yyyymmddThhmmss"; +const READABLEFORMAT: &str = "yyyy-mm-dd hh:mm:ss"; + +fn parse_isotime(time: &str) -> Option { + NaiveDateTime::parse_from_str(time, "%Y%m%dT%H%M%S").ok() +} + +fn parse_readable_time(time: &str) -> Option { + if let Ok(time) = NaiveDateTime::parse_from_str(time, "%Y-%m-%d %H:%M:%S") { + return Some(time); + } + if let Ok(time) = NaiveDateTime::parse_from_str(time, "%Y-%m-%d %H:%M") { + return Some(time); + } + if let Some((date, hours)) = time.split_once(" ") { + if let Ok(date) = NaiveDate::parse_from_str(date, "%Y-%m-%d") { + if let Ok(hours) = hours.parse::() { + if let Some(time) = date.and_hms_opt(hours, 0, 0) { + return Some(time); + } + } + } + } + if let Ok(date) = NaiveDate::parse_from_str(time, "%Y-%m-%d") { + // Cannot fail, since we add no time to the date + return Some(date.and_hms_opt(0, 0, 0).unwrap()); + } + + None +} + +fn parse_time(time: &str) -> Result { + if let Some(time) = parse_isotime(time) { + return Ok(time); + } + if let Some(time) = parse_readable_time(time) { + return Ok(time); + } + Err(FunctionErrorKind::Diagnostic( + format!( + "The given time is not in the correct isotime ({}) or readable time format ({}): {}", + ISOFORMAT, READABLEFORMAT, time + ), + None, + )) +} + +#[nasl_function(named(years, days, seconds))] +fn isotime_add( + time: &str, + years: Option, + days: Option, + seconds: Option, +) -> Result { + let mut time = parse_time(time)?; + + if let Some(years) = years { + if years < 0 { + time = time - Months::new((-years) as u32 * 12); + } else { + time = time + Months::new(years as u32 * 12); + } + } + + if let Some(days) = days { + time += TimeDelta::days(days); + } + + if let Some(seconds) = seconds { + time += TimeDelta::seconds(seconds); + } + + if time.year() < 0 || time.year() > 9999 { + return Err(FunctionErrorKind::Diagnostic( + format!( + "The resulting year is out of range (0000-9999): {}.", + time.year() + ), + None, + )); + } + + Ok(time.format("%Y%m%dT%H%M%S").to_string()) +} + +#[nasl_function] +fn isotime_is_valid(time: &str) -> bool { + parse_time(time).is_ok() +} + +#[nasl_function] +fn isotime_now() -> String { + chrono::Utc::now().format("%Y%m%dT%H%M%S").to_string() +} + +#[nasl_function] +fn isotime_print(time: &str) -> Result { + Ok(parse_time(time)?.format("%Y-%m-%d %H:%M:%S").to_string()) +} + +#[nasl_function] +fn isotime_scan(time: &str) -> Result { + let time = parse_time(time)?; + + Ok(time.format("%Y%m%dT%H%M%S").to_string()) +} + +pub struct NaslIsotime; + +function_set! { + NaslIsotime, + sync_stateless, + ( + isotime_add, + isotime_is_valid, + isotime_now, + isotime_print, + isotime_scan + ) +} diff --git a/rust/nasl-builtin-isotime/tests/isotime.rs b/rust/nasl-builtin-isotime/tests/isotime.rs new file mode 100644 index 000000000..3e0eeec5b --- /dev/null +++ b/rust/nasl-builtin-isotime/tests/isotime.rs @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2023 Greenbone AG +// +// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception +#[cfg(test)] +mod tests { + use nasl_interpreter::{check_err_matches, test_utils::check_code_result, FunctionErrorKind}; + + #[test] + fn isotime_is_valid() { + check_code_result("isotime_is_valid(\"\");", false); + check_code_result("isotime_is_valid(\"a8691002T123456\");", false); + check_code_result("isotime_is_valid(\"18691002T123456\");", true); + check_code_result("isotime_is_valid(\"18691002T1234\");", false); + check_code_result("isotime_is_valid(\"18691002T1234512\");", false); + check_code_result("isotime_is_valid(\"1869-10-02 12:34:56\");", true); + check_code_result("isotime_is_valid(\"1869-10-02 12:34\");", true); + check_code_result("isotime_is_valid(\"1869-10-02 12\");", true); + check_code_result("isotime_is_valid(\"1869-10-02\");", true); + check_code_result("isotime_is_valid(\"1869-10-02T12:34:56\");", false); + } + + #[test] + fn isotime_scan() { + check_err_matches!("isotime_scan(\"\");", FunctionErrorKind::Diagnostic { .. }); + check_err_matches!( + "isotime_scan(\"a8691002T123456\");", + FunctionErrorKind::Diagnostic { .. } + ); + check_err_matches!( + "isotime_scan(\"18691002T1234\");", + FunctionErrorKind::Diagnostic { .. } + ); + check_err_matches!( + "isotime_scan(\"18691002T1234512\");", + FunctionErrorKind::Diagnostic { .. } + ); + check_err_matches!( + "isotime_scan(\"1869-10-02T12:34:56\");", + FunctionErrorKind::Diagnostic { .. } + ); + + check_code_result("isotime_scan(\"18691002T123456\");", "18691002T123456"); + check_code_result("isotime_scan(\"1869-10-02 12:34:56\");", "18691002T123456"); + check_code_result("isotime_scan(\"1869-10-02 12:34\");", "18691002T123400"); + check_code_result("isotime_scan(\"1869-10-02 12\");", "18691002T120000"); + } + + #[test] + fn isotime_print() { + check_err_matches!("isotime_print(\"\");", FunctionErrorKind::Diagnostic { .. }); + check_err_matches!( + "isotime_print(\"a8691002T123456\");", + FunctionErrorKind::Diagnostic { .. } + ); + check_err_matches!( + "isotime_print(\"18691002T1234\");", + FunctionErrorKind::Diagnostic { .. } + ); + check_err_matches!( + "isotime_print(\"1869-10-02T12:34:56\");", + FunctionErrorKind::Diagnostic { .. } + ); + + check_code_result("isotime_print(\"18691002T123456\");", "1869-10-02 12:34:56"); + check_code_result("isotime_print(\"18691002T123451\");", "1869-10-02 12:34:51"); + check_code_result( + "isotime_print(\"1869-10-02 12:34:56\");", + "1869-10-02 12:34:56", + ); + check_code_result( + "isotime_print(\"1869-10-02 12:34\");", + "1869-10-02 12:34:00", + ); + check_code_result("isotime_print(\"1869-10-02 12\");", "1869-10-02 12:00:00"); + } + + #[test] + fn isotime_add() { + check_err_matches!( + "isotime_add(\"\", years: 0);", + FunctionErrorKind::Diagnostic { .. } + ); + check_err_matches!( + "isotime_add(\"50001002T120000\", years: 5000);", + FunctionErrorKind::Diagnostic { .. } + ); + check_err_matches!( + "isotime_add(\"50001002T120000\", years: -5001);", + FunctionErrorKind::Diagnostic { .. } + ); + + check_code_result( + "isotime_add(\"20240228T000000\", days: 1);", + "20240229T000000", + ); + check_code_result( + "isotime_add(\"20240228T000000\", years: 1);", + "20250228T000000", + ); + check_code_result( + "isotime_add(\"20240228T000000\", seconds: 1);", + "20240228T000001", + ); + check_code_result( + "isotime_add(\"20240228T000000\", days: -1);", + "20240227T000000", + ); + check_code_result( + "isotime_add(\"20240228T000000\", years: -1);", + "20230228T000000", + ); + check_code_result( + "isotime_add(\"20240228T000000\", seconds: -1);", + "20240227T235959", + ); + check_code_result( + "isotime_add(\"20240228T000000\", years: 1, days: -1, seconds: -1);", + "20250226T235959", + ); + } +} diff --git a/rust/nasl-builtin-std/Cargo.toml b/rust/nasl-builtin-std/Cargo.toml index 79fd7ade0..ede35f31b 100644 --- a/rust/nasl-builtin-std/Cargo.toml +++ b/rust/nasl-builtin-std/Cargo.toml @@ -16,6 +16,7 @@ nasl-builtin-description = { path = "../nasl-builtin-description" } nasl-builtin-network = { path = "../nasl-builtin-network" } nasl-builtin-misc = { path = "../nasl-builtin-misc" } nasl-builtin-regex = { path = "../nasl-builtin-regex" } +nasl-builtin-isotime = { path = "../nasl-builtin-isotime" } nasl-function-proc-macro = { path = "../nasl-function-proc-macro" } storage = { path = "../storage" } models = { path = "../models" } diff --git a/rust/nasl-builtin-std/src/lib.rs b/rust/nasl-builtin-std/src/lib.rs index a0e81f9d3..40eb7e43d 100644 --- a/rust/nasl-builtin-std/src/lib.rs +++ b/rust/nasl-builtin-std/src/lib.rs @@ -30,7 +30,8 @@ pub fn nasl_std_functions() -> Executor { .add_set(nasl_builtin_network::network::Network) .add_set(nasl_builtin_regex::RegularExpressions) .add_set(nasl_builtin_cryptographic::Cryptographic) - .add_set(nasl_builtin_description::Description); + .add_set(nasl_builtin_description::Description) + .add_set(nasl_builtin_isotime::NaslIsotime); #[cfg(feature = "nasl-builtin-ssh")] executor.add_set(nasl_builtin_ssh::Ssh::default());