diff options
Diffstat (limited to '')
-rw-r--r-- | src/archive.rs | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/src/archive.rs b/src/archive.rs index 268bb47..c96814f 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,9 +1,13 @@ use actix_web::http::ContentEncoding; use libflate::gzip::Encoder; +use zip::{ZipWriter, write}; +use std::fs::File; +use std::path::PathBuf; use serde::Deserialize; use std::path::Path; use strum_macros::{Display, EnumIter, EnumString}; use tar::Builder; +use std::io::{Cursor, Write, Read}; use crate::errors::ContextualError; @@ -17,6 +21,9 @@ pub enum CompressionMethod { /// Regular tarball Tar, + + /// Regular zip + Zip, } impl CompressionMethod { @@ -24,6 +31,7 @@ impl CompressionMethod { match self { CompressionMethod::TarGz => "tar.gz", CompressionMethod::Tar => "tar", + CompressionMethod::Zip => "zip", } .to_string() } @@ -32,6 +40,7 @@ impl CompressionMethod { match self { CompressionMethod::TarGz => "application/gzip", CompressionMethod::Tar => "application/tar", + CompressionMethod::Zip => "application/zip", } .to_string() } @@ -40,6 +49,15 @@ impl CompressionMethod { match self { CompressionMethod::TarGz => ContentEncoding::Gzip, CompressionMethod::Tar => ContentEncoding::Identity, + CompressionMethod::Zip => ContentEncoding::Identity, + } + } + + pub fn is_enabled(self, tar_enabled: bool, zip_enabled: bool,) -> bool { + match self { + CompressionMethod::TarGz => tar_enabled, + CompressionMethod::Tar => tar_enabled, + CompressionMethod::Zip => zip_enabled, } } @@ -62,6 +80,7 @@ impl CompressionMethod { match self { CompressionMethod::TarGz => tar_gz(dir, skip_symlinks, out), CompressionMethod::Tar => tar_dir(dir, skip_symlinks, out), + CompressionMethod::Zip => zip_dir(dir, skip_symlinks, out), } } } @@ -159,3 +178,137 @@ where Ok(()) } + +/// Write a zip of `dir` in `out`. +/// +/// The target directory will be saved as a top-level directory in the archive. +/// +/// For example, consider this directory structure: +/// +/// ``` +/// a +/// └── b +/// └── c +/// ├── e +/// ├── f +/// └── g +/// ``` +/// +/// Making a zip out of `"a/b/c"` will result in this archive content: +/// +/// ``` +/// c +/// ├── e +/// ├── f +/// └── g +/// ``` +fn create_zip_from_directory<W>(out: W, directory: &PathBuf, skip_symlinks: bool,) -> Result<(), ContextualError> +where + W: std::io::Write + std::io::Seek +{ + let options = write::FileOptions::default().compression_method(zip::CompressionMethod::Stored); + let mut paths_queue: Vec<PathBuf> = vec![]; + paths_queue.push(directory.clone()); + let zip_root_folder_name = directory.file_name().ok_or_else(|| { + ContextualError::InvalidPathError("Directory name terminates in \"..\"".to_string()) + })?; + + let mut zip_writer = ZipWriter::new(out); + let mut buffer = Vec::new(); + while paths_queue.len() > 0 { + let next = paths_queue.pop().ok_or(ContextualError::CustomError("Could not get path from queue".to_string()))?; + let current_dir = next.as_path(); + let directory_entry_iterator = std::fs::read_dir(current_dir).map_err(|e|{ + ContextualError::IOError("Could not read directory".to_string(), e) + })?; + let zip_directory = Path::new(zip_root_folder_name) + .join(current_dir.strip_prefix(directory).map_err(|_|{ + ContextualError::CustomError("Could not append base directory".to_string()) + })?); + + for entry in directory_entry_iterator { + let entry_path = entry.ok().ok_or( + ContextualError::InvalidPathError("Directory name terminates in \"..\"".to_string()) + )?.path(); + let entry_metadata = std::fs::metadata(entry_path.clone()).map_err(|e|{ + ContextualError::IOError("Could not get file metadata".to_string(), e) + })?; + + if entry_metadata.file_type().is_symlink() && skip_symlinks { + continue; + } + let current_entry_name = entry_path.file_name().ok_or_else(|| { + ContextualError::InvalidPathError("Invalid file or direcotory name".to_string()) + })?; + if entry_metadata.is_file() { + let mut f = File::open(&entry_path).map_err(|e| { + ContextualError::IOError("Could not open file".to_string(), e) + })?; + f.read_to_end(&mut buffer).map_err(|e| { + ContextualError::IOError("Could not read from file".to_string(), e) + })?; + let relative_path = zip_directory.join(current_entry_name); + zip_writer.start_file_from_path(Path::new(&relative_path), options).map_err(|_| { + ContextualError::CustomError("Could not add file path to ZIP".to_string()) + })?; + zip_writer.write(buffer.as_ref()).map_err(|_| { + ContextualError::CustomError("Could not write file to ZIP".to_string()) + })?; + buffer.clear(); + } else if entry_metadata.is_dir() { + let relative_path = zip_directory.join(current_entry_name); + zip_writer.add_directory_from_path(Path::new(&relative_path), options).map_err(|_| { + ContextualError::CustomError("Could not add directory path to ZIP".to_string()) + })?; + paths_queue.push(entry_path.clone()); + } + } + } + + zip_writer.finish().map_err(|_| { + ContextualError::CustomError("Could not finish writing ZIP archive".to_string()) + })?; + Ok(()) +} + +/// Writes a zip of `dir` in `out`. +/// +/// The content of `src_dir` will be saved in the archive as the folder named . +fn zip_data<W>( + src_dir: &Path, + skip_symlinks: bool, + mut out: W, +) -> Result<(), ContextualError> +where + W: std::io::Write, +{ + let mut data = Vec::new(); + let memory_file = Cursor::new(&mut data); + create_zip_from_directory(memory_file, &src_dir.to_path_buf(), skip_symlinks).map_err(|e| { + ContextualError::ArchiveCreationError("Failed to create the ZIP archive".to_string(), Box::new(e)) + })?; + + out.write_all(data.as_mut_slice()).map_err(|e| { + ContextualError::IOError("Failed to write the ZIP archive".to_string(), e) + })?; + + Ok(()) +} + +fn zip_dir<W>(dir: &Path, skip_symlinks: bool, out: W) -> Result<(), ContextualError> +where + W: std::io::Write, +{ + let inner_folder = dir.file_name().ok_or_else(|| { + ContextualError::InvalidPathError("Directory name terminates in \"..\"".to_string()) + })?; + + inner_folder.to_str().ok_or_else(|| { + ContextualError::InvalidPathError( + "Directory name contains invalid UTF-8 characters".to_string(), + ) + })?; + + zip_data(dir, skip_symlinks, out) + .map_err(|e| ContextualError::ArchiveCreationError("zip".to_string(), Box::new(e))) +}
\ No newline at end of file |