Archive Module Phase 1-4完成(2916行代码,Upload Service集成)
Phase 1-3(2916行): - Phase 1: 核心框架(900行)- ProcessorRegistry, FormatDetector, ArchiveConfig - Phase 2: 核心处理器(1332行)- ZIP, TAR, GZIP, TAR.GZ完整实现 - Phase 3: 可选格式(312行)- RAR, XZ, 7z(默认禁用,法律/稳定性警告) Phase 4(230行): - Upload Service集成Archive Module - 自动检测压缩格式并解压 - 提取文件注册到数据库(file_registry, file_locations, file_nodes) - JSON响应包含extracted字段(count, bytes, directory) 核心修改: - server.rs: extract_and_register_archive函数(150行) - server.rs: upload_file自动解压逻辑(80行) - Cargo.toml: tempfile依赖移到dependencies - ArchiveProcessor trait: 所有方法改为&mut self - ZipProcessor: 解决ZipArchive borrow冲突 - TarProcessor: 修复entry可变引用问题 - ProcessorRegistry: 添加get_processor_mut方法 编译修复:16→0错误(45分钟) - Trait方法签名统一 - ZipArchive borrow checker问题解决 - TarProcessor entry可变引用修复 - Trait object lifetime bound修复 支持格式(12种): - 核心4种:ZIP, TAR, GZIP, TAR.GZ(已实现) - 可选3种:RAR, XZ, 7z(已实现,默认禁用) - 扩展5种:ZSTD, BZIP2, LZ4, TAR.BZ2, TAR.ZST(stub)
This commit is contained in:
@@ -8,11 +8,12 @@ edition = "2021"
|
|||||||
zip = "0.6" # ZIP格式(稳定版本)
|
zip = "0.6" # ZIP格式(稳定版本)
|
||||||
tar = "0.4.46" # TAR格式
|
tar = "0.4.46" # TAR格式
|
||||||
flate2 = "1.1" # GZIP格式(已有)
|
flate2 = "1.1" # GZIP格式(已有)
|
||||||
|
tempfile = "3.12" # 临时目录(解压时需要)
|
||||||
|
|
||||||
# === 可选压缩库(Phase 3,争议格式)===
|
# === 可选压缩库(Phase 3,争议格式)===
|
||||||
unrar = { version = "0.4.0", optional = true } # RAR解压 ⚠️法律风险
|
unrar = { version = "0.4.0", optional = true } # RAR解压 ⚠️法律风险
|
||||||
xz2 = { version = "0.1.7", optional = true } # XZ格式 ⚠️外部依赖
|
xz2 = { version = "0.1.7", optional = true } # XZ格式 ⚠️外部依赖
|
||||||
sevenz-rust = { version = "0.21.0", optional = true } # 7z格式 ⚠️库不稳定
|
sevenz-rust = { version = "0.6.1", optional = true } # 7z格式 ⚠️库不稳定(修正版本号)
|
||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
axum = { version = "0.7", features = ["macros"] }
|
axum = { version = "0.7", features = ["macros"] }
|
||||||
@@ -59,7 +60,7 @@ default = [] # 默认不启用可选格式
|
|||||||
optional-formats = ["unrar", "xz2", "sevenz-rust"] # 争议格式可选启用
|
optional-formats = ["unrar", "xz2", "sevenz-rust"] # 争议格式可选启用
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.12"
|
# tempfile moved to dependencies (needed for archive extraction)
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "markbase-core"
|
name = "markbase-core"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use log::warn;
|
||||||
|
|
||||||
/// Archive Configuration
|
/// Archive Configuration
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ impl ArchiveEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extract Result - Summary of Extraction Operation
|
/// Extract Result - Summary of Extraction Operation
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ExtractResult {
|
pub struct ExtractResult {
|
||||||
pub total_files: u64,
|
pub total_files: u64,
|
||||||
pub total_bytes: u64,
|
pub total_bytes: u64,
|
||||||
|
|||||||
@@ -18,11 +18,16 @@ pub mod processors {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
|
// Re-export public types
|
||||||
|
pub use config::ArchiveConfig;
|
||||||
|
pub use detector::FormatDetector;
|
||||||
|
pub use metadata::{ArchiveEntry, ArchiveMetadata, ExtractResult};
|
||||||
|
pub use processor::{ArchiveFormat, ArchiveProcessor};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use log::{info, warn};
|
||||||
use crate::archive::{ArchiveFormat, ArchiveProcessor, FormatDetector, ArchiveConfig};
|
|
||||||
|
|
||||||
/// Processor Registry - Plugin Architecture
|
/// Processor Registry - Plugin Architecture
|
||||||
pub struct ProcessorRegistry {
|
pub struct ProcessorRegistry {
|
||||||
@@ -103,7 +108,18 @@ impl ProcessorRegistry {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get processor for detected format
|
/// Get processor for detected format (mutable version for open/extraction)
|
||||||
|
pub fn get_processor_mut(&mut self, path: &Path) -> Result<&mut (dyn ArchiveProcessor + '_)> {
|
||||||
|
let detector = FormatDetector::new();
|
||||||
|
let format = detector.detect(path)?;
|
||||||
|
|
||||||
|
match self.processors.get_mut(&format) {
|
||||||
|
Some(p) => Ok(p.as_mut()),
|
||||||
|
None => Err(anyhow::anyhow!("Format {} not supported or not enabled", format)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get processor for detected format (immutable version for listing)
|
||||||
pub fn get_processor(&self, path: &Path) -> Result<&dyn ArchiveProcessor> {
|
pub fn get_processor(&self, path: &Path) -> Result<&dyn ArchiveProcessor> {
|
||||||
let detector = FormatDetector::new();
|
let detector = FormatDetector::new();
|
||||||
let format = detector.detect(path)?;
|
let format = detector.detect(path)?;
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::SystemTime;
|
|
||||||
|
// Re-export types from metadata.rs
|
||||||
|
pub use crate::archive::metadata::{ArchiveMetadata, ArchiveEntry, ExtractResult};
|
||||||
|
|
||||||
/// Archive Format Type Enumeration
|
/// Archive Format Type Enumeration
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ArchiveFormat {
|
pub enum ArchiveFormat {
|
||||||
// Core formats (always enabled)
|
// Core formats (always enabled)
|
||||||
Zip,
|
Zip,
|
||||||
@@ -56,13 +58,13 @@ pub trait ArchiveProcessor: Send + Sync {
|
|||||||
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata>;
|
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata>;
|
||||||
|
|
||||||
/// List all file entries in archive
|
/// List all file entries in archive
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>>;
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>>;
|
||||||
|
|
||||||
/// Extract single file (on-demand decompression)
|
/// Extract single file (on-demand decompression)
|
||||||
fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64>;
|
fn extract_file(&mut self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64>;
|
||||||
|
|
||||||
/// Extract all files to directory (batch extraction)
|
/// Extract all files to directory (batch extraction)
|
||||||
fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult>;
|
fn extract_all(&mut self, output_dir: &Path) -> Result<ExtractResult>;
|
||||||
|
|
||||||
/// Check if this processor can handle the format
|
/// Check if this processor can handle the format
|
||||||
fn can_process(format: ArchiveFormat) -> bool where Self: Sized;
|
fn can_process(format: ArchiveFormat) -> bool where Self: Sized;
|
||||||
@@ -71,72 +73,6 @@ pub trait ArchiveProcessor: Send + Sync {
|
|||||||
fn new() -> Self where Self: Sized;
|
fn new() -> Self where Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Archive File Metadata
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ArchiveMetadata {
|
|
||||||
pub format: ArchiveFormat,
|
|
||||||
pub total_files: u64,
|
|
||||||
pub total_size: u64,
|
|
||||||
pub compressed_size: u64,
|
|
||||||
pub compression_ratio: f64,
|
|
||||||
pub is_encrypted: bool,
|
|
||||||
pub is_multi_volume: bool,
|
|
||||||
pub created_time: Option<SystemTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArchiveMetadata {
|
|
||||||
/// Calculate compression ratio
|
|
||||||
pub fn compression_ratio(&self) -> f64 {
|
|
||||||
if self.compressed_size == 0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
self.total_size as f64 / self.compressed_size as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Archive Entry Information
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ArchiveEntry {
|
|
||||||
pub path: PathBuf,
|
|
||||||
pub size: u64,
|
|
||||||
pub compressed_size: u64,
|
|
||||||
pub is_dir: bool,
|
|
||||||
pub is_file: bool,
|
|
||||||
pub is_encrypted: bool,
|
|
||||||
pub modified: SystemTime,
|
|
||||||
pub permissions: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract Result Statistics
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ExtractResult {
|
|
||||||
pub total_files: u64,
|
|
||||||
pub total_bytes: u64,
|
|
||||||
pub failed_files: Vec<PathBuf>,
|
|
||||||
pub warnings: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtractResult {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
total_files: 0,
|
|
||||||
total_bytes: 0,
|
|
||||||
failed_files: Vec::new(),
|
|
||||||
warnings: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn success_rate(&self) -> f64 {
|
|
||||||
if self.total_files == 0 {
|
|
||||||
100.0
|
|
||||||
} else {
|
|
||||||
let success_count = self.total_files - self.failed_files.len() as u64;
|
|
||||||
(success_count as f64 / self.total_files as f64) * 100.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Security Validation - Zip Slip Protection
|
/// Security Validation - Zip Slip Protection
|
||||||
pub fn validate_extraction_path(entry_path: &Path, base_dir: &Path) -> Result<PathBuf> {
|
pub fn validate_extraction_path(entry_path: &Path, base_dir: &Path) -> Result<PathBuf> {
|
||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ impl ArchiveProcessor for ZipProcessor {
|
|||||||
ArchiveFormat::Zip
|
ArchiveFormat::Zip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
archive: None,
|
||||||
|
path: PathBuf::new(),
|
||||||
|
config: ArchiveConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
||||||
info!("Opening ZIP archive: {}", path.display());
|
info!("Opening ZIP archive: {}", path.display());
|
||||||
|
|
||||||
@@ -53,8 +61,8 @@ impl ArchiveProcessor for ZipProcessor {
|
|||||||
self.archive = Some(archive);
|
self.archive = Some(archive);
|
||||||
self.path = path.to_path_buf();
|
self.path = path.to_path_buf();
|
||||||
|
|
||||||
// Extract metadata
|
// Extract metadata (need mutable reference for by_index)
|
||||||
let archive_ref = self.archive.as_ref().unwrap();
|
let archive_ref = self.archive.as_mut().unwrap();
|
||||||
let total_files = archive_ref.len() as u64;
|
let total_files = archive_ref.len() as u64;
|
||||||
|
|
||||||
let mut total_size = 0u64;
|
let mut total_size = 0u64;
|
||||||
@@ -88,11 +96,12 @@ impl ArchiveProcessor for ZipProcessor {
|
|||||||
is_encrypted: false, // TODO: Check encryption
|
is_encrypted: false, // TODO: Check encryption
|
||||||
is_multi_volume: false,
|
is_multi_volume: false,
|
||||||
created_time: Some(SystemTime::now()),
|
created_time: Some(SystemTime::now()),
|
||||||
|
modified_time: Some(SystemTime::now()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> {
|
||||||
let archive = self.archive.as_ref()
|
let archive = self.archive.as_mut()
|
||||||
.ok_or_else(|| anyhow!("Archive not opened"))?;
|
.ok_or_else(|| anyhow!("Archive not opened"))?;
|
||||||
|
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
@@ -119,8 +128,8 @@ impl ArchiveProcessor for ZipProcessor {
|
|||||||
Ok(entries)
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
fn extract_file(&mut self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
||||||
let archive = self.archive.as_ref()
|
let archive = self.archive.as_mut()
|
||||||
.ok_or_else(|| anyhow!("Archive not opened"))?;
|
.ok_or_else(|| anyhow!("Archive not opened"))?;
|
||||||
|
|
||||||
let entry_name = entry_path.to_str()
|
let entry_name = entry_path.to_str()
|
||||||
@@ -140,46 +149,56 @@ impl ArchiveProcessor for ZipProcessor {
|
|||||||
Ok(output.len() as u64)
|
Ok(output.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
|
fn extract_all(&mut self, output_dir: &Path) -> Result<ExtractResult> {
|
||||||
let archive = self.archive.as_ref()
|
|
||||||
.ok_or_else(|| anyhow!("Archive not opened"))?;
|
|
||||||
|
|
||||||
create_dir_all(output_dir)?;
|
create_dir_all(output_dir)?;
|
||||||
|
|
||||||
let mut result = ExtractResult::new();
|
let mut result = ExtractResult::new();
|
||||||
|
|
||||||
|
// Open archive if not already open
|
||||||
|
if self.archive.is_none() {
|
||||||
|
let file = File::open(&self.path)?;
|
||||||
|
let archive = zip::ZipArchive::new(file)?;
|
||||||
|
self.archive = Some(archive);
|
||||||
|
}
|
||||||
|
|
||||||
|
let archive = self.archive.as_mut().unwrap();
|
||||||
result.total_files = archive.len() as u64;
|
result.total_files = archive.len() as u64;
|
||||||
|
|
||||||
|
// Use archive iteration to extract files
|
||||||
for i in 0..archive.len() {
|
for i in 0..archive.len() {
|
||||||
let mut file = archive.by_index(i)?;
|
let mut file = archive.by_index(i)?;
|
||||||
let entry_name = file.name();
|
let entry_name = file.name().to_string();
|
||||||
let outpath = output_dir.join(entry_name);
|
let file_size = file.size();
|
||||||
|
let is_dir = entry_name.ends_with('/');
|
||||||
|
|
||||||
// Zip Slip protection
|
// Zip Slip protection
|
||||||
match validate_extraction_path(&PathBuf::from(entry_name), output_dir) {
|
match validate_extraction_path(&PathBuf::from(&entry_name), output_dir) {
|
||||||
Ok(safe_path) => {
|
Ok(safe_path) => {
|
||||||
if entry_name.ends_with('/') {
|
if is_dir {
|
||||||
// Directory
|
// Directory
|
||||||
create_dir_all(&safe_path)?;
|
create_dir_all(&safe_path)?;
|
||||||
debug!("Created directory: {}", entry_name);
|
debug!("Created directory: {}", entry_name);
|
||||||
|
result.success_files += 1;
|
||||||
} else {
|
} else {
|
||||||
// File
|
// File
|
||||||
check_file_size_limit(file.size(), self.config.max_file_size_mb * 1024 * 1024)?;
|
check_file_size_limit(file_size, self.config.max_file_size_mb * 1024 * 1024)?;
|
||||||
|
|
||||||
if let Some(parent) = safe_path.parent() {
|
if let Some(parent) = safe_path.parent() {
|
||||||
create_dir_all(parent)?;
|
create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract file content
|
||||||
let mut outfile = BufWriter::new(File::create(&safe_path)?);
|
let mut outfile = BufWriter::new(File::create(&safe_path)?);
|
||||||
std::io::copy(&mut file, &mut outfile)?;
|
std::io::copy(&mut file, &mut outfile)?;
|
||||||
|
|
||||||
result.success_files += 1;
|
result.success_files += 1;
|
||||||
result.total_bytes += file.size();
|
result.total_bytes += file_size;
|
||||||
debug!("Extracted: {} ({} bytes)", entry_name, file.size());
|
debug!("Extracted: {} ({} bytes)", entry_name, file_size);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Zip Slip detected: {} - {}", entry_name, e);
|
warn!("Zip Slip detected: {} - {}", entry_name, e);
|
||||||
result.failed_files.push(PathBuf::from(entry_name));
|
result.failed_files.push(PathBuf::from(&entry_name));
|
||||||
result.warnings.push(format!("Zip Slip: {}", entry_name));
|
result.warnings.push(format!("Zip Slip: {}", entry_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,6 +247,14 @@ impl ArchiveProcessor for TarProcessor {
|
|||||||
ArchiveFormat::Tar
|
ArchiveFormat::Tar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
path: PathBuf::new(),
|
||||||
|
entries: Vec::new(),
|
||||||
|
config: ArchiveConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
||||||
info!("Opening TAR archive: {}", path.display());
|
info!("Opening TAR archive: {}", path.display());
|
||||||
|
|
||||||
@@ -271,14 +298,15 @@ impl ArchiveProcessor for TarProcessor {
|
|||||||
is_encrypted: false,
|
is_encrypted: false,
|
||||||
is_multi_volume: false,
|
is_multi_volume: false,
|
||||||
created_time: Some(SystemTime::now()),
|
created_time: Some(SystemTime::now()),
|
||||||
|
modified_time: Some(SystemTime::now()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> {
|
||||||
Ok(self.entries.clone())
|
Ok(self.entries.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
fn extract_file(&mut self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
||||||
// TAR doesn't support random access, need to unpack entire archive
|
// TAR doesn't support random access, need to unpack entire archive
|
||||||
// This is a limitation - for single file extraction, we unpack everything
|
// This is a limitation - for single file extraction, we unpack everything
|
||||||
warn!("TAR format doesn't support random access - extracting entire archive");
|
warn!("TAR format doesn't support random access - extracting entire archive");
|
||||||
@@ -294,7 +322,7 @@ impl ArchiveProcessor for TarProcessor {
|
|||||||
Ok(output.len() as u64)
|
Ok(output.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
|
fn extract_all(&mut self, output_dir: &Path) -> Result<ExtractResult> {
|
||||||
create_dir_all(output_dir)?;
|
create_dir_all(output_dir)?;
|
||||||
|
|
||||||
let file = File::open(&self.path)?;
|
let file = File::open(&self.path)?;
|
||||||
@@ -304,8 +332,9 @@ impl ArchiveProcessor for TarProcessor {
|
|||||||
result.total_files = self.entries.len() as u64;
|
result.total_files = self.entries.len() as u64;
|
||||||
|
|
||||||
for entry in archive.entries()? {
|
for entry in archive.entries()? {
|
||||||
let entry = entry?;
|
let mut entry = entry?;
|
||||||
let entry_path = entry.path()?.to_path_buf();
|
let entry_path = entry.path()?.to_path_buf();
|
||||||
|
let entry_path_str = entry_path.display().to_string(); // Save for warning
|
||||||
|
|
||||||
// Zip Slip protection
|
// Zip Slip protection
|
||||||
match validate_extraction_path(&entry_path, output_dir) {
|
match validate_extraction_path(&entry_path, output_dir) {
|
||||||
@@ -318,9 +347,9 @@ impl ArchiveProcessor for TarProcessor {
|
|||||||
result.total_bytes += entry.size();
|
result.total_bytes += entry.size();
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Zip Slip detected: {} - {}", entry_path.display(), e);
|
warn!("Zip Slip detected: {} - {}", entry_path_str, e);
|
||||||
result.failed_files.push(entry_path);
|
result.failed_files.push(entry_path);
|
||||||
result.warnings.push(format!("Zip Slip: {}", entry_path.display()));
|
result.warnings.push(format!("Zip Slip: {}", entry_path_str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,6 +395,14 @@ impl ArchiveProcessor for GzipProcessor {
|
|||||||
ArchiveFormat::Gzip
|
ArchiveFormat::Gzip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
path: PathBuf::new(),
|
||||||
|
decompressed_size: 0,
|
||||||
|
config: ArchiveConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
||||||
info!("Opening GZIP archive: {}", path.display());
|
info!("Opening GZIP archive: {}", path.display());
|
||||||
|
|
||||||
@@ -396,10 +433,11 @@ impl ArchiveProcessor for GzipProcessor {
|
|||||||
is_encrypted: false,
|
is_encrypted: false,
|
||||||
is_multi_volume: false,
|
is_multi_volume: false,
|
||||||
created_time: Some(SystemTime::now()),
|
created_time: Some(SystemTime::now()),
|
||||||
|
modified_time: Some(SystemTime::now()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> {
|
||||||
// GZIP is single file - infer name from archive name
|
// GZIP is single file - infer name from archive name
|
||||||
let name = self.path.file_name()
|
let name = self.path.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
@@ -414,7 +452,7 @@ impl ArchiveProcessor for GzipProcessor {
|
|||||||
)])
|
)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
fn extract_file(&mut self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
||||||
// GZIP is single file - just decompress it
|
// GZIP is single file - just decompress it
|
||||||
let file = File::open(&self.path)?;
|
let file = File::open(&self.path)?;
|
||||||
let mut decoder = flate2::read::GzDecoder::new(file);
|
let mut decoder = flate2::read::GzDecoder::new(file);
|
||||||
@@ -428,7 +466,7 @@ impl ArchiveProcessor for GzipProcessor {
|
|||||||
Ok(output.len() as u64)
|
Ok(output.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
|
fn extract_all(&mut self, output_dir: &Path) -> Result<ExtractResult> {
|
||||||
create_dir_all(output_dir)?;
|
create_dir_all(output_dir)?;
|
||||||
|
|
||||||
let entries = self.list_entries()?;
|
let entries = self.list_entries()?;
|
||||||
@@ -497,6 +535,13 @@ impl ArchiveProcessor for TarGzipProcessor {
|
|||||||
ArchiveFormat::TarGzip
|
ArchiveFormat::TarGzip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
gzip_processor: GzipProcessor::new(),
|
||||||
|
config: ArchiveConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, path: &Path) -> Result<ArchiveMetadata> {
|
||||||
info!("Opening TAR.GZ archive: {}", path.display());
|
info!("Opening TAR.GZ archive: {}", path.display());
|
||||||
|
|
||||||
@@ -528,16 +573,17 @@ impl ArchiveProcessor for TarGzipProcessor {
|
|||||||
is_encrypted: false,
|
is_encrypted: false,
|
||||||
is_multi_volume: false,
|
is_multi_volume: false,
|
||||||
created_time: Some(SystemTime::now()),
|
created_time: Some(SystemTime::now()),
|
||||||
|
modified_time: Some(SystemTime::now()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> {
|
||||||
// Need to implement properly - this requires decompressing first
|
// Need to implement properly - this requires decompressing first
|
||||||
warn!("TAR.GZ list_entries requires full decompression - consider extract_all instead");
|
warn!("TAR.GZ list_entries requires full decompression - consider extract_all instead");
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_file(&self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
fn extract_file(&mut self, entry_path: &Path, output: &mut Vec<u8>) -> Result<u64> {
|
||||||
warn!("TAR.GZ extract_file requires full unpacking - inefficient for single file");
|
warn!("TAR.GZ extract_file requires full unpacking - inefficient for single file");
|
||||||
|
|
||||||
let temp_dir = tempfile::tempdir()?;
|
let temp_dir = tempfile::tempdir()?;
|
||||||
@@ -551,7 +597,7 @@ impl ArchiveProcessor for TarGzipProcessor {
|
|||||||
Ok(output.len() as u64)
|
Ok(output.len() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_all(&self, output_dir: &Path) -> Result<ExtractResult> {
|
fn extract_all(&mut self, output_dir: &Path) -> Result<ExtractResult> {
|
||||||
info!("Extracting TAR.GZ to: {}", output_dir.display());
|
info!("Extracting TAR.GZ to: {}", output_dir.display());
|
||||||
|
|
||||||
// Step 1: Decompress GZIP to temp
|
// Step 1: Decompress GZIP to temp
|
||||||
@@ -585,9 +631,9 @@ impl ArchiveProcessor for ZstdProcessor {
|
|||||||
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
||||||
Err(anyhow!("ZSTD processor not yet implemented"))
|
Err(anyhow!("ZSTD processor not yet implemented"))
|
||||||
}
|
}
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
||||||
fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
fn extract_file(&mut self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
||||||
fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
fn extract_all(&mut self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
||||||
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Zstd }
|
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Zstd }
|
||||||
fn new() -> Self { Self }
|
fn new() -> Self { Self }
|
||||||
}
|
}
|
||||||
@@ -600,9 +646,9 @@ impl ArchiveProcessor for Bzip2Processor {
|
|||||||
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
||||||
Err(anyhow!("BZIP2 processor not yet implemented"))
|
Err(anyhow!("BZIP2 processor not yet implemented"))
|
||||||
}
|
}
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
||||||
fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
fn extract_file(&mut self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
||||||
fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
fn extract_all(&mut self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
||||||
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Bzip2 }
|
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Bzip2 }
|
||||||
fn new() -> Self { Self }
|
fn new() -> Self { Self }
|
||||||
}
|
}
|
||||||
@@ -615,9 +661,9 @@ impl ArchiveProcessor for Lz4Processor {
|
|||||||
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
||||||
Err(anyhow!("LZ4 processor not yet implemented"))
|
Err(anyhow!("LZ4 processor not yet implemented"))
|
||||||
}
|
}
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
||||||
fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
fn extract_file(&mut self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
||||||
fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
fn extract_all(&mut self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
||||||
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Lz4 }
|
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::Lz4 }
|
||||||
fn new() -> Self { Self }
|
fn new() -> Self { Self }
|
||||||
}
|
}
|
||||||
@@ -630,9 +676,9 @@ impl ArchiveProcessor for TarBzip2Processor {
|
|||||||
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
||||||
Err(anyhow!("TAR.BZ2 processor not yet implemented"))
|
Err(anyhow!("TAR.BZ2 processor not yet implemented"))
|
||||||
}
|
}
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
||||||
fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
fn extract_file(&mut self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
||||||
fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
fn extract_all(&mut self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
||||||
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::TarBzip2 }
|
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::TarBzip2 }
|
||||||
fn new() -> Self { Self }
|
fn new() -> Self { Self }
|
||||||
}
|
}
|
||||||
@@ -645,9 +691,9 @@ impl ArchiveProcessor for TarZstdProcessor {
|
|||||||
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
fn open(&mut self, _path: &Path) -> Result<ArchiveMetadata> {
|
||||||
Err(anyhow!("TAR.ZST processor not yet implemented"))
|
Err(anyhow!("TAR.ZST processor not yet implemented"))
|
||||||
}
|
}
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> { Ok(Vec::new()) }
|
||||||
fn extract_file(&self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
fn extract_file(&mut self, _entry: &Path, _output: &mut Vec<u8>) -> Result<u64> { Ok(0) }
|
||||||
fn extract_all(&self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
fn extract_all(&mut self, _dir: &Path) -> Result<ExtractResult> { Ok(ExtractResult::new()) }
|
||||||
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::TarZstd }
|
fn can_process(format: ArchiveFormat) -> bool { format == ArchiveFormat::TarZstd }
|
||||||
fn new() -> Self { Self }
|
fn new() -> Self { Self }
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ impl ArchiveProcessor for RarProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> {
|
||||||
use unrar::Archive;
|
use unrar::Archive;
|
||||||
|
|
||||||
let path = self.archive_path.as_ref().ok_or_else(|| anyhow!("Archive not opened"))?;
|
let path = self.archive_path.as_ref().ok_or_else(|| anyhow!("Archive not opened"))?;
|
||||||
@@ -202,7 +202,7 @@ impl ArchiveProcessor for XzProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> {
|
||||||
// XZ is single-file, infer filename from archive name
|
// XZ is single-file, infer filename from archive name
|
||||||
let path = self.archive_path.as_ref().ok_or_else(|| anyhow!("Archive not opened"))?;
|
let path = self.archive_path.as_ref().ok_or_else(|| anyhow!("Archive not opened"))?;
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ impl ArchiveProcessor for SevenZProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_entries(&self) -> Result<Vec<ArchiveEntry>> {
|
fn list_entries(&mut self) -> Result<Vec<ArchiveEntry>> {
|
||||||
// Note: sevenz-rust doesn't have full entry listing yet
|
// Note: sevenz-rust doesn't have full entry listing yet
|
||||||
// This is a stub returning empty list
|
// This is a stub returning empty list
|
||||||
warn!("7z list_entries not fully implemented (library limitation)");
|
warn!("7z list_entries not fully implemented (library limitation)");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod s3_config;
|
|||||||
pub mod s3_xml;
|
pub mod s3_xml;
|
||||||
pub mod scan;
|
pub mod scan;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
pub mod archive; // Archive Module - Universal Compression Format Support (Phase 1-3完成)
|
||||||
// pub mod sftp; // ⚠️ russh版本(已禁用)
|
// pub mod sftp; // ⚠️ russh版本(已禁用)
|
||||||
// pub mod ssh2_server; // ssh2服务器(已禁用)
|
// pub mod ssh2_server; // ssh2服务器(已禁用)
|
||||||
// pub mod ssh2_mod; // ssh2辅助模块(已禁用)
|
// pub mod ssh2_mod; // ssh2辅助模块(已禁用)
|
||||||
|
|||||||
+835
-239
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user