Repository: alexcrichton/tar-rs (language: rust)

Issue:
Fails to unpack long paths
On Windows `tar-rs` fails to unpack an archive when the archive contains a folder containing a file where the name of the folder is long enough to require a `@LongLink`. See here for a [repo ](https://github.com/davidMcneil/tar-long-path-repro) replicating the issue.

Essentially, unpacking an archive with a directory structure as shown below fails.
* `this_is_a_file_with_a_name_that_just_keeps_going_on_and_on_and_on_it_is_a_very_very_long_file_name`
    * `test.txt`

Code used to unpack:

```rust
use tar::Archive;
use std::fs::File;

fn main() {
    let file = File::open("test/test.tar").expect("failed to open tar file");
    let mut tar = Archive::new(file);
    tar.unpack("test/expanded/").expect("failed to unpack tar file");
}
```

The underlying error is "Access is denied." By adding logging to tar you can see the order of entries processed is as follows:

    "test/expanded/this_is_a_file_with_a_name_that_just_keeps_going_on_and_on_and_on_it_is_a_very_very_long_file_name" Directory
    "test/expanded/@LongLink" GNULongName
    "test/expanded/this_is_a_file_with_a_name_that_just_keeps_going_on_and_on_and_on_it_is_a_very_very_long_file_name" Regular

The third entry is pointing to the directory when it should be pointing to the file. The "Access is denied." error is the result of trying to open a directory as a file.


Discussion / hints:

Fix patch:
diff --git a/src/archive.rs b/src/archive.rs
index 5299b7fd..c9d3ca5f 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -287,7 +287,10 @@ impl<'a> EntriesFields<'a> {
                 None => return Ok(None),
             };
 
-            if entry.header().as_gnu().is_some() && entry.header().entry_type().is_gnu_longname() {
+            let is_recognized_header =
+                entry.header().as_gnu().is_some() || entry.header().as_ustar().is_some();
+
+            if is_recognized_header && entry.header().entry_type().is_gnu_longname() {
                 if gnu_longname.is_some() {
                     return Err(other(
                         "two long name entries describing \
@@ -298,7 +301,7 @@ impl<'a> EntriesFields<'a> {
                 continue;
             }
 
-            if entry.header().as_gnu().is_some() && entry.header().entry_type().is_gnu_longlink() {
+            if is_recognized_header && entry.header().entry_type().is_gnu_longlink() {
                 if gnu_longlink.is_some() {
                     return Err(other(
                         "two long name entries describing \
@@ -309,9 +312,7 @@ impl<'a> EntriesFields<'a> {
                 continue;
             }
 
-            if entry.header().as_ustar().is_some()
-                && entry.header().entry_type().is_pax_local_extensions()
-            {
+            if is_recognized_header && entry.header().entry_type().is_pax_local_extensions() {
                 if pax_extensions.is_some() {
                     return Err(other(
                         "two pax extensions entries describing \
diff --git a/src/header.rs b/src/header.rs
index bcce1808..626e386f 100644
--- a/src/header.rs
+++ b/src/header.rs
@@ -1604,8 +1604,8 @@ pub fn bytes2path(bytes: Cow<[u8]>) -> io::Result<Cow<Path>> {
     use std::ffi::{OsStr, OsString};
 
     Ok(match bytes {
-        Cow::Borrowed(bytes) => Cow::Borrowed({ Path::new(OsStr::from_bytes(bytes)) }),
-        Cow::Owned(bytes) => Cow::Owned({ PathBuf::from(OsString::from_vec(bytes)) }),
+        Cow::Borrowed(bytes) => Cow::Borrowed(Path::new(OsStr::from_bytes(bytes))),
+        Cow::Owned(bytes) => Cow::Owned(PathBuf::from(OsString::from_vec(bytes))),
     })
 }
 


Test patch:
diff --git a/tests/all.rs b/tests/all.rs
index 33dc69e6..9d52564e 100644
--- a/tests/all.rs
+++ b/tests/all.rs
@@ -1011,3 +1011,11 @@ fn tar_directory_containing_symlink_to_directory() {
     ar.append_dir_all("symlinks", td.path()).unwrap();
     ar.finish().unwrap();
 }
+
+#[test]
+fn long_path() {
+    let td = t!(TempBuilder::new().prefix("tar-rs").tempdir());
+    let rdr = Cursor::new(tar!("7z_long_path.tar"));
+    let mut ar = Archive::new(rdr);
+    assert!(ar.unpack(td.path()).is_ok());
+}
diff --git a/tests/archives/7z_long_path.tar b/tests/archives/7z_long_path.tar
new file mode 100644
index 00000000..3fc7f970
Binary files /dev/null and b/tests/archives/7z_long_path.tar differ