unzip-0.1.0/.gitignore01006640001747000174600000000037132145663200013105 0ustar0000000000000000/target/ **/*.rs.bk Cargo.lock unzip-0.1.0/.gitlab-ci.yml01006440001747000000000000000364132173341730013532 0ustar0000000000000000image: rust:latest stages: - test - publish test: stage: test script: - cargo test publish: only: - tags stage: publish before_script: - cargo login "$CARGO_IO_KEY" script: - cargo package - cargo publish unzip-0.1.0/Cargo.toml.orig01006640001747000174700000000467132173342440014015 0ustar0000000000000000[package] name = "unzip" description = "Library to decompress all (or some) files from a zipped archive." version = "0.1.0" authors = ["Ryan Leonard (http://ryanleonard.us)"] license = "MIT" [dev-dependencies] temporary = "0.6.3" [dependencies] zip = "0.2.6" time = "0.1.38" transformation-pipeline = "0.1.0" unzip-0.1.0/Cargo.toml0000644000000016010007212 0ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "unzip" version = "0.1.0" authors = ["Ryan Leonard (http://ryanleonard.us)"] description = "Library to decompress all (or some) files from a zipped archive." license = "MIT" [dependencies.time] version = "0.1.38" [dependencies.transformation-pipeline] version = "0.1.0" [dependencies.zip] version = "0.2.6" [dev-dependencies.temporary] version = "0.6.3" unzip-0.1.0/README.md01006640001747000174600000000127132145662040012375 0ustar0000000000000000# 'unzip' for Rust Library to decompress all (or some) files from a `zip`ped archive. unzip-0.1.0/src/lib.rs01006640001747000174700000005124132173341520013022 0ustar0000000000000000extern crate zip; extern crate time; extern crate transformation_pipeline; mod transformation_metadata; use std::io; use std::io::prelude::Read; use std::fs; use std::path::{Path, PathBuf}; use transformation_pipeline::TransformationPipeline; use transformation_metadata::ExtractionMetadata; mod strip_components; use strip_components::StripComponents; pub struct UnzipperStats { dirs: u16, files: u16, } type UnzipperResult = Result; pub struct Unzipper> { source: R, outdir: O, strip_components: u8, } impl> Unzipper { pub fn new(reader: R, output: O) -> Unzipper { Unzipper { source: reader, outdir: output, strip_components: 0, } } pub fn strip_components(mut self, num: u8) -> Unzipper { self.strip_components = num; self } pub fn unzip(self) -> UnzipperResult { let mut archive = zip::ZipArchive::new(self.source)?; let outdir: &Path = Path::new(self.outdir.as_ref()); let mut stats = UnzipperStats { dirs: 0, files: 0, }; let pipeline: TransformationPipeline = TransformationPipeline::new(vec![ Box::new(StripComponents::new(self.strip_components)), ]); for i in 0..archive.len() { let mut file = archive.by_index(i)?; let metadata = pipeline.run(ExtractionMetadata { extract: true, filename: file.name().to_owned(), comment: file.comment().to_owned(), compressed_size: file.compressed_size(), uncompressed_size: file.size(), crc32: file.crc32(), data_start: file.data_start(), last_modified: file.last_modified(), unix_mode: file.unix_mode(), })?; if !metadata.extract { continue; } let outpath: PathBuf = outdir.join(metadata.filename); if let Some(parent_dir) = outpath.as_path().parent() { fs::create_dir_all(&parent_dir)?; } if (&*file.name()).ends_with('/') { stats.dirs = stats.dirs + 1; continue; } let mut outfile = fs::File::create(&outpath)?; io::copy(&mut file, &mut outfile)?; // TODO: Handle unix_mode, last_modified stats.files = stats.files + 1; } Ok(stats) } } unzip-0.1.0/src/strip_components.rs01006640001747000174700000004357132173341520015671 0ustar0000000000000000use std::io; use std::path::{Path, PathBuf}; use transformation_pipeline::{TransformationStage, StageActions, StageResult}; use transformation_metadata::ExtractionMetadata; pub struct StripComponents { num: u8, } impl StripComponents { pub fn new(num: u8) -> StripComponents { StripComponents { num: num, } } } impl TransformationStage for StripComponents { fn run(&self, previous: ExtractionMetadata) -> StageResult { if self.num < 1 { return Ok(StageActions::Skip); } let last_filename: String = previous.filename.clone(); let path: &Path = Path::new(&last_filename); if path.components().count() < self.num.into() { return Ok(StageActions::Finish(ExtractionMetadata { extract: false, comment: previous.comment, compressed_size: previous.compressed_size, uncompressed_size: previous.uncompressed_size, crc32: previous.crc32, data_start: previous.data_start, filename: previous.filename, last_modified: previous.last_modified, unix_mode: previous.unix_mode, })); } let start_str: String = "".to_owned(); let start: &Path = Path::new(&start_str); let mut output: PathBuf = start.join("."); path .components() .skip(self.num.into()) .map(|comp| comp.as_os_str()) .for_each(|comp| output = output.join(comp)); let joined = output.as_os_str().to_str(); if joined.is_none() { return Err(io::Error::new(io::ErrorKind::Other, "Couldn't join stripped string.")); } Ok(StageActions::Next(ExtractionMetadata { filename: joined.unwrap().to_owned(), extract: previous.extract, comment: previous.comment, compressed_size: previous.compressed_size, uncompressed_size: previous.uncompressed_size, crc32: previous.crc32, data_start: previous.data_start, last_modified: previous.last_modified, unix_mode: previous.unix_mode, })) } } unzip-0.1.0/src/transformation_metadata.rs01006640001747000174700000002062132173341520017160 0ustar0000000000000000use time::Tm; use std::clone::Clone; pub struct ExtractionMetadata { // Fields that can't effect output: // (aka don't modify them unless you really need to change downstream behavior) pub comment: String, pub compressed_size: u64, pub uncompressed_size: u64, pub crc32: u32, pub data_start: u64, // Fields used by output: // (aka most likely to be modified) pub extract: bool, pub filename: String, pub last_modified: Tm, pub unix_mode: Option, } impl Clone for ExtractionMetadata { fn clone(&self) -> Self { ExtractionMetadata { comment: self.comment.clone(), compressed_size: self.compressed_size.clone(), uncompressed_size: self.uncompressed_size.clone(), crc32: self.crc32.clone(), data_start: self.data_start.clone(), extract: self.extract.clone(), filename: self.filename.clone(), last_modified: self.last_modified.clone(), unix_mode: self.unix_mode.clone(), } } } unzip-0.1.0/tests/assertions.rs01006440001747000000000000000437132146116730015001 0ustar0000000000000000use std::fs; use std::path::PathBuf; pub fn non_empty_file(path: PathBuf) { let data = fs::metadata(path).unwrap(); assert!(data.is_file()); assert!(data.len() > 0); } pub fn is_dir(path: PathBuf) { let data = fs::metadata(path).unwrap(); assert!(data.is_dir()); } unzip-0.1.0/tests/strip_components.rs01006640001747000174700000006457132173341520016247 0ustar0000000000000000extern crate zip; extern crate unzip; extern crate temporary; use std::fs::File; use self::temporary::Directory as TempDir; use unzip::Unzipper; mod assertions; use assertions::{non_empty_file, is_dir}; mod utils; use utils::{create_archive, zip_single_file}; /** * By default, no directories are stripped. */ #[test] fn default_no_strip() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo/bar.txt", b"Hello World", &mut writer); zip_single_file("foo/baz.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir) .unzip() .unwrap(); is_dir(dir.join("foo")); non_empty_file(dir.join("foo").join("bar.txt")); non_empty_file(dir.join("foo").join("baz.txt")); } /** * Nested contents are moved to the top-level if a directory is stripped. */ #[test] fn strip_single_dir_contents() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo/bar.txt", b"Hello World", &mut writer); zip_single_file("foo/baz.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir) .strip_components(1) .unzip() .unwrap(); non_empty_file(dir.join("bar.txt")); non_empty_file(dir.join("baz.txt")); } /** * Nested directories remain if only one directory is stripped. */ #[test] fn strip_single_dir_subdir() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo/bar/baz.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir) .strip_components(1) .unzip() .unwrap(); is_dir(dir.join("bar")); non_empty_file(dir.join("bar").join("baz.txt")); } /** * Multiple levels can be stripped. */ #[test] fn strip_multiple_dirs() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo/bar/baz.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir) .strip_components(2) .unzip() .unwrap(); non_empty_file(dir.join("baz.txt")); } /** * Two directories will be merged if they are both stripped. */ #[test] fn strip_merge_dirs() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo1/bar.txt", b"Hello World", &mut writer); zip_single_file("foo2/baz.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir) .strip_components(1) .unzip() .unwrap(); non_empty_file(dir.join("bar.txt")); non_empty_file(dir.join("baz.txt")); } unzip-0.1.0/tests/unzipper.rs01006640001747000174700000005062132173341520014504 0ustar0000000000000000extern crate zip; extern crate unzip; extern crate temporary; use std::fs::File; use self::temporary::Directory as TempDir; use unzip::Unzipper; mod assertions; use assertions::{non_empty_file, is_dir}; mod utils; use utils::{create_archive, zip_single_file}; #[test] fn unpacks_single_file() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir).unzip().unwrap(); non_empty_file(dir.join("foo.txt")); } #[test] fn unpacks_multiple_files() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo.txt", b"Hello World", &mut writer); zip_single_file("bar.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir).unzip().unwrap(); non_empty_file(dir.join("foo.txt")); non_empty_file(dir.join("bar.txt")); } #[test] fn creates_subdirectories() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo/bar.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir).unzip().unwrap(); is_dir(dir.join("foo")); } #[test] fn creates_files_in_subdirectories() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo/bar.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir).unzip().unwrap(); non_empty_file(dir.join("foo/bar.txt")); } #[test] fn unpacks_deep_directory() { let directory = TempDir::new("single_file").unwrap(); let dir = directory.into_path(); let archive = dir.join("archive.zip"); let mut writer = create_archive(&archive); zip_single_file("foo/bar/baz.txt", b"Hello World", &mut writer); writer.finish().unwrap(); Unzipper::new(File::open(archive).unwrap(), &dir).unzip().unwrap(); is_dir(dir.join("foo")); is_dir(dir.join("foo/bar")); non_empty_file(dir.join("foo/bar/baz.txt")); } unzip-0.1.0/tests/utils.rs01006640001747000174700000001063132173341520013765 0ustar0000000000000000extern crate zip; use std::io::Write; use std::fs::File; use std::path::PathBuf; use zip::ZipWriter; pub fn create_archive(path: &PathBuf) -> ZipWriter { let archive_file = File::create(path).unwrap(); zip::ZipWriter::new(archive_file) } pub fn zip_single_file(filename: &str, content: &[u8], archive_file: &mut ZipWriter) { let options = zip::write::FileOptions::default() .compression_method(zip::CompressionMethod::Stored); archive_file.start_file(filename, options).unwrap(); archive_file.write(content).unwrap(); }