Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
Merge pull request #50 from whizsid/download_dir
Browse files Browse the repository at this point in the history
Download directory as zip
  • Loading branch information
weihanglo authored Sep 14, 2020
2 parents 0d91362 + 63b4209 commit 55f73de
Show file tree
Hide file tree
Showing 8 changed files with 669 additions and 478 deletions.
859 changes: 450 additions & 409 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ serde = { version = "1.0", features = ["derive"] } # For tera serializing variab
ignore = "0.4" # Respect to .gitignore while listing directories.
# Logging
chrono = "0.4"
# Directory Download
qstring = "0.7"
zip = { version = "0.5", default-features = false, features = ["deflate"] }

[dev-dependencies]
tempdir = "0.3"
Expand Down
3 changes: 3 additions & 0 deletions src/server/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
{% endif %}
<span class="separator">/</span>
{% endfor %}
<a href="?action=zip" title="Download folder as a .zip file">
<svg height="16" width="16" viewBox="0 0 24 24" ><path fill-rule="evenodd" d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"></path></svg>
</a>
</div>
<ul>
{% for file in files %}
Expand Down
98 changes: 90 additions & 8 deletions src/server/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@

use std::convert::AsRef;
use std::fs::File;
use std::io::{self, BufReader};
use std::io::{self, BufReader, Read, Seek, SeekFrom};
use std::path::Path;

use ignore::WalkBuilder;
use serde::Serialize;
use tera::{Context, Tera};
use zip::ZipWriter;

use crate::extensions::PathExt;
use crate::server::PathType;
Expand All @@ -34,6 +35,21 @@ struct Breadcrumb<'a> {
path: String,
}

/// Walking inside a directory recursively
fn get_dir_contents<P: AsRef<Path>>(
dir_path: P,
with_ignore: bool,
show_all: bool,
depth: Option<usize>,
) -> ignore::Walk {
WalkBuilder::new(dir_path)
.standard_filters(false) // Disable all standard filters.
.git_ignore(with_ignore)
.hidden(!show_all) // Filter out hidden entries on demand.
.max_depth(depth) // Do not traverse subpaths.
.build()
}

/// Send a HTML page of all files under the path.
///
/// # Parameters
Expand All @@ -59,12 +75,7 @@ pub fn send_dir<P1: AsRef<Path>, P2: AsRef<Path>>(
let breadcrumbs = create_breadcrumbs(dir_path, base_path, prefix);

// Collect filename and there links.
let files_iter = WalkBuilder::new(dir_path)
.standard_filters(false) // Disable all standard filters.
.git_ignore(with_ignore)
.hidden(!show_all) // Filter out hidden entries on demand.
.max_depth(Some(1)) // Do not traverse subpaths.
.build()
let files_iter = get_dir_contents(dir_path, with_ignore, show_all, Some(1))
.filter_map(|entry| entry.ok())
.filter(|entry| dir_path != entry.path()) // Exclude `.`
.map(|entry| {
Expand All @@ -86,6 +97,7 @@ pub fn send_dir<P1: AsRef<Path>, P2: AsRef<Path>>(
} else {
// CWD == sub dir of base dir
// Append an item for popping back to parent directory.

let path = format!(
"{}/{}",
prefix,
Expand All @@ -97,6 +109,7 @@ pub fn send_dir<P1: AsRef<Path>, P2: AsRef<Path>>(
.to_str()
.unwrap()
);

vec![Item {
name: "..".to_owned(),
path,
Expand All @@ -118,9 +131,59 @@ pub fn send_file<P: AsRef<Path>>(file_path: P) -> io::Result<Vec<u8>> {
let f = File::open(file_path)?;
let mut buffer = Vec::new();
BufReader::new(f).read_to_end(&mut buffer)?;

Ok(buffer)
}

/// Sending a directory as zip buffer
pub fn send_dir_as_zip<P: AsRef<Path>>(
dir_path: P,
show_all: bool,
with_ignore: bool,
) -> io::Result<Vec<u8>> {
let dir_path = dir_path.as_ref();

// Creating a memory buffer to make zip file
let mut zip_buffer = Vec::new();
let cursor = std::io::Cursor::new(&mut zip_buffer);

let mut zip_writer = ZipWriter::new(cursor);
let zip_options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(0o755);

// Recursively finding files and directories
let files_iter = get_dir_contents(dir_path, with_ignore, show_all, None)
.filter_map(|entry| entry.ok())
.filter(|entry| entry.path() != dir_path);

for dir_entry in files_iter {
let file_path = dir_entry.path();
let name = file_path.strip_prefix(dir_path).unwrap().to_str().unwrap();

if file_path.is_dir() {
zip_writer
.add_directory(name, zip_options)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
} else {
zip_writer
.start_file(name, zip_options)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
let mut file = File::open(file_path)?;

std::io::copy(&mut file, &mut zip_writer)?;
}
}

let mut zip = zip_writer
.finish()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;

zip.seek(SeekFrom::Start(0))?;

zip.bytes().collect()
}

/// Send a buffer with specific range.
///
/// # Parameters
Expand All @@ -132,7 +195,6 @@ pub fn send_file_with_range<P: AsRef<Path>>(
range: (u64, u64),
) -> io::Result<Vec<u8>> {
use std::io::prelude::*;
use std::io::SeekFrom;
let (start, end) = range; // TODO: should return HTTP 416
if end < start {
return Err(io::Error::from(io::ErrorKind::InvalidInput));
Expand Down Expand Up @@ -282,6 +344,12 @@ mod t_send {
path
}

fn dir_with_sub_dir_path() -> std::path::PathBuf {
let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("./tests/dir_with_sub_dirs/");
path
}

fn missing_file_path() -> std::path::PathBuf {
let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("./missing/file");
Expand Down Expand Up @@ -338,4 +406,18 @@ mod t_send {
let buf = send_file_with_range(file_txt_path(), (1, 0));
assert_eq!(buf.unwrap_err().kind(), std::io::ErrorKind::InvalidInput);
}

#[test]
fn t_send_dir_as_zip() {
let buf = send_dir_as_zip(dir_with_sub_dir_path(), true, false);

assert_eq!(buf.is_ok(), true);

let buf = buf.unwrap();

assert_eq!(buf.len() > 0, true);

// https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html#localheader
assert_eq!(&buf[0..4], &[0x50, 0x4b, 0x03, 0x04]);
}
}
Loading

0 comments on commit 55f73de

Please sign in to comment.