use std::path::{Path, PathBuf};
use std::fs;
pub fn is_readable_file<P: AsRef<Path>>(path: P) -> bool {
let path = path.as_ref();
path.exists() && path.is_file() && fs::metadata(path).map(|m| !m.permissions().readonly()).unwrap_or(false)
}
pub fn is_accessible_directory<P: AsRef<Path>>(path: P) -> bool {
let path = path.as_ref();
path.exists() && path.is_dir()
}
pub fn get_file_extension<P: AsRef<Path>>(path: P) -> Option<String> {
path.as_ref()
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_lowercase())
}
pub fn has_allowed_extension<P: AsRef<Path>>(path: P, allowed: &[String]) -> bool {
if let Some(ext) = get_file_extension(path) {
allowed.iter().any(|allowed_ext| {
allowed_ext.trim_start_matches('.').eq_ignore_ascii_case(&ext)
})
} else {
false
}
}
pub fn get_file_size<P: AsRef<Path>>(path: P) -> Result<u64, std::io::Error> {
fs::metadata(path).map(|m| m.len())
}
pub fn is_within_size_limit<P: AsRef<Path>>(path: P, max_size: u64) -> bool {
get_file_size(path).map(|size| size <= max_size).unwrap_or(false)
}
pub fn create_backup<P: AsRef<Path>>(path: P) -> Result<PathBuf, std::io::Error> {
let path = path.as_ref();
let backup_path = path.with_extension(format!("{}.bak",
path.extension().and_then(|s| s.to_str()).unwrap_or("")));
fs::copy(path, &backup_path)?;
Ok(backup_path)
}
pub fn find_files_by_pattern<P: AsRef<Path>>(
dir: P,
pattern: &str,
recursive: bool,
) -> Result<Vec<PathBuf>, std::io::Error> {
let mut results = Vec::new();
find_files_recursive(dir.as_ref(), pattern, recursive, &mut results)?;
Ok(results)
}
fn find_files_recursive(
dir: &Path,
pattern: &str,
recursive: bool,
results: &mut Vec<PathBuf>,
) -> Result<(), std::io::Error> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if name.contains(pattern) {
results.push(path);
}
}
} else if path.is_dir() && recursive {
find_files_recursive(&path, pattern, recursive, results)?;
}
}
Ok(())
}
pub fn get_relative_path<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Option<PathBuf> {
pathdiff::diff_paths(to, from)
}
pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let mut components = Vec::new();
for component in path.components() {
match component {
std::path::Component::CurDir => {
}
std::path::Component::ParentDir => {
components.pop();
}
_ => {
components.push(component);
}
}
}
components.iter().collect()
}
pub fn is_safe_path<P: AsRef<Path>>(path: P) -> bool {
let path_str = path.as_ref().to_string_lossy();
if path_str.contains("..") {
return false;
}
let dangerous_prefixes = [
"/etc", "/sys", "/proc", "/dev", "/boot",
"C:\\Windows", "C:\\System32", "C:\\Program Files",
"/System", "/Library/System",
];
for prefix in &dangerous_prefixes {
if path_str.starts_with(prefix) {
return false;
}
}
true
}
pub fn get_mime_type<P: AsRef<Path>>(path: P) -> String {
match get_file_extension(path).as_deref() {
Some("txt") => "text/plain",
Some("md") => "text/markdown",
Some("rs") => "text/x-rust",
Some("py") => "text/x-python",
Some("js") => "text/javascript",
Some("ts") => "text/typescript",
Some("html") => "text/html",
Some("css") => "text/css",
Some("json") => "application/json",
Some("xml") => "application/xml",
Some("yaml") | Some("yml") => "application/x-yaml",
Some("toml") => "application/toml",
Some("png") => "image/png",
Some("jpg") | Some("jpeg") => "image/jpeg",
Some("gif") => "image/gif",
Some("svg") => "image/svg+xml",
Some("pdf") => "application/pdf",
Some("zip") => "application/zip",
_ => "application/octet-stream",
}.to_string()
}
pub fn is_text_file<P: AsRef<Path>>(path: P) -> bool {
let mime_type = get_mime_type(path);
mime_type.starts_with("text/") ||
mime_type == "application/json" ||
mime_type == "application/xml" ||
mime_type == "application/x-yaml" ||
mime_type == "application/toml"
}
pub fn get_temp_file_path(extension: &str) -> PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("coderlib_{}.{}",
uuid::Uuid::new_v4().to_string()[..8].to_string(),
extension.trim_start_matches('.')
));
path
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
#[test]
fn test_get_file_extension() {
assert_eq!(get_file_extension("test.txt"), Some("txt".to_string()));
assert_eq!(get_file_extension("test.RS"), Some("rs".to_string()));
assert_eq!(get_file_extension("test"), None);
}
#[test]
fn test_has_allowed_extension() {
let allowed = vec![".txt".to_string(), ".rs".to_string()];
assert!(has_allowed_extension("test.txt", &allowed));
assert!(has_allowed_extension("test.RS", &allowed));
assert!(!has_allowed_extension("test.py", &allowed));
}
#[test]
fn test_is_safe_path() {
assert!(is_safe_path("safe/path/file.txt"));
assert!(!is_safe_path("../dangerous"));
assert!(!is_safe_path("/etc/passwd"));
assert!(!is_safe_path("C:\\Windows\\system32"));
}
#[test]
fn test_get_mime_type() {
assert_eq!(get_mime_type("test.txt"), "text/plain");
assert_eq!(get_mime_type("test.rs"), "text/x-rust");
assert_eq!(get_mime_type("test.json"), "application/json");
assert_eq!(get_mime_type("test.unknown"), "application/octet-stream");
}
#[test]
fn test_is_text_file() {
assert!(is_text_file("test.txt"));
assert!(is_text_file("test.rs"));
assert!(is_text_file("test.json"));
assert!(!is_text_file("test.png"));
assert!(!is_text_file("test.exe"));
}
#[test]
fn test_normalize_path() {
assert_eq!(normalize_path("./test/../file.txt"), PathBuf::from("file.txt"));
assert_eq!(normalize_path("dir/./file.txt"), PathBuf::from("dir/file.txt"));
}
#[test]
fn test_file_operations() {
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("test.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "test content").unwrap();
assert!(is_readable_file(&file_path));
assert!(get_file_size(&file_path).unwrap() > 0);
assert!(is_within_size_limit(&file_path, 1000));
let backup_path = create_backup(&file_path).unwrap();
assert!(backup_path.exists());
}
#[test]
fn test_get_temp_file_path() {
let temp_path = get_temp_file_path("txt");
assert!(temp_path.to_string_lossy().contains("coderlib_"));
assert!(temp_path.to_string_lossy().ends_with(".txt"));
}
}