use std::io::Cursor;
use std::path::{Component, Path};
use flate2::read::GzDecoder;
use tar::Archive;
use super::InstallError;
pub fn extract_tarball(bytes: &[u8], dest: &Path) -> Result<(), InstallError> {
{
let gz = GzDecoder::new(Cursor::new(bytes));
let mut archive = Archive::new(gz);
let entries = archive.entries().map_err(|source| InstallError::Extract {
context: "read tar entries".to_owned(),
source,
})?;
for entry in entries {
let entry = entry.map_err(|source| InstallError::Extract {
context: "read tar entry".to_owned(),
source,
})?;
let path = entry.path().map_err(|source| InstallError::Extract {
context: "decode tar entry path".to_owned(),
source,
})?;
reject_unsafe_path(&path)?;
}
}
let gz = GzDecoder::new(Cursor::new(bytes));
let mut archive = Archive::new(gz);
archive
.unpack(dest)
.map_err(|source| InstallError::Extract {
context: format!("unpack tarball into {}", dest.display()),
source,
})?;
Ok(())
}
fn reject_unsafe_path(path: &Path) -> Result<(), InstallError> {
for component in path.components() {
match component {
Component::Normal(_) | Component::CurDir => {}
Component::ParentDir => {
return Err(InstallError::Extract {
context: format!("tar entry uses `..` path traversal: {}", path.display(),),
source: std::io::Error::other("unsafe tar entry path"),
});
}
Component::RootDir | Component::Prefix(_) => {
return Err(InstallError::Extract {
context: format!("tar entry uses absolute path: {}", path.display(),),
source: std::io::Error::other("absolute tar entry path"),
});
}
}
}
Ok(())
}