openserve 2.0.3

A modern, high-performance, AI-enhanced file server built in Rust
Documentation
//! Utilities Module
//!
//! This module contains various utility functions that are used
//! throughout the application. These utilities are organized into
//! sub-modules for clarity and separation of concerns.

pub mod crypto;
pub mod validation;
pub mod time;
pub mod compression;
pub mod file_utils;

use std::path::Path;

/// Checks if a file is likely a text file based on its extension or filename.
///
/// This function is useful for determining whether to attempt reading a file as text
/// for operations like content analysis or search indexing. It uses a comprehensive
/// list of common text-based file extensions.
///
/// For files without extensions, it checks against a list of common text-based filenames
/// like `Dockerfile`, `Makefile`, `README`, etc.
///
/// # Arguments
///
/// * `path` - A reference to the path of the file to check.
///
/// # Returns
///
/// `true` if the file is likely a text file, `false` otherwise.
pub fn is_text_file(path: &Path) -> bool {
    if let Some(extension) = path.extension() {
        let ext = extension.to_string_lossy().to_lowercase();
        matches!(ext.as_str(), 
            "txt" | "md" | "rs" | "py" | "js" | "ts" | "html" | "css" | "json" | 
            "yaml" | "yml" | "toml" | "xml" | "csv" | "log" | "conf" | "cfg" |
            "ini" | "sh" | "bat" | "ps1" | "sql" | "c" | "cpp" | "h" | "hpp" |
            "java" | "kt" | "go" | "php" | "rb" | "swift" | "dart" | "scala" |
            "clj" | "hs" | "elm" | "ex" | "exs" | "erl" | "pl" | "r" | "m" |
            "dockerfile" | "gitignore" | "gitattributes" | "license" | "readme"
        )
    } else {
        // Check common files without extensions
        if let Some(filename) = path.file_name() {
            let name = filename.to_string_lossy().to_lowercase();
            matches!(name.as_str(),
                "dockerfile" | "makefile" | "readme" | "license" | "changelog" |
                "authors" | "contributors" | "copying" | "install" | "news" |
                "todo" | "version" | "manifest"
            )
        } else {
            false
        }
    }
}

/// Formats a file size in bytes into a human-readable string.
///
/// It converts the size into KB, MB, GB, or TB as appropriate, using
/// one decimal place for sizes larger than 1 KB.
///
/// # Arguments
///
/// * `size` - The file size in bytes.
///
/// # Returns
///
/// A `String` representing the formatted file size (e.g., "1.5 KB").
pub fn format_file_size(size: u64) -> String {
    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
    let mut size = size as f64;
    let mut unit_index = 0;

    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
        size /= 1024.0;
        unit_index += 1;
    }

    if unit_index == 0 {
        format!("{} {}", size as u64, UNITS[unit_index])
    } else {
        format!("{:.1} {}", size, UNITS[unit_index])
    }
}

/// Sanitizes a filename to prevent directory traversal and invalid characters.
///
/// This function removes potentially dangerous characters like `/`, `\`, and others
/// that could be used for path manipulation. It also trims whitespace and removes
/// leading dots to prevent creating hidden files or attempting path traversal
/// (e.g., `../`).
///
/// # Arguments
///
/// * `filename` - The filename to sanitize.
///
/// # Returns
///
/// A sanitized `String` that is safe to use as a filename.
pub fn sanitize_filename(filename: &str) -> String {
    let sanitized = filename
        .chars()
        .filter(|c| !matches!(c, '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|'))
        .collect::<String>()
        .trim()
        .to_string();
    
    // Remove leading dots to prevent hidden files and directory traversal
    sanitized.trim_start_matches('.').to_string()
}

/// Generates a random alphanumeric string of a specified length.
///
/// This is useful for creating unique identifiers, tokens, or temporary filenames.
///
/// # Arguments
///
/// * `length` - The desired length of the random string.
///
/// # Returns
///
/// A randomly generated `String`.
pub fn generate_random_string(length: usize) -> String {
    use rand::{distributions::Alphanumeric, Rng};
    rand::thread_rng()
        .sample_iter(&Alphanumeric)
        .take(length)
        .map(char::from)
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_format_file_size() {
        assert_eq!(format_file_size(0), "0 B");
        assert_eq!(format_file_size(1023), "1023 B");
        assert_eq!(format_file_size(1024), "1.0 KB");
        assert_eq!(format_file_size(1536), "1.5 KB");
        assert_eq!(format_file_size(1048576), "1.0 MB");
    }

    #[test]
    fn test_sanitize_filename() {
        assert_eq!(sanitize_filename("test.txt"), "test.txt");
        assert_eq!(sanitize_filename("../../../etc/passwd"), "etcpasswd");
        assert_eq!(sanitize_filename("file<>name.txt"), "filename.txt");
    }

    #[test]
    fn test_generate_random_string() {
        let random = generate_random_string(10);
        assert_eq!(random.len(), 10);
        assert!(random.chars().all(|c| c.is_alphanumeric()));
    }
}