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

Fix io error when extracing zip archive containing folders #68

Merged
merged 2 commits into from
Feb 24, 2021
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [unreleased]
### Added
### Changed
- Fix io error triggered when updating binary contained in a zipped folder.
- Fix issues updating Windows binaries on non-`C:` drives.
### Removed

Expand Down
285 changes: 147 additions & 138 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,17 @@ impl<'a> Extract<'a> {
let mut archive = zip::ZipArchive::new(source)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let path = into_dir.join(file.name());
let mut output = fs::File::create(path)?;

let output_path = into_dir.join(file.name());
if let Some(parent_dir) = output_path.parent() {
if let Err(e) = fs::create_dir_all(parent_dir) {
if e.kind() != io::ErrorKind::AlreadyExists {
return Err(Error::Io(e));
}
}
}

let mut output = fs::File::create(output_path)?;
io::copy(&mut file, &mut output)?;
}
}
Expand Down Expand Up @@ -510,7 +519,17 @@ impl<'a> Extract<'a> {
ArchiveKind::Zip => {
let mut archive = zip::ZipArchive::new(source)?;
let mut file = archive.by_name(file_to_extract.to_str().unwrap())?;
let mut output = fs::File::create(into_dir.join(file.name()))?;

let output_path = into_dir.join(file.name());
if let Some(parent_dir) = output_path.parent() {
if let Err(e) = fs::create_dir_all(parent_dir) {
if e.kind() != io::ErrorKind::AlreadyExists {
return Err(Error::Io(e));
}
}
}

let mut output = fs::File::create(output_path)?;
io::copy(&mut file, &mut output)?;
}
};
Expand Down Expand Up @@ -859,54 +878,11 @@ mod tests {
#[cfg(all(feature = "archive-tar", feature = "compression-flate2"))]
#[test]
fn unpack_tar_gzip() {
let tmp_dir = tempfile::Builder::new()
.prefix("self_update_unpack_tar_gzip_src")
.tempdir()
.expect("tempdir fail");
let tmp_path = tmp_dir.path();

let archive_src = tmp_path.join("src_archive");
fs::create_dir_all(&archive_src).expect("tmp archive-dir create fail");

let fp = archive_src.join("temp.txt");
let mut tmp_file = File::create(&fp).expect("temp file create fail");
tmp_file.write_all(b"This is a test!").unwrap();
tmp_file.sync_all().expect("sync fail");

let fp2 = archive_src.join("temp2.txt");
let mut tmp_file = File::create(&fp2).expect("temp file 2 create fail");
tmp_file.write_all(b"This is a second test!").unwrap();
tmp_file.sync_all().expect("sync fail");

let mut ar = tar::Builder::new(vec![]);
ar.append_dir_all("inner_archive", &archive_src)
.expect("tar append dir all fail");
let tar_writer = ar.into_inner().expect("failed getting tar writer");

let archive_fp = tmp_path.with_file_name("archive_file.tar.gz");
let mut archive_file = File::create(&archive_fp).expect("failed creating archive file");
let mut e = GzEncoder::new(&mut archive_file, flate2::Compression::default());
io::copy(&mut tar_writer.as_slice(), &mut e)
.expect("failed writing from tar archive to gz encoder");
e.finish().expect("gz finish fail");
archive_file.sync_all().expect("sync fail");

let out_tmp = tempfile::Builder::new()
.prefix("self_update_unpack_tar_gzip_outdir")
.tempdir()
.expect("tempdir fail");
let out_path = out_tmp.path();
Extract::from_source(&archive_fp)
.extract_into(&out_path)
.expect("extract fail");

let out_file = out_path.join("inner_archive/temp.txt");
assert!(out_file.exists());
cmp_content(&out_file, "This is a test!");

let out_file = out_path.join("inner_archive/temp2.txt");
assert!(out_file.exists());
cmp_content(&out_file, "This is a second test!");
test_extract_into(
"self_update_unpack_tar_gzip_src",
"archive.tar.gz",
ArchiveKind::Tar(Some(Compression::Gz)),
);
}

#[cfg(not(feature = "compression-flate2"))]
Expand Down Expand Up @@ -950,42 +926,11 @@ mod tests {
#[cfg(all(feature = "archive-tar", feature = "compression-flate2"))]
#[test]
fn unpack_file_tar_gzip() {
let tmp_dir = tempfile::Builder::new()
.prefix("self_update_unpack_file_tar_gzip_src")
.tempdir()
.expect("tempdir fail");
let tmp_path = tmp_dir.path();

let archive_src = tmp_path.join("src_archive");
fs::create_dir_all(&archive_src).expect("tmp archive-dir create fail");

let fp = archive_src.join("temp.txt");
let mut tmp_file = File::create(&fp).expect("temp file create fail");
tmp_file.write_all(b"This is a test!").unwrap();

let mut ar = tar::Builder::new(vec![]);
ar.append_dir_all("inner_archive", &archive_src)
.expect("tar append dir all fail");
let tar_writer = ar.into_inner().expect("failed getting tar writer");

let archive_fp = tmp_path.with_file_name("archive_file.tar.gz");
let mut archive_file = File::create(&archive_fp).expect("failed creating archive file");
let mut e = GzEncoder::new(&mut archive_file, flate2::Compression::default());
io::copy(&mut tar_writer.as_slice(), &mut e)
.expect("failed writing from tar archive to gz encoder");
e.finish().expect("gz finish fail");

let out_tmp = tempfile::Builder::new()
.prefix("self_update_unpack_file_tar_gzip_outdir")
.tempdir()
.expect("tempdir fail");
let out_path = out_tmp.path();
Extract::from_source(&archive_fp)
.extract_file(&out_path, "inner_archive/temp.txt")
.expect("extract fail");
let out_file = out_path.join("inner_archive/temp.txt");
assert!(out_file.exists());
cmp_content(&out_file, "This is a test!");
test_extract_file(
"self_update_unpack_file_tar_gzip_src",
"archive.tar.gz",
ArchiveKind::Tar(Some(Compression::Gz)),
);
}

#[cfg(not(feature = "archive-zip"))]
Expand All @@ -997,84 +942,148 @@ mod tests {
#[cfg(feature = "archive-zip")]
#[test]
fn unpack_zip() {
test_extract_into(
"self_update_unpack_zip_src",
"archive.zip",
ArchiveKind::Zip,
);
}

#[cfg(not(feature = "archive-zip"))]
#[test]
#[ignore]
fn unpack_zip_file() {
Copy link
Owner

Choose a reason for hiding this comment

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

this needed or can it be deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function? The other tests do the same so I just copied them, but happy to remove it.

Copy link
Owner

@jaemk jaemk Feb 24, 2021

Choose a reason for hiding this comment

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

ah right, that's fine leaving it then

println!("WARNING: Please enable 'archive-zip' feature!");
}
#[cfg(feature = "archive-zip")]
#[test]
fn unpack_zip_file() {
test_extract_file(
"self_update_unpack_zip_src",
"archive.zip",
ArchiveKind::Zip,
);
}

fn test_extract_into(tmpfile_prefix: &str, src_archive_path: &str, archive_kind: ArchiveKind) {
let tmp_dir = tempfile::Builder::new()
.prefix("self_update_unpack_zip_src")
.prefix(tmpfile_prefix)
.tempdir()
.expect("tempdir fail");
.expect("Failed to create temp dir");

let tmp_path = tmp_dir.path();
let archive_file_path = tmp_path.join(src_archive_path);
let archive_file = File::create(&archive_file_path).expect("Failed to create archive file");

let archive_path = tmp_path.join("archive.zip");
let archive_file = File::create(&archive_path).expect("create file fail");
let mut zip = zip::ZipWriter::new(archive_file);
let options =
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
zip.start_file("zipped.txt", options)
.expect("failed starting zip file");
zip.write_all(b"This is a test!")
.expect("failed writing to zip");
zip.start_file("zipped2.txt", options)
.expect("failed starting second zip file");
zip.write_all(b"This is a second test!")
.expect("failed writing to second zip");
zip.finish().expect("failed finishing zip");
build_test_archive(archive_file, &archive_file_path, archive_kind);

let out_tmp = tempfile::Builder::new()
.prefix("self_update_unpack_zip_outdir")
.prefix(&format!("{}_outdir", tmpfile_prefix))
.tempdir()
.expect("tempdir fail");
let out_path = out_tmp.path();
Extract::from_source(&archive_path)

Extract::from_source(&archive_file_path)
.extract_into(&out_path)
.expect("extract fail");
let out_file = out_path.join("zipped.txt");

let out_file = out_path.join("temp.txt");
assert!(out_file.exists());
cmp_content(&out_file, "This is a test!");

let out_file2 = out_path.join("zipped2.txt");
assert!(out_file2.exists());
cmp_content(&out_file2, "This is a second test!");
let out_file = out_path.join("inner_archive/temp2.txt");
assert!(out_file.exists());
cmp_content(&out_file, "This is a second test!");
}

#[cfg(not(feature = "archive-zip"))]
#[test]
#[ignore]
fn unpack_zip_file() {
println!("WARNING: Please enable 'archive-zip' feature!");
}
#[cfg(feature = "archive-zip")]
#[test]
fn unpack_zip_file() {
fn test_extract_file(tmpfile_prefix: &str, src_archive_path: &str, archive_kind: ArchiveKind) {
let tmp_dir = tempfile::Builder::new()
.prefix("self_update_unpack_zip_src")
.prefix(tmpfile_prefix)
.tempdir()
.expect("tempdir fail");
.expect("Failed to create temp dir");

let tmp_path = tmp_dir.path();
let archive_file_path = tmp_path.join(src_archive_path);
let archive_file = File::create(&archive_file_path).expect("Failed to create archive file");

let archive_path = tmp_path.join("archive.zip");
let archive_file = File::create(&archive_path).expect("create file fail");
let mut zip = zip::ZipWriter::new(archive_file);
let options =
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
zip.start_file("zipped.txt", options)
.expect("failed starting zip file");
zip.write_all(b"This is a test!")
.expect("failed writing to zip");
zip.start_file("zipped2.txt", options)
.expect("failed starting second zip file");
zip.write_all(b"This is a second test!")
.expect("failed writing to second zip");
zip.finish().expect("failed finishing zip");
build_test_archive(archive_file, &archive_file_path, archive_kind);

let out_tmp = tempfile::Builder::new()
.prefix("self_update_unpack_zip_outdir")
.prefix(&format!("{}_outdir", tmpfile_prefix))
.tempdir()
.expect("tempdir fail");
let out_path = out_tmp.path();
Extract::from_source(&archive_path)
.extract_file(&out_path, "zipped2.txt")

Extract::from_source(&archive_file_path)
.extract_file(&out_path, "temp.txt")
.expect("extract fail");
let out_file = out_path.join("temp.txt");
assert!(out_file.exists());
cmp_content(&out_file, "This is a test!");

Extract::from_source(&archive_file_path)
.extract_file(&out_path, "inner_archive/temp2.txt")
.expect("extract fail");
let out_file = out_path.join("zipped2.txt");
let out_file = out_path.join("inner_archive/temp2.txt");
assert!(out_file.exists());
cmp_content(&out_file, "This is a second test!");
}

fn build_test_archive<T: AsRef<Path>>(
mut archive_file: fs::File,
archive_file_path: T,
archive_kind: ArchiveKind,
) {
let archive_file_path = archive_file_path.as_ref();

match archive_kind {
#[cfg(all(feature = "archive-tar", feature = "compression-flate2"))]
ArchiveKind::Tar(Some(Compression::Gz)) => {
let tmp_tar_path = archive_file_path
.parent()
.expect("Missing archive file path parent")
.join("tar_contents");
let tmp_tar_inner_path = tmp_tar_path.join("inner_archive");
fs::create_dir_all(&tmp_tar_inner_path).expect("Failed to create temp tar path");

let fp = tmp_tar_path.join("temp.txt");
let mut tmp_file = File::create(&fp).expect("temp file create fail");
tmp_file.write_all(b"This is a test!").unwrap();

let fp = tmp_tar_inner_path.join("temp2.txt");
let mut tmp_file = File::create(&fp).expect("temp file create fail");
tmp_file.write_all(b"This is a second test!").unwrap();

let mut ar = tar::Builder::new(vec![]);
ar.append_dir_all(".", &tmp_tar_path)
.expect("tar append dir all fail");
let tar_writer = ar.into_inner().expect("failed getting tar writer");

let mut e = GzEncoder::new(&mut archive_file, flate2::Compression::default());
io::copy(&mut tar_writer.as_slice(), &mut e)
.expect("failed writing from tar archive to gz encoder");
e.finish().expect("gz finish fail");
}

#[cfg(feature = "archive-zip")]
ArchiveKind::Zip => {
let mut zip = zip::ZipWriter::new(archive_file);
let options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Stored);
zip.start_file("temp.txt", options)
.expect("failed starting zip file");
zip.write_all(b"This is a test!")
.expect("failed writing to zip");
zip.start_file("inner_archive/temp2.txt", options)
.expect("failed starting second zip file");
zip.write_all(b"This is a second test!")
.expect("failed writing to second zip");
zip.finish().expect("failed finishing zip");
}

_ => {
unimplemented!("{:?} not handled", archive_kind);
}
}
}
}