52 lines
1.6 KiB
Rust
52 lines
1.6 KiB
Rust
pub fn contains_ignore_ascii_case(haystack: &str, needle: &str) -> bool {
|
|
if needle.is_empty() {
|
|
return true;
|
|
}
|
|
haystack
|
|
.as_bytes()
|
|
.windows(needle.len())
|
|
.any(|w| w.eq_ignore_ascii_case(needle.as_bytes()))
|
|
}
|
|
pub fn now_secs() -> u64 {
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs()
|
|
}
|
|
|
|
pub fn validate_safe_path(path_str: &str) -> crate::error::Result<std::path::PathBuf> {
|
|
let path = std::path::Path::new(path_str);
|
|
for component in path.components() {
|
|
if component == std::path::Component::ParentDir {
|
|
return Err(crate::error::AppError::BadRequest(format!(
|
|
"Path traversal forbidden: '{}' contains relative parent directory components ('..')",
|
|
path_str
|
|
)));
|
|
}
|
|
}
|
|
Ok(path.to_path_buf())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_contains_ignore_ascii_case() {
|
|
assert!(contains_ignore_ascii_case("Hello World", "world"));
|
|
assert!(contains_ignore_ascii_case("Hello World", "HELLO"));
|
|
assert!(contains_ignore_ascii_case("test", ""));
|
|
assert!(!contains_ignore_ascii_case("test", "testing"));
|
|
assert!(!contains_ignore_ascii_case("Hello", "world"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_now_secs() {
|
|
let t1 = now_secs();
|
|
assert!(t1 > 1700000000); // Sanity check for recent epoch
|
|
std::thread::sleep(std::time::Duration::from_millis(1000));
|
|
let t2 = now_secs();
|
|
assert!(t2 >= t1);
|
|
}
|
|
}
|