secra_plugins 0.1.32

生产级插件系统 - 插件的生命周期
Documentation
//! 路径验证和清理相关方法

use crate::error::{PluginManagerError, PluginManagerResult};
use super::constants::MAX_PLUGIN_NAME_LEN;
use std::path::Path;

/// 清理路径字符串,防止路径遍历攻击
///
/// 移除所有危险字符,只保留安全的字符。
///
/// # 参数
/// * `path` - 原始路径字符串
/// * `allow_slash` - 是否允许路径分隔符(/ 和 \)
///
/// # 返回值
/// * `String` - 清理后的路径字符串
pub fn sanitize_path_string(path: &str, allow_slash: bool) -> String {
    path.chars()
        .filter(|c| {
            c.is_alphanumeric() 
                || *c == '-' 
                || *c == '_' 
                || (allow_slash && (*c == '/' || *c == '\\'))
        })
        .collect::<String>()
}

/// 清理插件名称,防止路径遍历攻击
///
/// 移除所有路径分隔符和特殊字符,只保留字母数字、连字符和下划线。
///
/// # 参数
/// * `name` - 原始插件名称
///
/// # 返回值
/// * `PluginManagerResult<String>` - 清理后的插件名称
pub fn sanitize_plugin_name(name: &str) -> PluginManagerResult<String> {
    // 移除所有路径分隔符和特殊字符
    let sanitized = sanitize_path_string(name, false);
    
    if sanitized.is_empty() {
        return Err(PluginManagerError::ValidationFailed("插件名称无效:清理后为空".to_string()));
    }
    
    if sanitized.len() > MAX_PLUGIN_NAME_LEN {
        return Err(PluginManagerError::ValidationFailed(
            format!("插件名称过长:超过{}个字符", MAX_PLUGIN_NAME_LEN)
        ));
    }
    
    Ok(sanitized)
}

/// 验证路径是否在指定基础目录内,防止路径遍历攻击
///
/// # 参数
/// * `base_path` - 基础目录路径
/// * `target_path` - 要验证的目标路径
///
/// # 返回值
/// * `PluginManagerResult<()>` - 验证通过返回 Ok(()),否则返回错误
///
/// # 行为
/// * 如果目标路径存在,直接规范化并验证
/// * 如果目标路径不存在,尝试规范化其父目录并验证
/// * 确保目标路径(或其父目录)在基础路径内
pub fn validate_path_within_base(
    base_path: &Path,
    target_path: &Path,
) -> PluginManagerResult<()> {
    // 规范化基础路径
    let canonical_base = base_path.canonicalize()
        .map_err(|e| PluginManagerError::Io(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            format!("无法规范化基础路径: {}", e)
        )))?;
    
    // 尝试规范化目标路径
    // 如果目标路径不存在,尝试规范化其父目录
    let canonical_target = target_path.canonicalize()
        .or_else(|e| {
            // 如果路径不存在,尝试规范化父目录
            if let Some(parent) = target_path.parent() {
                parent.canonicalize()
                    .map(|p| {
                        // 如果父目录存在,组合父目录和目标文件名
                        if let Some(file_name) = target_path.file_name() {
                            p.join(file_name)
                        } else {
                            p
                        }
                    })
                    .map_err(|_| {
                        std::io::Error::new(
                            std::io::ErrorKind::NotFound,
                            format!("无法规范化目标路径或其父目录: {} (原始错误: {})", target_path.display(), e)
                        )
                    })
            } else {
                Err(std::io::Error::new(
                    std::io::ErrorKind::NotFound,
                    format!("无法规范化目标路径且没有父目录: {} (原始错误: {})", target_path.display(), e)
                ))
            }
        })
        .map_err(|e| PluginManagerError::Io(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            format!("无法规范化目标路径: {}", e)
        )))?;
    
    // 检查目标路径是否在基础路径内
    if !canonical_target.starts_with(&canonical_base) {
        return Err(PluginManagerError::ValidationFailed(
            "路径验证失败:目标路径不在基础路径内".to_string()
        ));
    }
    
    Ok(())
}