feat: add option --allow-archive (#152)
BREAKING CHANGE: explicitly allow download folder as zip file
This commit is contained in:
parent
7eef4407fc
commit
8d9705caa4
8 changed files with 60 additions and 11 deletions
|
@ -59,6 +59,7 @@ Options:
|
||||||
--allow-delete Allow delete files/folders
|
--allow-delete Allow delete files/folders
|
||||||
--allow-search Allow search files/folders
|
--allow-search Allow search files/folders
|
||||||
--allow-symlink Allow symlink to files/folders outside root directory
|
--allow-symlink Allow symlink to files/folders outside root directory
|
||||||
|
--allow-archive Allow zip archive generation
|
||||||
--enable-cors Enable CORS, sets `Access-Control-Allow-Origin: *`
|
--enable-cors Enable CORS, sets `Access-Control-Allow-Origin: *`
|
||||||
--render-index Serve index.html when requesting a directory, returns 404 if not found index.html
|
--render-index Serve index.html when requesting a directory, returns 404 if not found index.html
|
||||||
--render-try-index Serve index.html when requesting a directory, returns directory listing if not found index.html
|
--render-try-index Serve index.html when requesting a directory, returns directory listing if not found index.html
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<div class="breadcrumb"></div>
|
<div class="breadcrumb"></div>
|
||||||
<div class="toolbox">
|
<div class="toolbox">
|
||||||
<div>
|
<div>
|
||||||
<a href="?zip" title="Download folder as a .zip file">
|
<a href="?zip" class="zip-root hidden" title="Download folder as a .zip file">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
* @property {boolean} allow_upload
|
* @property {boolean} allow_upload
|
||||||
* @property {boolean} allow_delete
|
* @property {boolean} allow_delete
|
||||||
* @property {boolean} allow_search
|
* @property {boolean} allow_search
|
||||||
|
* @property {boolean} allow_archive
|
||||||
* @property {boolean} dir_exists
|
* @property {boolean} dir_exists
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -273,12 +274,14 @@ function addPath(file, index) {
|
||||||
let actionMove = "";
|
let actionMove = "";
|
||||||
if (file.path_type.endsWith("Dir")) {
|
if (file.path_type.endsWith("Dir")) {
|
||||||
url += "/";
|
url += "/";
|
||||||
actionDownload = `
|
if (DATA.allow_archive) {
|
||||||
<div class="action-btn">
|
actionDownload = `
|
||||||
<a href="${url}?zip" title="Download folder as a .zip file">
|
<div class="action-btn">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
|
<a href="${url}?zip" title="Download folder as a .zip file">
|
||||||
</a>
|
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
|
||||||
</div>`;
|
</a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
actionDownload = `
|
actionDownload = `
|
||||||
<div class="action-btn" >
|
<div class="action-btn" >
|
||||||
|
@ -528,6 +531,10 @@ function ready() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DATA.allow_archive) {
|
||||||
|
document.querySelector(".zip-root").classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
addBreadcrumb(DATA.href, DATA.uri_prefix);
|
addBreadcrumb(DATA.href, DATA.uri_prefix);
|
||||||
renderPathsTableHead();
|
renderPathsTableHead();
|
||||||
renderPathsTableBody();
|
renderPathsTableBody();
|
||||||
|
|
|
@ -108,6 +108,12 @@ pub fn build_cli() -> Command {
|
||||||
.action(ArgAction::SetTrue)
|
.action(ArgAction::SetTrue)
|
||||||
.help("Allow symlink to files/folders outside root directory"),
|
.help("Allow symlink to files/folders outside root directory"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("allow-archive")
|
||||||
|
.long("allow-archive")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.help("Allow zip archive generation"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("enable-cors")
|
Arg::new("enable-cors")
|
||||||
.long("enable-cors")
|
.long("enable-cors")
|
||||||
|
@ -191,6 +197,7 @@ pub struct Args {
|
||||||
pub allow_delete: bool,
|
pub allow_delete: bool,
|
||||||
pub allow_search: bool,
|
pub allow_search: bool,
|
||||||
pub allow_symlink: bool,
|
pub allow_symlink: bool,
|
||||||
|
pub allow_archive: bool,
|
||||||
pub render_index: bool,
|
pub render_index: bool,
|
||||||
pub render_spa: bool,
|
pub render_spa: bool,
|
||||||
pub render_try_index: bool,
|
pub render_try_index: bool,
|
||||||
|
@ -244,6 +251,7 @@ impl Args {
|
||||||
let allow_delete = matches.get_flag("allow-all") || matches.get_flag("allow-delete");
|
let allow_delete = matches.get_flag("allow-all") || matches.get_flag("allow-delete");
|
||||||
let allow_search = matches.get_flag("allow-all") || matches.get_flag("allow-search");
|
let allow_search = matches.get_flag("allow-all") || matches.get_flag("allow-search");
|
||||||
let allow_symlink = matches.get_flag("allow-all") || matches.get_flag("allow-symlink");
|
let allow_symlink = matches.get_flag("allow-all") || matches.get_flag("allow-symlink");
|
||||||
|
let allow_archive = matches.get_flag("allow-all") || matches.get_flag("allow-archive");
|
||||||
let render_index = matches.get_flag("render-index");
|
let render_index = matches.get_flag("render-index");
|
||||||
let render_try_index = matches.get_flag("render-try-index");
|
let render_try_index = matches.get_flag("render-try-index");
|
||||||
let render_spa = matches.get_flag("render-spa");
|
let render_spa = matches.get_flag("render-spa");
|
||||||
|
@ -286,6 +294,7 @@ impl Args {
|
||||||
allow_upload,
|
allow_upload,
|
||||||
allow_search,
|
allow_search,
|
||||||
allow_symlink,
|
allow_symlink,
|
||||||
|
allow_archive,
|
||||||
render_index,
|
render_index,
|
||||||
render_try_index,
|
render_try_index,
|
||||||
render_spa,
|
render_spa,
|
||||||
|
|
|
@ -182,6 +182,7 @@ impl Server {
|
||||||
let allow_upload = self.args.allow_upload;
|
let allow_upload = self.args.allow_upload;
|
||||||
let allow_delete = self.args.allow_delete;
|
let allow_delete = self.args.allow_delete;
|
||||||
let allow_search = self.args.allow_search;
|
let allow_search = self.args.allow_search;
|
||||||
|
let allow_archive = self.args.allow_archive;
|
||||||
let render_index = self.args.render_index;
|
let render_index = self.args.render_index;
|
||||||
let render_spa = self.args.render_spa;
|
let render_spa = self.args.render_spa;
|
||||||
let render_try_index = self.args.render_try_index;
|
let render_try_index = self.args.render_try_index;
|
||||||
|
@ -195,7 +196,11 @@ impl Server {
|
||||||
Method::GET | Method::HEAD => {
|
Method::GET | Method::HEAD => {
|
||||||
if is_dir {
|
if is_dir {
|
||||||
if render_try_index {
|
if render_try_index {
|
||||||
if query_params.contains_key("zip") {
|
if allow_archive && query_params.contains_key("zip") {
|
||||||
|
if !allow_archive {
|
||||||
|
status_not_found(&mut res);
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
self.handle_zip_dir(path, head_only, &mut res).await?;
|
self.handle_zip_dir(path, head_only, &mut res).await?;
|
||||||
} else if allow_search && query_params.contains_key("q") {
|
} else if allow_search && query_params.contains_key("q") {
|
||||||
self.handle_search_dir(path, &query_params, head_only, &mut res)
|
self.handle_search_dir(path, &query_params, head_only, &mut res)
|
||||||
|
@ -214,6 +219,10 @@ impl Server {
|
||||||
self.handle_render_index(path, &query_params, headers, head_only, &mut res)
|
self.handle_render_index(path, &query_params, headers, head_only, &mut res)
|
||||||
.await?;
|
.await?;
|
||||||
} else if query_params.contains_key("zip") {
|
} else if query_params.contains_key("zip") {
|
||||||
|
if !allow_archive {
|
||||||
|
status_not_found(&mut res);
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
self.handle_zip_dir(path, head_only, &mut res).await?;
|
self.handle_zip_dir(path, head_only, &mut res).await?;
|
||||||
} else if allow_search && query_params.contains_key("q") {
|
} else if allow_search && query_params.contains_key("q") {
|
||||||
self.handle_search_dir(path, &query_params, head_only, &mut res)
|
self.handle_search_dir(path, &query_params, head_only, &mut res)
|
||||||
|
@ -824,6 +833,7 @@ impl Server {
|
||||||
allow_upload: self.args.allow_upload,
|
allow_upload: self.args.allow_upload,
|
||||||
allow_delete: self.args.allow_delete,
|
allow_delete: self.args.allow_delete,
|
||||||
allow_search: self.args.allow_search,
|
allow_search: self.args.allow_search,
|
||||||
|
allow_archive: self.args.allow_archive,
|
||||||
dir_exists: exist,
|
dir_exists: exist,
|
||||||
};
|
};
|
||||||
let data = serde_json::to_string(&data).unwrap();
|
let data = serde_json::to_string(&data).unwrap();
|
||||||
|
@ -984,6 +994,7 @@ struct IndexData {
|
||||||
allow_upload: bool,
|
allow_upload: bool,
|
||||||
allow_delete: bool,
|
allow_delete: bool,
|
||||||
allow_search: bool,
|
allow_search: bool,
|
||||||
|
allow_archive: bool,
|
||||||
dir_exists: bool,
|
dir_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,13 @@ fn default_not_allow_delete(server: TestServer) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn default_not_allow_archive(server: TestServer) -> Result<(), Error> {
|
||||||
|
let resp = reqwest::blocking::get(format!("{}?zip", server.url()))?;
|
||||||
|
assert_eq!(resp.status(), 404);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn default_not_exist_dir(server: TestServer) -> Result<(), Error> {
|
fn default_not_exist_dir(server: TestServer) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(format!("{}404/", server.url()))?;
|
let resp = reqwest::blocking::get(format!("{}404/", server.url()))?;
|
||||||
|
@ -71,3 +78,15 @@ fn allow_search(#[with(&["--allow-search"])] server: TestServer) -> Result<(), E
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn allow_archive(#[with(&["--allow-archive"])] server: TestServer) -> Result<(), Error> {
|
||||||
|
let resp = reqwest::blocking::get(format!("{}?zip", server.url()))?;
|
||||||
|
assert_eq!(resp.status(), 200);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("content-type").unwrap(),
|
||||||
|
"application/zip"
|
||||||
|
);
|
||||||
|
assert!(resp.headers().contains_key("content-disposition"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ fn head_dir_404(server: TestServer) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn get_dir_zip(server: TestServer) -> Result<(), Error> {
|
fn get_dir_zip(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(format!("{}?zip", server.url()))?;
|
let resp = reqwest::blocking::get(format!("{}?zip", server.url()))?;
|
||||||
assert_eq!(resp.status(), 200);
|
assert_eq!(resp.status(), 200);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -50,7 +50,7 @@ fn get_dir_zip(server: TestServer) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn head_dir_zip(server: TestServer) -> Result<(), Error> {
|
fn head_dir_zip(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||||
let resp = fetch!(b"HEAD", format!("{}?zip", server.url())).send()?;
|
let resp = fetch!(b"HEAD", format!("{}?zip", server.url())).send()?;
|
||||||
assert_eq!(resp.status(), 200);
|
assert_eq!(resp.status(), 200);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -40,7 +40,9 @@ fn render_try_index2(#[with(&["--render-try-index"])] server: TestServer) -> Res
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn render_try_index3(#[with(&["--render-try-index"])] server: TestServer) -> Result<(), Error> {
|
fn render_try_index3(
|
||||||
|
#[with(&["--render-try-index", "--allow-archive"])] server: TestServer,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(format!("{}{}?zip", server.url(), DIR_NO_INDEX))?;
|
let resp = reqwest::blocking::get(format!("{}{}?zip", server.url(), DIR_NO_INDEX))?;
|
||||||
assert_eq!(resp.status(), 200);
|
assert_eq!(resp.status(), 200);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
Loading…
Reference in a new issue