use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub fn ensure_path_within_dir(output_dir: &Path, entry_name: &str) -> Result<PathBuf, String> {
let target = output_dir.join(entry_name);
let canonical_dir = output_dir.canonicalize().unwrap_or_else(|_| output_dir.to_path_buf());
if let Some(parent) = target.parent() {
if parent.exists() {
if let Ok(canonical_target) = parent.canonicalize() {
if !canonical_target.starts_with(&canonical_dir) {
return Err(format!("路径遍历攻击检测: \"{}\" 试图写到输出目录之外", entry_name));
}
}
}
}
if entry_name.contains("..") {
return Err(format!("路径遍历攻击检测: \"{}\" 包含非法路径组件", entry_name));
}
Ok(target)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ArchiveFormat {
Zip,
Rar,
SevenZ,
Tar,
TarGz,
TarBz2,
TarXz,
Gz,
Bz2,
Xz,
Unknown,
}
impl ArchiveFormat {
pub fn from_path(path: &str) -> Self {
let lower = path.to_lowercase();
if lower.ends_with(".zip") {
Self::Zip
} else if lower.ends_with(".rar") {
Self::Rar
} else if lower.ends_with(".7z") {
Self::SevenZ
} else if lower.ends_with(".tar.gz") || lower.ends_with(".tgz") {
Self::TarGz
} else if lower.ends_with(".tar.bz2") || lower.ends_with(".tbz2") {
Self::TarBz2
} else if lower.ends_with(".tar.xz") || lower.ends_with(".txz") {
Self::TarXz
} else if lower.ends_with(".tar") {
Self::Tar
} else if lower.ends_with(".gz") {
Self::Gz
} else if lower.ends_with(".bz2") {
Self::Bz2
} else if lower.ends_with(".xz") {
Self::Xz
} else {
Self::Unknown
}
}
#[allow(dead_code)]
pub fn name(&self) -> &str {
match self {
Self::Zip => "ZIP",
Self::Rar => "RAR",
Self::SevenZ => "7z",
Self::Tar => "TAR",
Self::TarGz => "TAR.GZ",
Self::TarBz2 => "TAR.BZ2",
Self::TarXz => "TAR.XZ",
Self::Gz => "GZ",
Self::Bz2 => "BZ2",
Self::Xz => "XZ",
Self::Unknown => "未知",
}
}
#[allow(dead_code)]
pub fn can_compress(&self) -> bool {
matches!(
self,
Self::Zip | Self::SevenZ | Self::Tar | Self::TarGz | Self::TarBz2 | Self::TarXz
)
}
#[allow(dead_code)]
pub fn can_decompress(&self) -> bool {
!matches!(self, Self::Unknown)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchiveEntry {
pub name: String,
pub path: String,
pub size: u64,
pub compressed_size: u64,
pub is_dir: bool,
pub modified: Option<String>,
pub is_encrypted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchiveInfo {
pub format: ArchiveFormat,
pub path: String,
pub entries: Vec<ArchiveEntry>,
pub total_size: u64,
pub total_compressed_size: u64,
pub file_count: usize,
pub has_password: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompressOptions {
pub format: ArchiveFormat,
pub output_path: String,
pub password: Option<String>,
pub compression_level: CompressionLevel,
pub split_volume: Option<u64>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CompressionLevel {
Store,
Fastest,
Fast,
Normal,
Good,
Best,
}
impl Default for CompressionLevel {
fn default() -> Self {
Self::Normal
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractOptions {
pub output_dir: String,
pub password: Option<String>,
pub selected_entries: Option<Vec<String>>, pub overwrite: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressInfo {
pub task_id: String,
pub current_file: String,
pub processed_bytes: u64,
pub total_bytes: u64,
pub percent: f64,
pub speed: f64, pub elapsed_secs: f64,
pub remaining_secs: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskResult {
pub task_id: String,
pub success: bool,
pub message: String,
pub output_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RarSupport {
pub can_decompress: bool,
pub can_compress: bool,
pub winrar_path: Option<String>,
}