use crate::csaf::types::csaf_datetime::CsafDateTime;
use crate::csaf::types::csaf_datetime::CsafDateTime::{Invalid, Valid};
use crate::csaf_traits::{
CsafTrait, DocumentTrait, FirstKnownExploitationDatesTrait, TrackingTrait, VulnerabilityTrait, WithDate,
WithOptionalDate,
};
use crate::validation::ValidationError;
use regex::Regex;
use std::sync::LazyLock;
static CSAF_RFC3339_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^((\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:(?:[0-4]\d|5[0-9])(?:\.\d+)?)(Z|[+-]\d{2}:\d{2}))$").unwrap()
});
pub fn test_6_1_37_date_and_time(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> {
let tracking = doc.get_document().get_tracking();
check_datetime(
&tracking.get_initial_release_date(),
"/document/tracking/initial_release_date",
)?;
check_datetime(
&tracking.get_current_release_date(),
"/document/tracking/current_release_date",
)?;
if let Some(generator) = tracking.get_generator()
&& let Some(date) = generator.get_date()
{
check_datetime(&date, "/document/tracking/generator/date")?;
}
for (i_r, revision) in tracking.get_revision_history().iter().enumerate() {
check_datetime(
&revision.get_date(),
&format!("/document/tracking/revision_history/{i_r}/date"),
)?;
}
for (i_v, vuln) in doc.get_vulnerabilities().iter().enumerate() {
if let Some(date) = vuln.get_disclosure_date() {
check_datetime(&date, &format!("/vulnerabilities/{i_v}/disclosure_date"))?;
}
if let Some(date) = vuln.get_discovery_date() {
check_datetime(&date, &format!("/vulnerabilities/{i_v}/discovery_date"))?;
}
if let Some(flags) = vuln.get_flags() {
for (i_f, flag) in flags.iter().enumerate() {
if let Some(date) = flag.get_date() {
check_datetime(&date, &format!("/vulnerabilities/{i_v}/flags/{i_f}/date"))?;
}
}
}
if let Some(involvements) = vuln.get_involvements() {
for (i_i, involvement) in involvements.iter().enumerate() {
if let Some(date) = involvement.get_date() {
check_datetime(&date, &format!("/vulnerabilities/{i_v}/involvements/{i_i}/date"))?;
}
}
}
for (i_r, remediation) in vuln.get_remediations().iter().enumerate() {
if let Some(date) = remediation.get_date() {
check_datetime(&date, &format!("/vulnerabilities/{i_v}/remediations/{i_r}/date"))?;
}
}
for (i_t, threat) in vuln.get_threats().iter().enumerate() {
if let Some(date) = threat.get_date() {
check_datetime(&date, &format!("/vulnerabilities/{i_v}/threats/{i_t}/date"))?;
}
}
if let Some(first_known_exploitation_dates) = vuln.get_first_known_exploitation_dates() {
for (i_d, date) in first_known_exploitation_dates.iter().enumerate() {
check_datetime(
&date.get_date(),
&format!("/vulnerabilities/{i_v}/first_known_exploitation_dates/{i_d}/date"),
)?;
check_datetime(
&date.get_exploitation_date(),
&format!("/vulnerabilities/{i_v}/first_known_exploitation_dates/{i_d}/exploitation_date"),
)?;
}
}
}
Ok(())
}
fn create_invalid_format_error(date_time: &str, instance_path: &str) -> ValidationError {
ValidationError {
message: format!(
"Invalid date-time string {date_time}, expected RFC3339-compliant format with non-empty timezone and no leap seconds"
),
instance_path: instance_path.to_string(),
}
}
fn create_parsing_error(date_time: &str, error: impl std::fmt::Display, instance_path: &str) -> ValidationError {
ValidationError {
message: format!("Date-time string {date_time} matched RFC3339 regex but failed chrono parsing: {error}"),
instance_path: instance_path.to_string(),
}
}
fn check_datetime(date_time: &CsafDateTime, instance_path: &str) -> Result<(), Vec<ValidationError>> {
let date = match date_time {
Valid(date) => date.get_raw_string(),
Invalid(err) => err.get_raw_string(),
};
if CSAF_RFC3339_REGEX.is_match(date) {
match date_time {
Valid(_) => Ok(()),
Invalid(err) => Err(vec![create_parsing_error(date, err, instance_path)]),
}
} else {
Err(vec![create_invalid_format_error(date, instance_path)])
}
}
crate::test_validation::impl_validator!(csaf2_1, ValidatorForTest6_1_37, test_6_1_37_date_and_time);
#[cfg(test)]
mod tests {
use super::*;
use crate::csaf2_1::testcases::TESTS_2_1;
#[test]
fn test_test_6_1_37() {
TESTS_2_1.test_6_1_37.expect(
Err(vec![create_invalid_format_error(
"2024-01-24 10:00:00.000Z",
"/document/tracking/initial_release_date",
)]),
Err(vec![create_invalid_format_error(
"2024-01-24T10:00:00.000z",
"/document/tracking/initial_release_date",
)]),
Err(vec![create_invalid_format_error(
"2017-01-01T02:59:60+04:00",
"/vulnerabilities/0/disclosure_date",
)]),
Err(vec![create_parsing_error(
"2023-04-31T00:00:00+01:00",
"Failed to parse '2023-04-31T00:00:00+01:00' as RFC3339 with reason 'input is out of range'",
"/vulnerabilities/0/disclosure_date",
)]),
Err(vec![create_parsing_error(
"2023-02-29T00:00:00+01:00",
"Failed to parse '2023-02-29T00:00:00+01:00' as RFC3339 with reason 'input is out of range'",
"/vulnerabilities/0/disclosure_date",
)]),
Err(vec![create_invalid_format_error(
"2016-12-31T00:00:60+23:59",
"/vulnerabilities/0/disclosure_date",
)]),
Err(vec![create_invalid_format_error(
"2015-06-30T10:29:60-13:30",
"/vulnerabilities/0/disclosure_date",
)]),
Err(vec![create_invalid_format_error(
"2015-06-30T10:29:60-13:30",
"/vulnerabilities/0/disclosure_date",
)]),
Err(vec![create_invalid_format_error(
"2016-12-31T23:59:60.0123+00:00",
"/vulnerabilities/0/disclosure_date",
)]),
Err(vec![create_invalid_format_error(
"2024-01-24t10:00:00.000Z",
"/vulnerabilities/0/first_known_exploitation_dates/0/date",
)]),
Ok(()),
Ok(()),
Ok(()),
Ok(()),
Ok(()),
Ok(()),
);
}
}