This commit is contained in:
2026-03-12 17:47:46 +08:00
parent 7a7229d70e
commit 0c9a391618
511 changed files with 234 additions and 12772 deletions

173
fix/src/fetcher/archive.rs Normal file
View File

@@ -0,0 +1,173 @@
use std::fs;
use std::io::Cursor;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use flate2::read::GzDecoder;
#[derive(Debug, Clone, Copy)]
pub enum ArchiveFormat {
TarGz,
TarXz,
TarBz2,
Tar,
}
impl ArchiveFormat {
pub fn detect(url: &str, data: &[u8]) -> Self {
if url.ends_with(".tar.gz") || url.ends_with(".tgz") {
return ArchiveFormat::TarGz;
}
if url.ends_with(".tar.xz") || url.ends_with(".txz") {
return ArchiveFormat::TarXz;
}
if url.ends_with(".tar.bz2") || url.ends_with(".tbz2") {
return ArchiveFormat::TarBz2;
}
if url.ends_with(".tar") {
return ArchiveFormat::Tar;
}
if data.len() >= 2 && data[0] == 0x1f && data[1] == 0x8b {
return ArchiveFormat::TarGz;
}
if data.len() >= 6 && &data[0..6] == b"\xfd7zXZ\x00" {
return ArchiveFormat::TarXz;
}
if data.len() >= 3 && &data[0..3] == b"BZh" {
return ArchiveFormat::TarBz2;
}
ArchiveFormat::TarGz
}
}
pub fn extract_tarball(data: &[u8], dest: &Path) -> Result<PathBuf, ArchiveError> {
let format = ArchiveFormat::detect("", data);
let temp_dir = dest.join("_extract_temp");
fs::create_dir_all(&temp_dir)?;
match format {
ArchiveFormat::TarGz => extract_tar_gz(data, &temp_dir)?,
ArchiveFormat::TarXz => extract_tar_xz(data, &temp_dir)?,
ArchiveFormat::TarBz2 => extract_tar_bz2(data, &temp_dir)?,
ArchiveFormat::Tar => extract_tar(data, &temp_dir)?,
}
strip_single_toplevel(&temp_dir, dest)
}
fn extract_tar_gz(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let decoder = GzDecoder::new(Cursor::new(data));
let mut archive = tar::Archive::new(decoder);
archive.unpack(dest)?;
Ok(())
}
fn extract_tar_xz(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let decoder = xz2::read::XzDecoder::new(Cursor::new(data));
let mut archive = tar::Archive::new(decoder);
archive.unpack(dest)?;
Ok(())
}
fn extract_tar_bz2(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let decoder = bzip2::read::BzDecoder::new(Cursor::new(data));
let mut archive = tar::Archive::new(decoder);
archive.unpack(dest)?;
Ok(())
}
fn extract_tar(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let mut archive = tar::Archive::new(Cursor::new(data));
archive.unpack(dest)?;
Ok(())
}
fn strip_single_toplevel(temp_dir: &Path, dest: &Path) -> Result<PathBuf, ArchiveError> {
let entries: Vec<_> = fs::read_dir(temp_dir)?
.filter_map(|e| e.ok())
.filter(|e| e.file_name().as_os_str().as_bytes()[0] != b'.')
.collect();
let source_dir = if entries.len() == 1 && entries[0].file_type()?.is_dir() {
entries[0].path()
} else {
temp_dir.to_path_buf()
};
let final_dest = dest.join("content");
if final_dest.exists() {
fs::remove_dir_all(&final_dest)?;
}
if source_dir == *temp_dir {
fs::rename(temp_dir, &final_dest)?;
} else {
copy_dir_recursive(&source_dir, &final_dest)?;
fs::remove_dir_all(temp_dir)?;
}
Ok(final_dest)
}
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), std::io::Error> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let path = entry.path();
let dest_path = dst.join(entry.file_name());
let metadata = fs::symlink_metadata(&path)?;
if metadata.is_symlink() {
let target = fs::read_link(&path)?;
#[cfg(unix)]
{
std::os::unix::fs::symlink(&target, &dest_path)?;
}
#[cfg(windows)]
{
if target.is_dir() {
std::os::windows::fs::symlink_dir(&target, &dest_path)?;
} else {
std::os::windows::fs::symlink_file(&target, &dest_path)?;
}
}
} else if metadata.is_dir() {
copy_dir_recursive(&path, &dest_path)?;
} else {
fs::copy(&path, &dest_path)?;
}
}
Ok(())
}
pub fn extract_tarball_to_temp(data: &[u8]) -> Result<(PathBuf, tempfile::TempDir), ArchiveError> {
let temp_dir = tempfile::tempdir()?;
let extracted_path = extract_tarball(data, temp_dir.path())?;
Ok((extracted_path, temp_dir))
}
#[derive(Debug)]
pub enum ArchiveError {
IoError(std::io::Error),
}
impl std::fmt::Display for ArchiveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArchiveError::IoError(e) => write!(f, "I/O error: {}", e),
}
}
}
impl std::error::Error for ArchiveError {}
impl From<std::io::Error> for ArchiveError {
fn from(e: std::io::Error) -> Self {
ArchiveError::IoError(e)
}
}