mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-09 09:09:03 +03:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
147048690f | ||
|
|
9cfd66dab9 | ||
|
|
b791549ec7 | ||
|
|
f148817c52 | ||
|
|
00ae36d486 | ||
|
|
4e823e8bba | ||
|
|
4e84e6c532 | ||
|
|
f49b590a56 |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -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
78
Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -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
|
||||||
|
|||||||
11
src/args.rs
11
src/args.rs
@@ -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
|
||||||
|
|||||||
@@ -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 ?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
58
src/utils.rs
58
src/utils.rs
@@ -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"));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
60
tests/single_file.rs
Normal 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(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user