aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormarawan ragab <marawan31@gmail.com>2020-05-03 23:26:21 +0000
committermarawan ragab <marawan31@gmail.com>2020-05-03 23:26:21 +0000
commitdd1684f1aa4066805001b4b02eb7619ad1b6fe7d (patch)
treebadba336f35063f8f89d09d846183bc1a1e24e92
parentMerge pull request #296 from svenstaro/dependabot/cargo/simplelog-0.7.6 (diff)
downloadminiserve-dd1684f1aa4066805001b4b02eb7619ad1b6fe7d.tar.gz
miniserve-dd1684f1aa4066805001b4b02eb7619ad1b6fe7d.zip
Add zip download functionality for windows users
Diffstat (limited to '')
-rw-r--r--Cargo.lock40
-rw-r--r--Cargo.toml1
-rw-r--r--src/archive.rs151
3 files changed, 192 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e3eb357..a8688f4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -593,6 +593,24 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "bzip2"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bzip2-sys 0.1.8+1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.8+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1575,6 +1593,7 @@ dependencies = [
"tar 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -1887,6 +1906,11 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "podio"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "port_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3392,6 +3416,18 @@ name = "yansi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "zip"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[metadata]
"checksum actix 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c616db5fa4b0c40702fb75201c2af7f8aa8f3a2e2c1dda3b0655772aa949666"
"checksum actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9f2c11af4b06dc935d8e1b1491dad56bfb32febc49096a91e773f8535c176453"
@@ -3444,6 +3480,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
"checksum bytesize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "716960a18f978640f25101b5cbf1c6f6b0d3192fab36a2d98ca96f0ecbe41010"
+"checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
+"checksum bzip2-sys 0.1.8+1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "05305b41c5034ff0e93937ac64133d109b5a2660114ec45e9760bc6816d83038"
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
@@ -3591,6 +3629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+"checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd"
"checksum port_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f6519412c9e0d4be579b9f0618364d19cb434b324fc6ddb1b27b1e682c7105ed"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
@@ -3754,3 +3793,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
"checksum yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
+"checksum zip 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6df134e83b8f0f8153a094c7b0fd79dfebe437f1d76e7715afa18ed95ebe2fd7"
diff --git a/Cargo.toml b/Cargo.toml
index db9836b..4e3bb2f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,6 +46,7 @@ strum = "0.18.0"
strum_macros = "0.18.0"
sha2 = "0.8.1"
hex = "0.4.2"
+zip = "0.5.5"
[dev-dependencies]
assert_cmd = "1.0"
diff --git a/src/archive.rs b/src/archive.rs
index 268bb47..6808907 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -1,9 +1,14 @@
use actix_web::http::ContentEncoding;
use libflate::gzip::Encoder;
+use zip::{ZipWriter, write};
+use std::io::BufWriter;
+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 +22,9 @@ pub enum CompressionMethod {
/// Regular tarball
Tar,
+
+ /// Regular zip
+ Zip,
}
impl CompressionMethod {
@@ -24,6 +32,7 @@ impl CompressionMethod {
match self {
CompressionMethod::TarGz => "tar.gz",
CompressionMethod::Tar => "tar",
+ CompressionMethod::Zip => "zip",
}
.to_string()
}
@@ -32,6 +41,7 @@ impl CompressionMethod {
match self {
CompressionMethod::TarGz => "application/gzip",
CompressionMethod::Tar => "application/tar",
+ CompressionMethod::Zip => "application/zip",
}
.to_string()
}
@@ -40,6 +50,7 @@ impl CompressionMethod {
match self {
CompressionMethod::TarGz => ContentEncoding::Gzip,
CompressionMethod::Tar => ContentEncoding::Identity,
+ CompressionMethod::Zip => ContentEncoding::Identity,
}
}
@@ -62,6 +73,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 +171,142 @@ 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().unwrap();
+ 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,
+ 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))
+ })?;
+ }
+
+ let mut buffer = BufWriter::new(out);
+ buffer.write_all(&mut data).map_err(|e| {
+ ContextualError::IOError("Failed to write the ZIP archive".to_string(), e)
+ })?;
+
+ buffer.flush().map_err(|e| {
+ ContextualError::IOError("Failed to finish writing 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