mod path;
mod root;
use std::path::Path;
use self::{path::decode_member, root::ExtractionRoot};
use super::*;
#[derive(Clone, Copy, Debug)]
pub struct ExtractPolicy {
pub(crate) link_policy: LinkPolicy,
pub(crate) allow_overwrites: bool,
pub(crate) name_validation: crate::name::NameValidation,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LinkPolicy {
pub(crate) symlink_policy: SymlinkPolicy,
pub(crate) allow_hard_links: bool,
pub(crate) allow_ambient_targets: bool,
pub(crate) allow_missing_targets: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum SymlinkPolicy {
#[default]
Preserve,
Skip,
Reject,
}
impl Default for ExtractPolicy {
fn default() -> Self {
Self {
link_policy: LinkPolicy::default(),
allow_overwrites: true,
name_validation: crate::name::NameValidation::Default,
}
}
}
impl Default for LinkPolicy {
fn default() -> Self {
Self {
symlink_policy: SymlinkPolicy::default(),
allow_hard_links: false,
allow_ambient_targets: false,
allow_missing_targets: true,
}
}
}
impl ExtractPolicy {
pub fn link_policy(mut self, policy: LinkPolicy) -> Self {
self.link_policy = policy;
self
}
pub fn allow_overwrites(mut self, allow: bool) -> Self {
self.allow_overwrites = allow;
self
}
pub fn name_validator(mut self, validator: Option<NameValidator>) -> Self {
self.name_validation = crate::name::NameValidation::from_validator(validator);
self
}
fn check_name<E>(
self,
position: u64,
context: &'static str,
value: &str,
) -> Result<(), ExtractError<E>> {
if !self.name_validation.accepts(value) {
return Err(ExtractError::policy_violation(
position,
ExtractPolicyViolation::NameRejected {
context,
value: value.to_owned(),
},
));
}
Ok(())
}
}
impl LinkPolicy {
pub fn symlink_policy(mut self, policy: SymlinkPolicy) -> Self {
self.symlink_policy = policy;
self
}
pub fn allow_hard_links(mut self, allow: bool) -> Self {
self.allow_hard_links = allow;
self
}
pub fn allow_ambient_targets(mut self, allow: bool) -> Self {
self.allow_ambient_targets = allow;
self
}
pub fn allow_missing_targets(mut self, allow: bool) -> Self {
self.allow_missing_targets = allow;
self
}
}
pub(crate) async fn extract<A: Archive>(
mut members: Members<A>,
destination: &Path,
policy: ExtractPolicy,
) -> Result<(), ExtractError<A::Error>> {
let mut root = ExtractionRoot::<A::Error>::open(destination, policy.allow_overwrites).await?;
let mut chunk_buffer = Vec::new();
let mut buffered_payload = Vec::new();
let result: Result<(), ExtractError<A::Error>> = async {
while let Some(member) = members.next().await.map_err(ExtractError::Archive)? {
check_member_policy(&member, policy)?;
let decoded = decode_member(&member, policy)?;
match member {
Member::File {
size,
executable,
payload,
..
} => {
root.extract_file(
&decoded.path,
size,
executable,
payload,
&mut chunk_buffer,
&mut buffered_payload,
)
.await?;
}
Member::Directory { .. } => root.extract_directory(&decoded.path).await?,
Member::SymbolicLink { .. } => {
if policy.link_policy.symlink_policy == SymlinkPolicy::Preserve {
root.reserve_symlink(&decoded).await?;
}
}
Member::HardLink { size, payload, .. } => {
root.extract_hard_link(&decoded, size, payload, &mut chunk_buffer)
.await?;
}
Member::Special { kind, .. } => {
return Err(ExtractError::UnsupportedMember {
position: decoded.position,
path: decoded.path.to_path_buf(),
kind,
});
}
}
}
Ok(())
}
.await;
root.flush_buffered_files().await?;
result?;
root.finalize_symlinks(policy.link_policy).await
}
fn check_member_policy<E, P>(
member: &Member<P>,
policy: ExtractPolicy,
) -> Result<(), ExtractError<E>> {
let position = member.metadata().position;
match member {
Member::SymbolicLink { .. } => {
let violation = match policy.link_policy.symlink_policy {
SymlinkPolicy::Reject => Some(ExtractPolicyViolation::SymbolicLink),
#[cfg(not(unix))]
SymlinkPolicy::Preserve => {
Some(ExtractPolicyViolation::NativeSymlinkCreationUnsupported)
}
_ => None,
};
if let Some(violation) = violation {
return Err(ExtractError::policy_violation(position, violation));
}
}
Member::HardLink { .. } if !policy.link_policy.allow_hard_links => {
return Err(ExtractError::policy_violation(
position,
ExtractPolicyViolation::HardLink,
));
}
_ => {}
}
Ok(())
}