Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

entry: convert hard link absolute paths to relative ones #248

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/entry.rs
Expand Up @@ -433,6 +433,21 @@ impl<'a> EntryFields<'a> {
})
}

// on Windows one path may be using the extended length syntax while another
// is not, so we remove any prefix before comparing them.
fn path_has_base(&self, path: &Path, base: &Path) -> bool {
let path: PathBuf = path
.components()
.skip_while(|c| matches!(*c, Component::Prefix(_)))
.collect();
let base: PathBuf = base
.components()
.skip_while(|c| matches!(*c, Component::Prefix(_)))
.collect();

path.starts_with(&base)
}

/// Returns access to the header of this entry in the archive.
fn unpack(&mut self, target_base: Option<&Path>, dst: &Path) -> io::Result<Unpacked> {
let kind = self.header.entry_type();
Expand Down Expand Up @@ -467,6 +482,9 @@ impl<'a> EntryFields<'a> {
// the destination of this hard link is both present and
// inside our own directory. This is needed because we want
// to make sure to not overwrite anything outside the root.
// If the link points to an absolute path, assume it's relative
// to the target dir by removing the leading '/', as we already
// do for files, see unpack_in() comments.
//
// Note that this logic is only needed for hard links
// currently. With symlinks the `validate_inside_dst` which
Expand All @@ -475,6 +493,21 @@ impl<'a> EntryFields<'a> {
// links though they're canonicalized to their existing path
// so we need to validate at this time.
Some(ref p) => {
let mut src = src.to_path_buf();
if src.is_absolute() {
let dest_canon = p.canonicalize()?;
if !self.path_has_base(&src, &dest_canon) {
// Skip root component, making the target relative to the target dir.
// Also skip prefix because on Windows the path may use the extended syntax.
src = src
.components()
.skip_while(|c| {
matches!(*c, Component::RootDir | Component::Prefix(_))
})
.collect();
}
}

let link_src = p.join(src);
self.validate_inside_dst(p, &link_src)?;
link_src
Expand Down
28 changes: 26 additions & 2 deletions tests/entry.rs
Expand Up @@ -44,6 +44,16 @@ fn absolute_symlink() {
#[test]
fn absolute_hardlink() {
let td = t!(Builder::new().prefix("tar").tempdir());
// on macos the tempdir is created in /var/folders/ which is actually
// /private/var/folders/. Canonicalize the path so the link target has
// the proper base. Do not do this on Windows as canonicalize() will
// convert the path to the extended length syntax which cannot be used
// in the tarball paths.
let td_canon = if cfg!(unix) {
td.path().canonicalize().unwrap()
} else {
td.path().to_path_buf()
};
let mut ar = tar::Builder::new(Vec::new());

let mut header = tar::Header::new_gnu();
Expand All @@ -58,16 +68,30 @@ fn absolute_hardlink() {
header.set_entry_type(tar::EntryType::Link);
t!(header.set_path("bar"));
// This absolute path under tempdir will be created at unpack time
t!(header.set_link_name(td.path().join("foo")));
t!(header.set_link_name(td_canon.join("foo")));
header.set_cksum();
t!(ar.append(&header, &[][..]));

let mut header = tar::Header::new_gnu();
header.set_size(0);
header.set_entry_type(tar::EntryType::Link);
t!(header.set_path("baz"));
// This absolute path on root will be converted when unpacking
if cfg!(unix) {
t!(header.set_link_name("/foo"));
} else {
t!(header.set_link_name("C:\\foo"));
}
header.set_cksum();
t!(ar.append(&header, &[][..]));
gdesmott marked this conversation as resolved.
Show resolved Hide resolved

let bytes = t!(ar.into_inner());
let mut ar = tar::Archive::new(&bytes[..]);

t!(ar.unpack(td.path()));
t!(ar.unpack(&td_canon));
t!(td.path().join("foo").metadata());
t!(td.path().join("bar").metadata());
t!(td.path().join("baz").metadata());
}

#[test]
Expand Down