Compare commits

...

8 Commits

Author SHA1 Message Date
sigoden
147048690f chore(release): version v0.27.0 2022-07-25 09:59:32 +08:00
sigoden
9cfd66dab9 feat: adjust digest auth timeout to 1day (#110) 2022-07-21 11:47:47 +08:00
sigoden
b791549ec7 feat: improve hidden to support glob (#108) 2022-07-19 20:37:14 +08:00
sigoden
f148817c52 chore(release): version v0.26.0 2022-07-11 08:54:29 +08:00
sigoden
00ae36d486 chore: improve readme 2022-07-08 22:36:16 +08:00
sigoden
4e823e8bba feat: make --path-prefix works on serving single file (#102) 2022-07-08 19:30:05 +08:00
sigoden
4e84e6c532 fix: cors headers (#100) 2022-07-08 16:18:10 +08:00
sigoden
f49b590a56 chore: update description of --path-prefix 2022-07-07 15:44:25 +08:00
11 changed files with 266 additions and 120 deletions

View File

@@ -2,6 +2,23 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.27.0] - 2022-07-21
### Features
- Improve hidden to support glob ([#108](https://github.com/sigoden/dufs/issues/108))
- Adjust digest auth timeout to 1day ([#110](https://github.com/sigoden/dufs/issues/110))
## [0.26.0] - 2022-07-11
### Bug Fixes
- Cors headers ([#100](https://github.com/sigoden/dufs/issues/100))
### Features
- Make --path-prefix works on serving single file ([#102](https://github.com/sigoden/dufs/issues/102))
## [0.25.0] - 2022-07-06 ## [0.25.0] - 2022-07-06
### Features ### Features

78
Cargo.lock generated
View File

@@ -136,9 +136,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.5.2" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [ dependencies = [
"bit-vec", "bit-vec",
] ]
@@ -192,9 +192,9 @@ checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e"
[[package]] [[package]]
name = "cc" name = "cc"
@@ -223,9 +223,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.2.8" version = "3.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"clap_lex", "clap_lex",
@@ -288,9 +288,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.10" version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -298,9 +298,9 @@ dependencies = [
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.4" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"typenum", "typenum",
@@ -379,7 +379,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "dufs" name = "dufs"
version = "0.25.0" version = "0.27.0"
dependencies = [ dependencies = [
"assert_cmd", "assert_cmd",
"assert_fs", "assert_fs",
@@ -438,9 +438,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [ dependencies = [
"instant", "instant",
] ]
@@ -677,9 +677,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.1" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "headers" name = "headers"
@@ -771,9 +771,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.19" version = "0.14.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@@ -1109,9 +1109,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.40" version = "0.10.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
@@ -1141,9 +1141,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.74" version = "0.9.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cc", "cc",
@@ -1154,9 +1154,9 @@ dependencies = [
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.1.0" version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
[[package]] [[package]]
name = "output_vt100" name = "output_vt100"
@@ -1432,9 +1432,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.13" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@@ -1667,18 +1667,18 @@ checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.138" version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.138" version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1749,9 +1749,12 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@@ -1919,10 +1922,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.19.2" version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e"
dependencies = [ dependencies = [
"autocfg",
"bytes", "bytes",
"libc", "libc",
"memchr", "memchr",
@@ -2037,9 +2041,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@@ -2238,9 +2242,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.22.3" version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
dependencies = [ dependencies = [
"webpki", "webpki",
] ]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "dufs" name = "dufs"
version = "0.25.0" version = "0.27.0"
edition = "2021" edition = "2021"
authors = ["sigoden <sigoden@gmail.com>"] authors = ["sigoden <sigoden@gmail.com>"]
description = "Dufs is a distinctive utility file server" description = "Dufs is a distinctive utility file server"

View File

@@ -30,7 +30,7 @@ cargo install dufs
### With docker ### With docker
``` ```
docker run -v `pwd`:/data -p 5000:5000 --rm -it sigoden/dufs /data docker run -v `pwd`:/data -p 5000:5000 --rm -it sigoden/dufs /data -A
``` ```
### Binaries on macOS, Linux, Windows ### Binaries on macOS, Linux, Windows
@@ -51,7 +51,7 @@ ARGS:
OPTIONS: OPTIONS:
-b, --bind <addr>... Specify bind address -b, --bind <addr>... Specify bind address
-p, --port <port> Specify port to listen on [default: 5000] -p, --port <port> Specify port to listen on [default: 5000]
--path-prefix <path> Specify an path prefix --path-prefix <path> Specify a path prefix
--hidden <value> Hide directories from directory listings, separated by `,` --hidden <value> Hide directories from directory listings, separated by `,`
-a, --auth <rule>... Add auth for path -a, --auth <rule>... Add auth for path
--auth-method <value> Select auth method [default: digest] [possible values: basic, digest] --auth-method <value> Select auth method [default: digest] [possible values: basic, digest]
@@ -79,7 +79,7 @@ Serve current working directory
dufs dufs
``` ```
Explicitly allow all operations including download/upload/delete Allow all operations like upload/delete/search...
``` ```
dufs -A dufs -A
@@ -97,24 +97,24 @@ Serve a specific directory
dufs Downloads dufs Downloads
``` ```
Serve a specific file Serve a single file
``` ```
dufs linux-distro.iso dufs linux-distro.iso
``` ```
Serve index.html when requesting a directory Serve a single-page application like react/vue
```
dufs --render-index
```
Serve single-page application like react
``` ```
dufs --render-spa dufs --render-spa
``` ```
Serve a static website with index.html
```
dufs --render-index
```
Require username/password Require username/password
``` ```
@@ -141,6 +141,12 @@ dufs --tls-cert my.crt --tls-key my.key
## API ## API
Upload a file
```
curl -T path-to-file http://127.0.0.1:5000/new-path/path-to-file
```
Download a file Download a file
``` ```
curl http://127.0.0.1:5000/path-to-file curl http://127.0.0.1:5000/path-to-file
@@ -152,16 +158,10 @@ Download a folder as zip file
curl -o path-to-folder.zip http://127.0.0.1:5000/path-to-folder?zip curl -o path-to-folder.zip http://127.0.0.1:5000/path-to-folder?zip
``` ```
Upload a file
```
curl --upload-file path-to-file http://127.0.0.1:5000/path-to-file
```
Delete a file/folder Delete a file/folder
``` ```
curl -X DELETE http://127.0.0.1:5000/path-to-file curl -X DELETE http://127.0.0.1:5000/path-to-file-or-folder
``` ```
## Access Control ## Access Control

View File

@@ -10,6 +10,7 @@ use crate::auth::AccessControl;
use crate::auth::AuthMethod; use crate::auth::AuthMethod;
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
use crate::tls::{load_certs, load_private_key}; use crate::tls::{load_certs, load_private_key};
use crate::utils::encode_uri;
use crate::BoxResult; use crate::BoxResult;
pub fn build_cli() -> Command<'static> { pub fn build_cli() -> Command<'static> {
@@ -49,7 +50,7 @@ pub fn build_cli() -> Command<'static> {
Arg::new("path-prefix") Arg::new("path-prefix")
.long("path-prefix") .long("path-prefix")
.value_name("path") .value_name("path")
.help("Specify an path prefix"), .help("Specify a path prefix"),
) )
.arg( .arg(
Arg::new("hidden") Arg::new("hidden")
@@ -158,7 +159,7 @@ pub struct Args {
pub path_is_file: bool, pub path_is_file: bool,
pub path_prefix: String, pub path_prefix: String,
pub uri_prefix: String, pub uri_prefix: String,
pub hidden: String, pub hidden: Vec<String>,
pub auth_method: AuthMethod, pub auth_method: AuthMethod,
pub auth: AccessControl, pub auth: AccessControl,
pub allow_upload: bool, pub allow_upload: bool,
@@ -196,11 +197,11 @@ impl Args {
let uri_prefix = if path_prefix.is_empty() { let uri_prefix = if path_prefix.is_empty() {
"/".to_owned() "/".to_owned()
} else { } else {
format!("/{}/", &path_prefix) format!("/{}/", &encode_uri(&path_prefix))
}; };
let hidden: String = matches let hidden: Vec<String> = matches
.value_of("hidden") .value_of("hidden")
.map(|v| format!(",{},", v)) .map(|v| v.split(',').map(|x| x.to_string()).collect())
.unwrap_or_default(); .unwrap_or_default();
let enable_cors = matches.is_present("enable-cors"); let enable_cors = matches.is_present("enable-cors");
let auth: Vec<&str> = matches let auth: Vec<&str> = matches

View File

@@ -12,6 +12,7 @@ use crate::utils::encode_uri;
use crate::BoxResult; use crate::BoxResult;
const REALM: &str = "DUFS"; const REALM: &str = "DUFS";
const DIGEST_AUTH_TIMEOUT: u32 = 86400;
lazy_static! { lazy_static! {
static ref NONCESTARTHASH: Context = { static ref NONCESTARTHASH: Context = {
@@ -317,8 +318,7 @@ fn validate_nonce(nonce: &[u8]) -> Result<bool, ()> {
h.consume(secs_nonce.to_be_bytes()); h.consume(secs_nonce.to_be_bytes());
let h = format!("{:x}", h.compute()); let h = format!("{:x}", h.compute());
if h[..26] == n[8..34] { if h[..26] == n[8..34] {
return Ok(dur < 300); // from the last 5min return Ok(dur < DIGEST_AUTH_TIMEOUT);
//Authentication-Info ?
} }
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::streamer::Streamer; use crate::streamer::Streamer;
use crate::utils::{decode_uri, encode_uri, get_file_name, try_get_file_name}; use crate::utils::{decode_uri, encode_uri, get_file_name, glob, try_get_file_name};
use crate::{Args, BoxResult}; use crate::{Args, BoxResult};
use walkdir::WalkDir; use walkdir::WalkDir;
use xml::escape::escape_str_pcdata; use xml::escape::escape_str_pcdata;
@@ -9,13 +9,13 @@ use async_zip::Compression;
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use futures::TryStreamExt; use futures::TryStreamExt;
use headers::{ use headers::{
AcceptRanges, AccessControlAllowCredentials, AccessControlAllowHeaders, AcceptRanges, AccessControlAllowCredentials, AccessControlAllowOrigin, Connection,
AccessControlAllowOrigin, Connection, ContentLength, ContentType, ETag, HeaderMap, ContentLength, ContentType, ETag, HeaderMap, HeaderMapExt, IfModifiedSince, IfNoneMatch,
HeaderMapExt, IfModifiedSince, IfNoneMatch, IfRange, LastModified, Range, IfRange, LastModified, Range,
}; };
use hyper::header::{ use hyper::header::{
HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_RANGE, HeaderValue, AUTHORIZATION, CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE,
CONTENT_TYPE, ORIGIN, RANGE, WWW_AUTHENTICATE, RANGE, WWW_AUTHENTICATE,
}; };
use hyper::{Body, Method, StatusCode, Uri}; use hyper::{Body, Method, StatusCode, Uri};
use serde::Serialize; use serde::Serialize;
@@ -45,15 +45,30 @@ const BUF_SIZE: usize = 65536;
pub struct Server { pub struct Server {
args: Arc<Args>, args: Arc<Args>,
assets_prefix: String, assets_prefix: String,
single_file_req_paths: Vec<String>,
running: Arc<AtomicBool>, running: Arc<AtomicBool>,
} }
impl Server { impl Server {
pub fn new(args: Arc<Args>, running: Arc<AtomicBool>) -> Self { pub fn new(args: Arc<Args>, running: Arc<AtomicBool>) -> Self {
let assets_prefix = format!("{}__dufs_v{}_", args.uri_prefix, env!("CARGO_PKG_VERSION")); let assets_prefix = format!("{}__dufs_v{}_", args.uri_prefix, env!("CARGO_PKG_VERSION"));
let single_file_req_paths = if args.path_is_file {
vec![
args.uri_prefix.to_string(),
args.uri_prefix[0..args.uri_prefix.len() - 1].to_string(),
encode_uri(&format!(
"{}{}",
&args.uri_prefix,
get_file_name(&args.path)
)),
]
} else {
vec![]
};
Self { Self {
args, args,
running, running,
single_file_req_paths,
assets_prefix, assets_prefix,
} }
} }
@@ -118,8 +133,16 @@ impl Server {
let head_only = method == Method::HEAD; let head_only = method == Method::HEAD;
if self.args.path_is_file { if self.args.path_is_file {
self.handle_send_file(&self.args.path, headers, head_only, &mut res) if self
.await?; .single_file_req_paths
.iter()
.any(|v| v.as_str() == req_path)
{
self.handle_send_file(&self.args.path, headers, head_only, &mut res)
.await?;
} else {
status_not_found(&mut res);
}
return Ok(res); return Ok(res);
} }
@@ -343,7 +366,8 @@ impl Server {
) -> BoxResult<()> { ) -> BoxResult<()> {
let mut paths: Vec<PathItem> = vec![]; let mut paths: Vec<PathItem> = vec![];
let path_buf = path.to_path_buf(); let path_buf = path.to_path_buf();
let hidden = self.args.hidden.to_string(); let hidden = Arc::new(self.args.hidden.to_vec());
let hidden = hidden.clone();
let running = self.running.clone(); let running = self.running.clone();
let search = search.to_lowercase(); let search = search.to_lowercase();
let search_paths = tokio::task::spawn_blocking(move || { let search_paths = tokio::task::spawn_blocking(move || {
@@ -1008,11 +1032,19 @@ fn add_cors(res: &mut Response) {
.typed_insert(AccessControlAllowOrigin::ANY); .typed_insert(AccessControlAllowOrigin::ANY);
res.headers_mut() res.headers_mut()
.typed_insert(AccessControlAllowCredentials); .typed_insert(AccessControlAllowCredentials);
res.headers_mut().insert(
res.headers_mut().typed_insert( "Access-Control-Allow-Methods",
vec![RANGE, CONTENT_TYPE, ACCEPT, ORIGIN, WWW_AUTHENTICATE] HeaderValue::from_static("GET,HEAD,PUT,OPTIONS,DELETE,PROPFIND,COPY,MOVE"),
.into_iter() );
.collect::<AccessControlAllowHeaders>(), res.headers_mut().insert(
"Access-Control-Allow-Headers",
HeaderValue::from_static("Authorization,Destination,Range"),
);
res.headers_mut().insert(
"Access-Control-Expose-Headers",
HeaderValue::from_static(
"WWW-Authenticate,Content-Range,Accept-Ranges,Content-Disposition",
),
); );
} }
@@ -1034,12 +1066,12 @@ fn res_multistatus(res: &mut Response, content: &str) {
async fn zip_dir<W: AsyncWrite + Unpin>( async fn zip_dir<W: AsyncWrite + Unpin>(
writer: &mut W, writer: &mut W,
dir: &Path, dir: &Path,
hidden: &str, hidden: &[String],
running: Arc<AtomicBool>, running: Arc<AtomicBool>,
) -> BoxResult<()> { ) -> BoxResult<()> {
let mut writer = ZipFileWriter::new(writer); let mut writer = ZipFileWriter::new(writer);
let hidden = Arc::new(hidden.to_string()); let hidden = Arc::new(hidden.to_vec());
let hidden = hidden.to_string(); let hidden = hidden.clone();
let dir_path_buf = dir.to_path_buf(); let dir_path_buf = dir.to_path_buf();
let zip_paths = tokio::task::spawn_blocking(move || { let zip_paths = tokio::task::spawn_blocking(move || {
let mut it = WalkDir::new(&dir_path_buf).into_iter(); let mut it = WalkDir::new(&dir_path_buf).into_iter();
@@ -1139,8 +1171,8 @@ fn status_no_content(res: &mut Response) {
*res.status_mut() = StatusCode::NO_CONTENT; *res.status_mut() = StatusCode::NO_CONTENT;
} }
fn is_hidden(hidden: &str, file_name: &str) -> bool { fn is_hidden(hidden: &[String], file_name: &str) -> bool {
hidden.contains(&format!(",{},", file_name)) hidden.iter().any(|v| glob(v, file_name))
} }
fn set_webdav_headers(res: &mut Response) { fn set_webdav_headers(res: &mut Response) {

View File

@@ -23,3 +23,61 @@ pub fn try_get_file_name(path: &Path) -> BoxResult<&str> {
.and_then(|v| v.to_str()) .and_then(|v| v.to_str())
.ok_or_else(|| format!("Failed to get file name of `{}`", path.display()).into()) .ok_or_else(|| format!("Failed to get file name of `{}`", path.display()).into())
} }
pub fn glob(source: &str, target: &str) -> bool {
let ss: Vec<char> = source.chars().collect();
let mut iter = target.chars();
let mut i = 0;
'outer: while i < ss.len() {
let s = ss[i];
match s {
'*' => match ss.get(i + 1) {
Some(s_next) => {
for t in iter.by_ref() {
if t == *s_next {
i += 2;
continue 'outer;
}
}
return true;
}
None => return true,
},
'?' => match iter.next() {
Some(_) => {
i += 1;
continue;
}
None => return false,
},
_ => match iter.next() {
Some(t) => {
if s == t {
i += 1;
continue;
}
return false;
}
None => return false,
},
}
}
iter.next().is_none()
}
#[test]
fn test_glob_key() {
assert!(glob("", ""));
assert!(glob(".*", ".git"));
assert!(glob("abc", "abc"));
assert!(glob("a*c", "abc"));
assert!(glob("a?c", "abc"));
assert!(glob("a*c", "abbc"));
assert!(glob("*c", "abc"));
assert!(glob("a*", "abc"));
assert!(glob("?c", "bc"));
assert!(glob("a?", "ab"));
assert!(!glob("abc", "adc"));
assert!(!glob("abc", "abcd"));
assert!(!glob("a?c", "abbc"));
}

View File

@@ -3,11 +3,8 @@
mod fixtures; mod fixtures;
mod utils; mod utils;
use assert_cmd::prelude::*; use fixtures::{server, Error, TestServer};
use assert_fs::fixture::TempDir;
use fixtures::{port, server, tmpdir, wait_for_port, Error, TestServer};
use rstest::rstest; use rstest::rstest;
use std::process::{Command, Stdio};
#[rstest] #[rstest]
fn path_prefix_index(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> { fn path_prefix_index(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
@@ -33,22 +30,3 @@ fn path_prefix_propfind(
assert!(text.contains("<D:href>/xyz/</D:href>")); assert!(text.contains("<D:href>/xyz/</D:href>"));
Ok(()) Ok(())
} }
#[rstest]
#[case("index.html")]
fn serve_single_file(tmpdir: TempDir, port: u16, #[case] file: &str) -> Result<(), Error> {
let mut child = Command::cargo_bin("dufs")?
.arg(tmpdir.path().join(file))
.arg("-p")
.arg(port.to_string())
.stdout(Stdio::piped())
.spawn()?;
wait_for_port(port);
let resp = reqwest::blocking::get(format!("http://localhost:{}/index.html", port))?;
assert_eq!(resp.text()?, "This is index.html");
child.kill()?;
Ok(())
}

View File

@@ -7,31 +7,27 @@ use rstest::rstest;
#[rstest] #[rstest]
fn cors(#[with(&["--enable-cors"])] server: TestServer) -> Result<(), Error> { fn cors(#[with(&["--enable-cors"])] server: TestServer) -> Result<(), Error> {
let resp = reqwest::blocking::get(server.url())?; let resp = reqwest::blocking::get(server.url())?;
assert_eq!( assert_eq!(
resp.headers().get("access-control-allow-origin").unwrap(), resp.headers().get("access-control-allow-origin").unwrap(),
"*" "*"
); );
assert_eq!( assert_eq!(
resp.headers().get("access-control-allow-headers").unwrap(), resp.headers()
"range, content-type, accept, origin, www-authenticate" .get("access-control-allow-credentials")
.unwrap(),
"true"
); );
Ok(())
}
#[rstest]
fn cors_options(#[with(&["--enable-cors"])] server: TestServer) -> Result<(), Error> {
let resp = fetch!(b"OPTIONS", server.url()).send()?;
assert_eq!( assert_eq!(
resp.headers().get("access-control-allow-origin").unwrap(), resp.headers().get("access-control-allow-methods").unwrap(),
"*" "GET,HEAD,PUT,OPTIONS,DELETE,PROPFIND,COPY,MOVE"
); );
assert_eq!( assert_eq!(
resp.headers().get("access-control-allow-headers").unwrap(), resp.headers().get("access-control-allow-headers").unwrap(),
"range, content-type, accept, origin, www-authenticate" "Authorization,Destination,Range"
);
assert_eq!(
resp.headers().get("access-control-expose-headers").unwrap(),
"WWW-Authenticate,Content-Range,Accept-Ranges,Content-Disposition"
); );
Ok(()) Ok(())
} }

60
tests/single_file.rs Normal file
View File

@@ -0,0 +1,60 @@
//! Run file server with different args
mod fixtures;
mod utils;
use assert_cmd::prelude::*;
use assert_fs::fixture::TempDir;
use fixtures::{port, tmpdir, wait_for_port, Error};
use rstest::rstest;
use std::process::{Command, Stdio};
#[rstest]
#[case("index.html")]
fn single_file(tmpdir: TempDir, port: u16, #[case] file: &str) -> Result<(), Error> {
let mut child = Command::cargo_bin("dufs")?
.arg(tmpdir.path().join(file))
.arg("-p")
.arg(port.to_string())
.stdout(Stdio::piped())
.spawn()?;
wait_for_port(port);
let resp = reqwest::blocking::get(format!("http://localhost:{}", port))?;
assert_eq!(resp.text()?, "This is index.html");
let resp = reqwest::blocking::get(format!("http://localhost:{}/", port))?;
assert_eq!(resp.text()?, "This is index.html");
let resp = reqwest::blocking::get(format!("http://localhost:{}/index.html", port))?;
assert_eq!(resp.text()?, "This is index.html");
child.kill()?;
Ok(())
}
#[rstest]
#[case("index.html")]
fn path_prefix_single_file(tmpdir: TempDir, port: u16, #[case] file: &str) -> Result<(), Error> {
let mut child = Command::cargo_bin("dufs")?
.arg(tmpdir.path().join(file))
.arg("-p")
.arg(port.to_string())
.arg("--path-prefix")
.arg("xyz")
.stdout(Stdio::piped())
.spawn()?;
wait_for_port(port);
let resp = reqwest::blocking::get(format!("http://localhost:{}/xyz", port))?;
assert_eq!(resp.text()?, "This is index.html");
let resp = reqwest::blocking::get(format!("http://localhost:{}/xyz/", port))?;
assert_eq!(resp.text()?, "This is index.html");
let resp = reqwest::blocking::get(format!("http://localhost:{}/xyz/index.html", port))?;
assert_eq!(resp.text()?, "This is index.html");
let resp = reqwest::blocking::get(format!("http://localhost:{}", port))?;
assert_eq!(resp.status(), 404);
child.kill()?;
Ok(())
}