- Phase 1 Critical: All 115 .unwrap() verified in test code only - Phase 1 Critical: All runtime .expect() converted to proper error handling - Phase 2 H1: Antivirus commands now use SafeCommand (added which/where to whitelist) - Phase 2 H2: db_api.rs error responses use log_and_sanitize() - Phase 2 H5: Removed duplicate sanitize_identifier (re-exports from sql_guard) 32 files modified for security hardening. Moon deployment criteria: 10/10 met
212 lines
6.6 KiB
Rust
212 lines
6.6 KiB
Rust
use crate::shared::models::UserSession;
|
|
use crate::shared::state::AppState;
|
|
use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike};
|
|
use log::debug;
|
|
use rhai::Engine;
|
|
use std::sync::Arc;
|
|
|
|
fn parse_date(date_str: &str) -> Option<NaiveDate> {
|
|
let trimmed = date_str.trim();
|
|
NaiveDate::parse_from_str(trimmed, "%Y-%m-%d")
|
|
.ok()
|
|
.or_else(|| NaiveDate::parse_from_str(trimmed, "%d/%m/%Y").ok())
|
|
.or_else(|| NaiveDate::parse_from_str(trimmed, "%m/%d/%Y").ok())
|
|
.or_else(|| NaiveDate::parse_from_str(trimmed, "%Y/%m/%d").ok())
|
|
.or_else(|| parse_datetime(trimmed).map(|dt| dt.date()))
|
|
}
|
|
|
|
fn parse_datetime(datetime_str: &str) -> Option<NaiveDateTime> {
|
|
let trimmed = datetime_str.trim();
|
|
NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%d %H:%M:%S")
|
|
.ok()
|
|
.or_else(|| NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%dT%H:%M:%S").ok())
|
|
.or_else(|| NaiveDateTime::parse_from_str(trimmed, "%Y-%m-%d %H:%M").ok())
|
|
.or_else(|| parse_date(trimmed).and_then(|d| d.and_hms_opt(0, 0, 0)))
|
|
}
|
|
|
|
pub fn year_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("YEAR", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.year()))
|
|
.unwrap_or(0)
|
|
});
|
|
engine.register_fn("year", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.year()))
|
|
.unwrap_or(0)
|
|
});
|
|
|
|
debug!("Registered YEAR keyword");
|
|
}
|
|
|
|
pub fn month_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("MONTH", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.month()))
|
|
.unwrap_or(0)
|
|
});
|
|
engine.register_fn("month", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.month()))
|
|
.unwrap_or(0)
|
|
});
|
|
|
|
debug!("Registered MONTH keyword");
|
|
}
|
|
|
|
pub fn day_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("DAY", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.day()))
|
|
.unwrap_or(0)
|
|
});
|
|
engine.register_fn("day", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.day()))
|
|
.unwrap_or(0)
|
|
});
|
|
|
|
debug!("Registered DAY keyword");
|
|
}
|
|
|
|
pub fn hour_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("HOUR", |datetime_str: &str| -> i64 {
|
|
parse_datetime(datetime_str)
|
|
.map(|d| i64::from(d.hour()))
|
|
.unwrap_or(0)
|
|
});
|
|
engine.register_fn("hour", |datetime_str: &str| -> i64 {
|
|
parse_datetime(datetime_str)
|
|
.map(|d| i64::from(d.hour()))
|
|
.unwrap_or(0)
|
|
});
|
|
|
|
debug!("Registered HOUR keyword");
|
|
}
|
|
|
|
pub fn minute_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("MINUTE", |datetime_str: &str| -> i64 {
|
|
parse_datetime(datetime_str)
|
|
.map(|d| i64::from(d.minute()))
|
|
.unwrap_or(0)
|
|
});
|
|
engine.register_fn("minute", |datetime_str: &str| -> i64 {
|
|
parse_datetime(datetime_str)
|
|
.map(|d| i64::from(d.minute()))
|
|
.unwrap_or(0)
|
|
});
|
|
|
|
debug!("Registered MINUTE keyword");
|
|
}
|
|
|
|
pub fn second_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("SECOND", |datetime_str: &str| -> i64 {
|
|
parse_datetime(datetime_str)
|
|
.map(|d| i64::from(d.second()))
|
|
.unwrap_or(0)
|
|
});
|
|
engine.register_fn("second", |datetime_str: &str| -> i64 {
|
|
parse_datetime(datetime_str)
|
|
.map(|d| i64::from(d.second()))
|
|
.unwrap_or(0)
|
|
});
|
|
|
|
debug!("Registered SECOND keyword");
|
|
}
|
|
|
|
pub fn weekday_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("WEEKDAY", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.weekday().num_days_from_sunday()) + 1)
|
|
.unwrap_or(0)
|
|
});
|
|
engine.register_fn("weekday", |date_str: &str| -> i64 {
|
|
parse_date(date_str)
|
|
.map(|d| i64::from(d.weekday().num_days_from_sunday()) + 1)
|
|
.unwrap_or(0)
|
|
});
|
|
|
|
debug!("Registered WEEKDAY keyword");
|
|
}
|
|
|
|
pub fn format_date_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("FORMAT_DATE", |date_str: &str, format: &str| -> String {
|
|
format_date_impl(date_str, format)
|
|
});
|
|
engine.register_fn("format_date", |date_str: &str, format: &str| -> String {
|
|
format_date_impl(date_str, format)
|
|
});
|
|
|
|
debug!("Registered FORMAT_DATE keyword");
|
|
}
|
|
|
|
pub fn isdate_keyword(_state: &Arc<AppState>, _user: UserSession, engine: &mut Engine) {
|
|
engine.register_fn("ISDATE", |value: &str| -> bool {
|
|
parse_date(value).is_some()
|
|
});
|
|
engine.register_fn("isdate", |value: &str| -> bool {
|
|
parse_date(value).is_some()
|
|
});
|
|
engine.register_fn("IS_DATE", |value: &str| -> bool {
|
|
parse_date(value).is_some()
|
|
});
|
|
|
|
debug!("Registered ISDATE keyword");
|
|
}
|
|
|
|
pub fn format_date_impl(date_str: &str, format: &str) -> String {
|
|
if let Some(datetime) = parse_datetime(date_str) {
|
|
let chrono_format = format
|
|
.replace("YYYY", "%Y")
|
|
.replace("yyyy", "%Y")
|
|
.replace("YY", "%y")
|
|
.replace("yy", "%y")
|
|
.replace("MMMM", "%B")
|
|
.replace("MMM", "%b")
|
|
.replace("MM", "%m")
|
|
.replace("DD", "%d")
|
|
.replace("dd", "%d")
|
|
.replace("HH", "%H")
|
|
.replace("hh", "%I")
|
|
.replace("mm", "%M")
|
|
.replace("ss", "%S")
|
|
.replace("AM/PM", "%p")
|
|
.replace("am/pm", "%P");
|
|
|
|
datetime.format(&chrono_format).to_string()
|
|
} else {
|
|
date_str.to_string()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use chrono::{Datelike, Timelike};
|
|
|
|
#[test]
|
|
fn test_parse_date() {
|
|
let date = parse_date("2025-01-22");
|
|
assert!(date.is_some());
|
|
let d = date.unwrap();
|
|
assert_eq!(d.year(), 2025);
|
|
assert_eq!(d.month(), 1);
|
|
assert_eq!(d.day(), 22);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_datetime() {
|
|
let dt = parse_datetime("2025-01-22 14:30:45");
|
|
assert!(dt.is_some());
|
|
let d = dt.unwrap();
|
|
assert_eq!(d.hour(), 14);
|
|
assert_eq!(d.minute(), 30);
|
|
assert_eq!(d.second(), 45);
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_date() {
|
|
let date = parse_date("invalid");
|
|
assert!(date.is_none());
|
|
}
|
|
}
|