Skip to content
48 changes: 40 additions & 8 deletions src/uu/tar/src/operations/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use crate::errors::TarError;
use std::collections::VecDeque;
use std::fs::{self, File};
use std::path::Component::{self, ParentDir, Prefix, RootDir};
use std::path::{self, Path, PathBuf};
use tar::Builder;
use uucore::error::UResult;
Expand Down Expand Up @@ -64,9 +65,26 @@ pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UR
println!("{to_print}");
}

// Normalize path if needed (so far, handles only absolute paths)
let normalized_name = if let Some(normalized) = normalize_path(path) {
let original_components: Vec<Component> = path.components().collect();
let normalized_components: Vec<Component> = normalized.components().collect();
if original_components.len() > normalized_components.len() {
let removed: PathBuf = original_components
[..original_components.len() - normalized_components.len()]
.iter()
.collect();
println!("Removing leading `{}' from member names", removed.display());
}

normalized
} else {
path.to_path_buf()
};

// If it's a directory, recursively add all contents
if path.is_dir() {
builder.append_dir_all(path, path).map_err(|e| {
builder.append_dir_all(normalized_name, path).map_err(|e| {
TarError::TarOperationError(format!(
"Failed to add directory '{}': {}",
path.display(),
Expand All @@ -75,13 +93,15 @@ pub fn create_archive(archive_path: &Path, files: &[&Path], verbose: bool) -> UR
})?;
} else {
// For files, add them directly
builder.append_path(path).map_err(|e| {
TarError::TarOperationError(format!(
"Failed to add file '{}': {}",
path.display(),
e
))
})?;
builder
.append_path_with_name(path, normalized_name)
.map_err(|e| {
TarError::TarOperationError(format!(
"Failed to add file '{}': {}",
path.display(),
e
))
})?;
}
}

Expand Down Expand Up @@ -110,3 +130,15 @@ fn get_tree(path: &Path) -> Result<Vec<PathBuf>, std::io::Error> {

Ok(paths)
}

fn normalize_path(path: &Path) -> Option<PathBuf> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good to know!

But I think uucode::fs::normalize_path do a different job. It is doing a normalization of path (dir/foo/../file -> dir/file) while my function is stripping leading / or C: prefix. I guess it will be more correct to just rename my function to something like to_relative_path or drop_root_prefix to avoid confusion.

But anyway, I guess, it also requires a normalization, since path in archive shouldn't contain ../. Shall I do it in this PR or better to keep it separate?

if path.is_absolute() {
Some(
path.components()
.filter(|c| !matches!(c, RootDir | ParentDir | Prefix(_)))
.collect::<PathBuf>(),
)
} else {
None
}
}
45 changes: 43 additions & 2 deletions tests/by-util/test_tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::path;
use std::path::{self, PathBuf};

use uutests::{at_and_ucmd, new_ucmd};

Expand Down Expand Up @@ -33,7 +33,7 @@ fn test_version() {
}

#[test]
fn test_verbose() {
fn test_create_dir_verbose() {
let (at, mut ucmd) = at_and_ucmd!();

let separator = path::MAIN_SEPARATOR;
Expand Down Expand Up @@ -81,6 +81,21 @@ fn test_create_multiple_files() {
assert!(at.file_exists("archive.tar"));
}

#[test]
fn test_create_absolute_path() {
let (at, mut ucmd) = at_and_ucmd!();

let mut file_abs_path = PathBuf::from(at.root_dir_resolved());
file_abs_path.push("file1.txt");

at.write(&file_abs_path.display().to_string(), "content1");
ucmd.args(&["-cf", "archive.tar", &file_abs_path.display().to_string()])
.succeeds()
.stdout_contains("Removing leading");

assert!(at.file_exists("archive.tar"));
}

// Extract operation tests

#[test]
Expand Down Expand Up @@ -139,3 +154,29 @@ fn test_extract_nonexistent_archive() {
.fails()
.code_is(2);
}

#[test]
fn test_extract_created_from_absolute_path() {
let (at, mut ucmd) = at_and_ucmd!();

let mut file_abs_path = PathBuf::from(at.root_dir_resolved());
file_abs_path.push("file1.txt");

at.write(&file_abs_path.display().to_string(), "content1");
ucmd.args(&["-cf", "archive.tar", &file_abs_path.display().to_string()])
.succeeds();

new_ucmd!()
.args(&["-xf", "archive.tar"])
.current_dir(at.as_string())
.succeeds();

let expected_path = file_abs_path
.components()
.filter(|c| !matches!(c, path::Component::RootDir | path::Component::Prefix(_)))
.map(|c| c.as_os_str().display().to_string())
.collect::<Vec<_>>()
.join(std::path::MAIN_SEPARATOR_STR);

assert!(at.file_exists(expected_path));
}
Loading