Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 68 additions & 16 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub struct Builder<W: Write> {
#[derive(Clone, Copy)]
struct BuilderOptions {
mode: HeaderMode,
preserve_absolute: bool,
follow: bool,
sparse: bool,
}
Expand All @@ -35,6 +36,7 @@ impl<W: Write> Builder<W> {
Builder {
options: BuilderOptions {
mode: HeaderMode::Complete,
preserve_absolute: false,
follow: true,
sparse: true,
},
Expand All @@ -50,6 +52,11 @@ impl<W: Write> Builder<W> {
self.options.mode = mode;
}

/// Peserve absolute path while creating an archive
pub fn preserve_absolute(&mut self, preserve: bool) {
self.options.preserve_absolute = preserve;
}

/// Follow symlinks, archiving the contents of the file they point to rather
/// than adding a symlink to the archive. Defaults to true.
///
Expand Down Expand Up @@ -179,7 +186,8 @@ impl<W: Write> Builder<W> {
path: P,
data: R,
) -> io::Result<()> {
prepare_header_path(self.get_mut(), header, path.as_ref())?;
let allow_absolute = self.options.preserve_absolute;
prepare_header_path(self.get_mut(), header, path.as_ref(), allow_absolute)?;
header.set_cksum();
self.append(header, data)
}
Expand Down Expand Up @@ -222,7 +230,8 @@ impl<W: Write> Builder<W> {
where
W: Seek,
{
EntryWriter::start(self.get_mut(), header, path.as_ref())
let allow_absolute = self.options.preserve_absolute;
EntryWriter::start(self.get_mut(), header, path.as_ref(), allow_absolute)
}

/// Adds a new link (symbolic or hard) entry to this archive with the specified path and target.
Expand Down Expand Up @@ -267,7 +276,8 @@ impl<W: Write> Builder<W> {
}

fn _append_link(&mut self, header: &mut Header, path: &Path, target: &Path) -> io::Result<()> {
prepare_header_path(self.get_mut(), header, path)?;
let allow_abolute = self.options.preserve_absolute;
prepare_header_path(self.get_mut(), header, path, allow_abolute)?;
prepare_header_link(self.get_mut(), header, target)?;
header.set_cksum();
self.append(header, std::io::empty())
Expand Down Expand Up @@ -515,8 +525,9 @@ impl EntryWriter<'_> {
obj: &'a mut dyn SeekWrite,
header: &'a mut Header,
path: &Path,
allow_absolute: bool,
) -> io::Result<EntryWriter<'a>> {
prepare_header_path(obj.as_write(), header, path)?;
prepare_header_path(obj.as_write(), header, path, allow_absolute)?;

// Reserve space for header, will be overwritten once data is written.
obj.write_all([0u8; BLOCK_SIZE as usize].as_ref())?;
Expand Down Expand Up @@ -624,14 +635,28 @@ fn append_path_with_name(
if stat.is_file() {
append_file(dst, ar_name, &mut fs::File::open(path)?, options)
} else if stat.is_dir() {
append_fs(dst, ar_name, &stat, options.mode, None)
append_fs(
dst,
ar_name,
&stat,
options.mode,
options.preserve_absolute,
None,
)
} else if stat.file_type().is_symlink() {
let link_name = fs::read_link(path)?;
append_fs(dst, ar_name, &stat, options.mode, Some(&link_name))
append_fs(
dst,
ar_name,
&stat,
options.mode,
options.preserve_absolute,
Some(&link_name),
)
} else {
#[cfg(unix)]
{
append_special(dst, path, &stat, options.mode)
append_special(dst, path, &stat, options.mode, options.preserve_absolute)
}
#[cfg(not(unix))]
{
Expand All @@ -646,6 +671,7 @@ fn append_special(
path: &Path,
stat: &fs::Metadata,
mode: HeaderMode,
allow_absolute: bool,
) -> io::Result<()> {
use ::std::os::unix::fs::{FileTypeExt, MetadataExt};

Expand All @@ -669,7 +695,7 @@ fn append_special(

let mut header = Header::new_gnu();
header.set_metadata_in_mode(stat, mode);
prepare_header_path(dst, &mut header, path)?;
prepare_header_path(dst, &mut header, path, allow_absolute)?;

header.set_entry_type(entry_type);
let dev_id = stat.rdev();
Expand All @@ -693,7 +719,7 @@ fn append_file(
let stat = file.metadata()?;
let mut header = Header::new_gnu();

prepare_header_path(dst, &mut header, path)?;
prepare_header_path(dst, &mut header, path, options.preserve_absolute)?;
header.set_metadata_in_mode(&stat, options.mode);
let sparse_entries = if options.sparse {
prepare_header_sparse(file, &stat, &mut header)?
Expand Down Expand Up @@ -725,7 +751,14 @@ fn append_dir(
options: BuilderOptions,
) -> io::Result<()> {
let stat = fs::metadata(src_path)?;
append_fs(dst, path, &stat, options.mode, None)
append_fs(
dst,
path,
&stat,
options.mode,
options.preserve_absolute,
None,
)
}

fn prepare_header(size: u64, entry_type: u8) -> Header {
Expand All @@ -743,12 +776,23 @@ fn prepare_header(size: u64, entry_type: u8) -> Header {
header
}

fn prepare_header_path(dst: &mut dyn Write, header: &mut Header, path: &Path) -> io::Result<()> {
fn prepare_header_path(
dst: &mut dyn Write,
header: &mut Header,
path: &Path,
allow_absolute: bool,
) -> io::Result<()> {
// Try to encode the path directly in the header, but if it ends up not
// working (probably because it's too long) then try to use the GNU-specific
// long name extension by emitting an entry which indicates that it's the
// filename.
if let Err(e) = header.set_path(path) {
let result = if allow_absolute {
header.set_path_absolute(path)
} else {
header.set_path(path)
};

if let Err(e) = result {
let data = path2bytes(path)?;
let max = header.as_old().name.len();
// Since `e` isn't specific enough to let us know the path is indeed too
Expand All @@ -769,7 +813,7 @@ fn prepare_header_path(dst: &mut dyn Write, header: &mut Header, path: &Path) ->
Ok(s) => s,
Err(e) => str::from_utf8(&data[..e.valid_up_to()]).unwrap(),
};
header.set_truncated_path_for_gnu_header(truncated)?;
header.set_truncated_path_for_gnu_header(truncated, allow_absolute)?;

let header2 = prepare_header(data.len() as u64, b'L');
// null-terminated string
Expand Down Expand Up @@ -859,11 +903,12 @@ fn append_fs(
path: &Path,
meta: &fs::Metadata,
mode: HeaderMode,
allow_absolute: bool,
link_name: Option<&Path>,
) -> io::Result<()> {
let mut header = Header::new_gnu();

prepare_header_path(dst, &mut header, path)?;
prepare_header_path(dst, &mut header, path, allow_absolute)?;
header.set_metadata_in_mode(meta, mode);
if let Some(link_name) = link_name {
prepare_header_link(dst, &mut header, link_name)?;
Expand Down Expand Up @@ -894,13 +939,20 @@ fn append_dir_all(
} else if !options.follow && is_symlink {
let stat = fs::symlink_metadata(&src)?;
let link_name = fs::read_link(&src)?;
append_fs(dst, &dest, &stat, options.mode, Some(&link_name))?;
append_fs(
dst,
&dest,
&stat,
options.mode,
options.preserve_absolute,
Some(&link_name),
)?;
} else {
#[cfg(unix)]
{
let stat = fs::metadata(&src)?;
if !stat.is_file() {
append_special(dst, &dest, &stat, options.mode)?;
append_special(dst, &dest, &stat, options.mode, options.preserve_absolute)?;
continue;
}
}
Expand Down
80 changes: 58 additions & 22 deletions src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,14 @@ impl Header {
/// use `Builder` methods to insert a long-name extension at the same time
/// as the file content.
pub fn set_path<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> {
self.set_path_inner(p.as_ref(), false)
self.set_path_inner(p.as_ref(), false, false)
}

/// Sets the path name for this header.
///
/// Same as set_path but allows abosolut paths
pub fn set_path_absolute<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> {
self.set_path_inner(p.as_ref(), false, true)
}

// Sets the truncated path for GNU header
Expand All @@ -396,18 +403,28 @@ impl Header {
pub(crate) fn set_truncated_path_for_gnu_header<P: AsRef<Path>>(
&mut self,
p: P,
allow_absolute: bool,
) -> io::Result<()> {
self.set_path_inner(p.as_ref(), true)
self.set_path_inner(p.as_ref(), true, allow_absolute)
}

fn set_path_inner(&mut self, path: &Path, is_truncated_gnu_long_path: bool) -> io::Result<()> {
fn set_path_inner(
&mut self,
path: &Path,
is_truncated_gnu_long_path: bool,
allow_absolute: bool,
) -> io::Result<()> {
if let Some(ustar) = self.as_ustar_mut() {
return ustar.set_path(path);
return if allow_absolute {
ustar.set_path_absolute(path)
} else {
ustar.set_path(path)
};
}
if is_truncated_gnu_long_path {
copy_path_into_gnu_long(&mut self.as_old_mut().name, path, false)
copy_path_into_gnu_long(&mut self.as_old_mut().name, path, false, allow_absolute)
} else {
copy_path_into(&mut self.as_old_mut().name, path, false)
copy_path_into(&mut self.as_old_mut().name, path, false, allow_absolute)
}
.map_err(|err| {
io::Error::new(
Expand Down Expand Up @@ -461,7 +478,7 @@ impl Header {
}

fn _set_link_name(&mut self, path: &Path) -> io::Result<()> {
copy_path_into(&mut self.as_old_mut().linkname, path, true).map_err(|err| {
copy_path_into(&mut self.as_old_mut().linkname, path, true, true).map_err(|err| {
io::Error::new(
err.kind(),
format!("{} when setting link name for {}", err, self.path_lossy()),
Expand Down Expand Up @@ -994,10 +1011,15 @@ impl UstarHeader {

/// See `Header::set_path`
pub fn set_path<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> {
self._set_path(p.as_ref())
self._set_path(p.as_ref(), false)
}

fn _set_path(&mut self, path: &Path) -> io::Result<()> {
/// See `Header::set_path_absolute`
pub fn set_path_absolute<P: AsRef<Path>>(&mut self, p: P) -> io::Result<()> {
self._set_path(p.as_ref(), true)
}

fn _set_path(&mut self, path: &Path, allow_absolute: bool) -> io::Result<()> {
// This can probably be optimized quite a bit more, but for now just do
// something that's relatively easy and readable.
//
Expand All @@ -1009,7 +1031,7 @@ impl UstarHeader {
let bytes = path2bytes(path)?;
let (maxnamelen, maxprefixlen) = (self.name.len(), self.prefix.len());
if bytes.len() <= maxnamelen {
copy_path_into(&mut self.name, path, false).map_err(|err| {
copy_path_into(&mut self.name, path, false, allow_absolute).map_err(|err| {
io::Error::new(
err.kind(),
format!("{} when setting path for {}", err, self.path_lossy()),
Expand All @@ -1033,14 +1055,14 @@ impl UstarHeader {
break;
}
}
copy_path_into(&mut self.prefix, prefix, false).map_err(|err| {
copy_path_into(&mut self.prefix, prefix, false, allow_absolute).map_err(|err| {
io::Error::new(
err.kind(),
format!("{} when setting path for {}", err, self.path_lossy()),
)
})?;
let path = bytes2path(Cow::Borrowed(&bytes[prefixlen + 1..]))?;
copy_path_into(&mut self.name, &path, false).map_err(|err| {
copy_path_into(&mut self.name, &path, false, allow_absolute).map_err(|err| {
io::Error::new(
err.kind(),
format!("{} when setting path for {}", err, self.path_lossy()),
Expand Down Expand Up @@ -1555,17 +1577,18 @@ fn copy_path_into_inner(
path: &Path,
is_link_name: bool,
is_truncated_gnu_long_path: bool,
allow_absolute: bool,
) -> io::Result<()> {
let mut emitted = false;
let mut needs_slash = false;
let mut iter = path.components().peekable();
while let Some(component) = iter.next() {
let bytes = path2bytes(Path::new(component.as_os_str()))?;
match (component, is_link_name) {
(Component::Prefix(..), false) | (Component::RootDir, false) => {
match (component, is_link_name, allow_absolute) {
(Component::Prefix(..), false, false) | (Component::RootDir, false, false) => {
return Err(other("paths in archives must be relative"));
}
(Component::ParentDir, false) => {
(Component::ParentDir, false, _) => {
// If it's last component of a gnu long path we know that there might be more
// to the component than .. (the rest is stored elsewhere)
// Otherwise it's a clear error
Expand All @@ -1574,9 +1597,12 @@ fn copy_path_into_inner(
}
}
// Allow "./" as the path
(Component::CurDir, false) if path.components().count() == 1 => {}
(Component::CurDir, false) => continue,
(Component::Normal(_), _) | (_, true) => {}
(Component::CurDir, false, _) if path.components().count() == 1 => {}
(Component::CurDir, false, _) => continue,
(Component::Normal(_), _, _)
| (_, true, _)
| (Component::Prefix(_), false, true)
| (Component::RootDir, false, true) => {}
};
if needs_slash {
copy(&mut slot, b"/")?;
Expand Down Expand Up @@ -1616,8 +1642,13 @@ fn copy_path_into_inner(
/// * a nul byte was found
/// * an invalid path component is encountered (e.g. a root path or parent dir)
/// * the path itself is empty
fn copy_path_into(slot: &mut [u8], path: &Path, is_link_name: bool) -> io::Result<()> {
copy_path_into_inner(slot, path, is_link_name, false)
fn copy_path_into(
slot: &mut [u8],
path: &Path,
is_link_name: bool,
allow_absolute: bool,
) -> io::Result<()> {
copy_path_into_inner(slot, path, is_link_name, false, allow_absolute)
}

/// Copies `path` into the `slot` provided
Expand All @@ -1630,8 +1661,13 @@ fn copy_path_into(slot: &mut [u8], path: &Path, is_link_name: bool) -> io::Resul
/// * the path itself is empty
///
/// This is less restrictive version meant to be used for truncated GNU paths.
fn copy_path_into_gnu_long(slot: &mut [u8], path: &Path, is_link_name: bool) -> io::Result<()> {
copy_path_into_inner(slot, path, is_link_name, true)
fn copy_path_into_gnu_long(
slot: &mut [u8],
path: &Path,
is_link_name: bool,
allow_absolute: bool,
) -> io::Result<()> {
copy_path_into_inner(slot, path, is_link_name, true, allow_absolute)
}

#[cfg(target_arch = "wasm32")]
Expand Down
Loading
Loading