clean up
This commit is contained in:
173
fix/src/fetcher/archive.rs
Normal file
173
fix/src/fetcher/archive.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user