Compare commits

...

15 Commits

Author SHA1 Message Date
sigoden
09bb738866 chore: update changelog 2023-08-15 07:29:02 +08:00
sigoden
3612ef10d1 chore: release 0.35.0 (#254)
* chore: release 0.35.0

* update release profile
2023-08-15 07:24:22 +08:00
sigoden
7ac2039a36 chore: update deps 2023-08-14 17:31:52 +08:00
sigoden
7f83de765a fix: typo __ASSERTS_PREFIX__ (#252) 2023-08-13 15:05:45 +08:00
sigoden
9b3779b13a chore: update readme
close #247
2023-07-20 06:33:17 +08:00
sigoden
11a52f29c4 chore: fix release ci (#244) 2023-07-15 16:34:22 +08:00
sigoden
10204c723f chore: fix clippy (#245) 2023-07-15 16:27:13 +08:00
sigoden
204421643d chore: update ci (#242) 2023-07-04 10:25:49 +08:00
sigoden
d9706d75ef feat: sort by type first, then sort by name/mtime/size (#241) 2023-07-04 10:10:48 +08:00
sigoden
40df0bd2f9 chore: update readme 2023-06-18 08:55:42 +08:00
sigoden
a53411b4d6 fix: search should ignore entry path (#235) 2023-06-15 08:28:21 +08:00
ElmTran
609017b2f5 chore: Update README.md (#233)
update examples on new auth.
2023-06-13 08:23:05 +08:00
sigoden
7dc0b0e218 chore: release v0.34.2 2023-06-05 11:51:56 +08:00
sigoden
6be36b8e51 fix: webdav only see public folder even logging in (#231) 2023-06-05 11:40:31 +08:00
sigoden
8be545d3da fix: ui refresh page after login (#230) 2023-06-03 10:09:02 +08:00
13 changed files with 481 additions and 349 deletions

View File

@@ -29,16 +29,12 @@ jobs:
RUSTFLAGS: --deny warnings RUSTFLAGS: --deny warnings
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install Rust Toolchain Components - name: Install Rust Toolchain Components
uses: actions-rs/toolchain@v1 uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
override: true
toolchain: stable
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v2
- name: Test - name: Test
run: cargo test --all run: cargo test --all

View File

@@ -71,30 +71,40 @@ jobs:
use-cross: true use-cross: true
cargo-flags: "--no-default-features" cargo-flags: "--no-default-features"
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
env:
BUILD_CMD: cargo
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Check Tag - name: Check Tag
id: check-tag id: check-tag
shell: bash shell: bash
run: | run: |
tag=${GITHUB_REF##*/} ver=${GITHUB_REF##*/}
echo "::set-output name=version::$tag" echo "version=$ver" >> $GITHUB_OUTPUT
if [[ "$tag" =~ [0-9]+.[0-9]+.[0-9]+$ ]]; then if [[ "$ver" =~ [0-9]+.[0-9]+.[0-9]+$ ]]; then
echo "::set-output name=rc::false" echo "rc=false" >> $GITHUB_OUTPUT
else else
echo "::set-output name=rc::true" echo "rc=true" >> $GITHUB_OUTPUT
fi fi
- name: Install Rust Toolchain Components - name: Install Rust Toolchain Components
uses: actions-rs/toolchain@v1 uses: dtolnay/rust-toolchain@stable
with: with:
override: true targets: ${{ matrix.target }}
target: ${{ matrix.target }}
toolchain: stable - name: Install cross
profile: minimal # minimal component installation (ie, no documentation) if: matrix.use-cross
uses: taiki-e/install-action@v2
with:
tool: cross
- name: Overwrite build command env variable
if: matrix.use-cross
shell: bash
run: echo "BUILD_CMD=cross" >> $GITHUB_ENV
- name: Show Version Information (Rust, cargo, GCC) - name: Show Version Information (Rust, cargo, GCC)
shell: bash shell: bash
@@ -107,11 +117,8 @@ jobs:
rustc -V rustc -V
- name: Build - name: Build
uses: actions-rs/cargo@v1 shell: bash
with: run: $BUILD_CMD build --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }}
use-cross: ${{ matrix.use-cross }}
command: build
args: --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }}
- name: Build Archive - name: Build Archive
shell: bash shell: bash
@@ -123,8 +130,7 @@ jobs:
set -euxo pipefail set -euxo pipefail
bin=${GITHUB_REPOSITORY##*/} bin=${GITHUB_REPOSITORY##*/}
src=`pwd` dist_dir=`pwd`/dist
dist=$src/dist
name=$bin-$version-$target name=$bin-$version-$target
executable=target/$target/release/$bin executable=target/$target/release/$bin
@@ -132,22 +138,22 @@ jobs:
executable=$executable.exe executable=$executable.exe
fi fi
mkdir $dist mkdir $dist_dir
cp $executable $dist cp $executable $dist_dir
cd $dist cd $dist_dir
if [[ "$RUNNER_OS" == "Windows" ]]; then if [[ "$RUNNER_OS" == "Windows" ]]; then
archive=$dist/$name.zip archive=$dist_dir/$name.zip
7z a $archive * 7z a $archive *
echo "::set-output name=archive::`pwd -W`/$name.zip" echo "archive=dist/$name.zip" >> $GITHUB_OUTPUT
else else
archive=$dist/$name.tar.gz archive=$dist_dir/$name.tar.gz
tar czf $archive * tar -czf $archive *
echo "::set-output name=archive::$archive" echo "archive=dist/$name.tar.gz" >> $GITHUB_OUTPUT
fi fi
- name: Publish Archive - name: Publish Archive
uses: softprops/action-gh-release@v0.1.5 uses: softprops/action-gh-release@v1
if: ${{ startsWith(github.ref, 'refs/tags/') }} if: ${{ startsWith(github.ref, 'refs/tags/') }}
with: with:
draft: false draft: false
@@ -163,16 +169,16 @@ jobs:
needs: release needs: release
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v2 uses: docker/build-push-action@v4
with: with:
build-args: | build-args: |
REPO=${{ github.repository }} REPO=${{ github.repository }}
@@ -191,13 +197,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: release needs: release
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
- name: Publish
- uses: dtolnay/rust-toolchain@stable
- name: Publish
env: env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}
run: cargo publish run: cargo publish

View File

@@ -2,6 +2,24 @@
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.35.0] - 2023-08-14
### Bug Fixes
- Search should ignore entry path ([#235](https://github.com/sigoden/dufs/issues/235))
- Typo __ASSERTS_PREFIX__ ([#252](https://github.com/sigoden/dufs/issues/252))
### Features
- Sort by type first, then sort by name/mtime/size ([#241](https://github.com/sigoden/dufs/issues/241))
## [0.34.2] - 2023-06-05
### Bug Fixes
- Ui refresh page after login ([#230](https://github.com/sigoden/dufs/issues/230))
- Webdav only see public folder even logging in ([#231](https://github.com/sigoden/dufs/issues/231))
## [0.34.1] - 2023-06-02 ## [0.34.1] - 2023-06-02
### Bug Fixes ### Bug Fixes

563
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "dufs" name = "dufs"
version = "0.34.1" version = "0.35.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"
@@ -17,7 +17,7 @@ chrono = "0.4"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util", "signal"]} tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util", "signal"]}
tokio-util = { version = "0.7", features = ["io-util", "compat"] } tokio-util = { version = "0.7", features = ["io-util", "compat"] }
hyper = { version = "0.14", features = ["http1", "server", "tcp", "stream"] } hyper = { version = "0.14", features = ["http1", "server", "tcp", "stream"] }
percent-encoding = "2.1" percent-encoding = "2.3"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
futures = "0.3" futures = "0.3"
@@ -31,20 +31,20 @@ rustls-pemfile = { version = "1", optional = true }
tokio-rustls = { version = "0.24", optional = true } tokio-rustls = { version = "0.24", optional = true }
md5 = "0.7" md5 = "0.7"
lazy_static = "1.4" lazy_static = "1.4"
uuid = { version = "1.1", features = ["v4", "fast-rng"] } uuid = { version = "1.4", features = ["v4", "fast-rng"] }
urlencoding = "2.1" urlencoding = "2.1"
xml-rs = "0.8" xml-rs = "0.8"
log = "0.4" log = "0.4"
socket2 = "0.5" socket2 = "0.5"
async-stream = "0.3" async-stream = "0.3"
walkdir = "2.3" walkdir = "2.3"
form_urlencoded = "1.0" form_urlencoded = "1.2"
alphanumeric-sort = "1.4" alphanumeric-sort = "1.4"
content_inspector = "0.2" content_inspector = "0.2"
anyhow = "1.0" anyhow = "1.0"
chardetng = "0.1" chardetng = "0.1"
glob = "0.3.1" glob = "0.3.1"
indexmap = "1.9" indexmap = "2.0"
[features] [features]
default = ["tls"] default = ["tls"]
@@ -55,13 +55,15 @@ assert_cmd = "2"
reqwest = { version = "0.11", features = ["blocking", "multipart", "rustls-tls"], default-features = false } reqwest = { version = "0.11", features = ["blocking", "multipart", "rustls-tls"], default-features = false }
assert_fs = "1" assert_fs = "1"
port_check = "0.1" port_check = "0.1"
rstest = "0.17" rstest = "0.18"
regex = "1" regex = "1"
url = "2" url = "2"
diqwest = { version = "1", features = ["blocking"] } diqwest = { version = "1", features = ["blocking"] }
predicates = "3" predicates = "3"
[profile.release] [profile.release]
opt-level = 3
lto = true lto = true
strip = true codegen-units = 1
opt-level = "z" panic = "abort"
strip = "symbols"

View File

@@ -126,7 +126,7 @@ dufs --render-index
Require username/password Require username/password
``` ```
dufs -a /@admin:123 dufs -a admin:123@/:rw
``` ```
Listen on specific host:ip Listen on specific host:ip
@@ -208,10 +208,11 @@ Dufs supports account based access control. You can control who can do what on w
``` ```
dufs -a [user:pass]@path[:rw][,path[:rw]...][|...] dufs -a [user:pass]@path[:rw][,path[:rw]...][|...]
``` ```
1: Multiple rules are separated by "|"
2: User and pass are the account name and password, if omitted, it is an anonymous user 1. Multiple rules are separated by "|"
3: One rule can set multiple paths, separated by "," 2. User and pass are the account name and password, if omitted, it is an anonymous user
4: Add `:rw` after the path to indicate that the path has read and write permissions, otherwise the path has readonly permissions. 3. One rule can set multiple paths, separated by ","
4. Add `:rw` after the path to indicate that the path has read and write permissions, otherwise the path has readonly permissions.
``` ```
dufs -A -a admin:admin@/:rw dufs -A -a admin:admin@/:rw
@@ -307,10 +308,10 @@ dufs --log-format '$remote_addr $remote_user "$request" $status' -a /@admin:admi
All options can be set using environment variables prefixed with `DUFS_`. All options can be set using environment variables prefixed with `DUFS_`.
``` ```
[ROOT_DIR] DUFS_ROOT_DIR=/dir [SERVE_PATH] DUFS_SERVE_PATH=/dir
-b, --bind <addrs> DUFS_BIND=0.0.0.0 -b, --bind <addrs> DUFS_BIND=0.0.0.0
-p, --port <port> DUFS_PORT=5000 -p, --port <port> DUFS_PORT=5000
--path-prefix <path> DUFS_PATH_RREFIX=/path --path-prefix <path> DUFS_PATH_PREFIX=/path
--hidden <value> DUFS_HIDDEN=*.log --hidden <value> DUFS_HIDDEN=*.log
-a, --auth <rules> DUFS_AUTH="admin:admin@/:rw|@/" -a, --auth <rules> DUFS_AUTH="admin:admin@/:rw|@/"
--auth-method <value> DUFS_AUTH_METHOD=basic --auth-method <value> DUFS_AUTH_METHOD=basic
@@ -343,7 +344,7 @@ Your assets folder must contains a `index.html` file.
`index.html` can use the following placeholder variables to retrieve internal data. `index.html` can use the following placeholder variables to retrieve internal data.
- `__INDEX_DATA__`: directory listing data - `__INDEX_DATA__`: directory listing data
- `__ASSERTS_PREFIX__`: assets url prefix - `__ASSETS_PREFIX__`: assets url prefix
</details> </details>

View File

@@ -4,12 +4,12 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/x-icon" href="__ASSERTS_PREFIX__favicon.ico"> <link rel="icon" type="image/x-icon" href="__ASSETS_PREFIX__favicon.ico">
<link rel="stylesheet" href="__ASSERTS_PREFIX__index.css"> <link rel="stylesheet" href="__ASSETS_PREFIX__index.css">
<script> <script>
DATA = __INDEX_DATA__ DATA = __INDEX_DATA__
</script> </script>
<script src="__ASSERTS_PREFIX__index.js"></script> <script src="__ASSETS_PREFIX__index.js"></script>
</head> </head>
<body> <body>

View File

@@ -445,6 +445,7 @@ function setupAuth() {
$loginBtn.addEventListener("click", async () => { $loginBtn.addEventListener("click", async () => {
try { try {
await checkAuth() await checkAuth()
location.reload();
} catch (err) { } catch (err) {
alert(err.message); alert(err.message);
} }

View File

@@ -80,8 +80,8 @@ impl AccessControl {
Ok(Self { users, anony }) Ok(Self { users, anony })
} }
pub fn valid(&self) -> bool { pub fn exist(&self) -> bool {
!self.users.is_empty() || self.anony.is_some() !self.users.is_empty()
} }
pub fn guard( pub fn guard(
@@ -257,18 +257,14 @@ pub enum AuthMethod {
} }
impl AuthMethod { impl AuthMethod {
pub fn www_auth(&self, stale: bool) -> Result<String> { pub fn www_auth(&self) -> Result<String> {
match self { match self {
AuthMethod::Basic => Ok(format!("Basic realm=\"{REALM}\"")), AuthMethod::Basic => Ok(format!("Basic realm=\"{REALM}\"")),
AuthMethod::Digest => { AuthMethod::Digest => Ok(format!(
let str_stale = if stale { "stale=true," } else { "" }; "Digest realm=\"{}\",nonce=\"{}\",qop=\"auth\"",
Ok(format!( REALM,
"Digest realm=\"{}\",nonce=\"{}\",{}qop=\"auth\"", create_nonce()?,
REALM, )),
create_nonce()?,
str_stale
))
}
} }
} }

View File

@@ -1,6 +1,6 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
use crate::auth::AccessPaths; use crate::auth::{AccessPaths, AccessPerm};
use crate::streamer::Streamer; use crate::streamer::Streamer;
use crate::utils::{ use crate::utils::{
decode_uri, encode_uri, get_file_mtime_and_mode, get_file_name, glob, try_get_file_name, decode_uri, encode_uri, get_file_mtime_and_mode, get_file_name, glob, try_get_file_name,
@@ -26,12 +26,13 @@ use hyper::header::{
use hyper::{Body, Method, StatusCode, Uri}; use hyper::{Body, Method, StatusCode, Uri};
use serde::Serialize; use serde::Serialize;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::Metadata; use std::fs::Metadata;
use std::io::SeekFrom; use std::io::SeekFrom;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{self, AtomicBool};
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use tokio::fs::File; use tokio::fs::File;
@@ -340,6 +341,12 @@ impl Server {
method => match method.as_str() { method => match method.as_str() {
"PROPFIND" => { "PROPFIND" => {
if is_dir { if is_dir {
let access_paths = if access_paths.perm().indexonly() {
// see https://github.com/sigoden/dufs/issues/229
AccessPaths::new(AccessPerm::ReadOnly)
} else {
access_paths
};
self.handle_propfind_dir(path, headers, access_paths, &mut res) self.handle_propfind_dir(path, headers, access_paths, &mut res)
.await?; .await?;
} else if is_file { } else if is_file {
@@ -487,8 +494,9 @@ impl Server {
let mut paths: Vec<PathBuf> = vec![]; let mut paths: Vec<PathBuf> = vec![];
for dir in access_paths.leaf_paths(&path_buf) { for dir in access_paths.leaf_paths(&path_buf) {
let mut it = WalkDir::new(&dir).into_iter(); let mut it = WalkDir::new(&dir).into_iter();
it.next();
while let Some(Ok(entry)) = it.next() { while let Some(Ok(entry)) = it.next() {
if !running.load(Ordering::SeqCst) { if !running.load(atomic::Ordering::SeqCst) {
break; break;
} }
let entry_path = entry.path(); let entry_path = entry.path();
@@ -759,7 +767,7 @@ impl Server {
uri_prefix: self.args.uri_prefix.clone(), uri_prefix: self.args.uri_prefix.clone(),
allow_upload: self.args.allow_upload, allow_upload: self.args.allow_upload,
allow_delete: self.args.allow_delete, allow_delete: self.args.allow_delete,
auth: self.args.auth.valid(), auth: self.args.auth.exist(),
user, user,
editable, editable,
}; };
@@ -767,7 +775,7 @@ impl Server {
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8)); .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
let output = self let output = self
.html .html
.replace("__ASSERTS_PREFIX__", &self.assets_prefix) .replace("__ASSETS_PREFIX__", &self.assets_prefix)
.replace("__INDEX_DATA__", &serde_json::to_string(&data)?); .replace("__INDEX_DATA__", &serde_json::to_string(&data)?);
res.headers_mut() res.headers_mut()
.typed_insert(ContentLength(output.as_bytes().len() as u64)); .typed_insert(ContentLength(output.as_bytes().len() as u64));
@@ -924,13 +932,28 @@ impl Server {
) -> Result<()> { ) -> Result<()> {
if let Some(sort) = query_params.get("sort") { if let Some(sort) = query_params.get("sort") {
if sort == "name" { if sort == "name" {
paths.sort_by(|v1, v2| { paths.sort_by(|v1, v2| match v1.path_type.cmp(&v2.path_type) {
alphanumeric_sort::compare_str(v1.name.to_lowercase(), v2.name.to_lowercase()) Ordering::Equal => {
alphanumeric_sort::compare_str(v1.name.clone(), v2.name.clone())
}
v => v,
}) })
} else if sort == "mtime" { } else if sort == "mtime" {
paths.sort_by(|v1, v2| v1.mtime.cmp(&v2.mtime)) paths.sort_by(|v1, v2| match v1.path_type.cmp(&v2.path_type) {
Ordering::Equal => v1.mtime.cmp(&v2.mtime),
v => v,
})
} else if sort == "size" { } else if sort == "size" {
paths.sort_by(|v1, v2| v1.size.unwrap_or(0).cmp(&v2.size.unwrap_or(0))) paths.sort_by(|v1, v2| match v1.path_type.cmp(&v2.path_type) {
Ordering::Equal => {
if v1.is_dir() {
alphanumeric_sort::compare_str(v1.name.clone(), v2.name.clone())
} else {
v1.size.unwrap_or(0).cmp(&v2.size.unwrap_or(0))
}
}
v => v,
})
} }
if query_params if query_params
.get("order") .get("order")
@@ -974,7 +997,7 @@ impl Server {
allow_search: self.args.allow_search, allow_search: self.args.allow_search,
allow_archive: self.args.allow_archive, allow_archive: self.args.allow_archive,
dir_exists: exist, dir_exists: exist,
auth: self.args.auth.valid(), auth: self.args.auth.exist(),
user, user,
paths, paths,
}; };
@@ -986,7 +1009,7 @@ impl Server {
res.headers_mut() res.headers_mut()
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8)); .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
self.html self.html
.replace("__ASSERTS_PREFIX__", &self.assets_prefix) .replace("__ASSETS_PREFIX__", &self.assets_prefix)
.replace("__INDEX_DATA__", &serde_json::to_string(&data)?) .replace("__INDEX_DATA__", &serde_json::to_string(&data)?)
}; };
res.headers_mut() res.headers_mut()
@@ -999,7 +1022,7 @@ impl Server {
} }
fn auth_reject(&self, res: &mut Response) -> Result<()> { fn auth_reject(&self, res: &mut Response) -> Result<()> {
let value = self.args.auth_method.www_auth(false)?; let value = self.args.auth_method.www_auth()?;
set_webdav_headers(res); set_webdav_headers(res);
res.headers_mut().insert(WWW_AUTHENTICATE, value.parse()?); res.headers_mut().insert(WWW_AUTHENTICATE, value.parse()?);
// set 401 to make the browser pop up the login box // set 401 to make the browser pop up the login box
@@ -1247,7 +1270,7 @@ impl PathItem {
} }
} }
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Serialize, Eq, PartialEq)]
enum PathType { enum PathType {
Dir, Dir,
SymlinkDir, SymlinkDir,
@@ -1255,6 +1278,24 @@ enum PathType {
SymlinkFile, SymlinkFile,
} }
impl Ord for PathType {
fn cmp(&self, other: &Self) -> Ordering {
let to_value = |t: &Self| -> u8 {
if matches!(t, Self::Dir | Self::SymlinkDir) {
0
} else {
1
}
};
to_value(self).cmp(&to_value(other))
}
}
impl PartialOrd for PathType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn to_timestamp(time: &SystemTime) -> u64 { fn to_timestamp(time: &SystemTime) -> u64 {
time.duration_since(SystemTime::UNIX_EPOCH) time.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default() .unwrap_or_default()
@@ -1327,8 +1368,9 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
let mut paths: Vec<PathBuf> = vec![]; let mut paths: Vec<PathBuf> = vec![];
for dir in access_paths.leaf_paths(&dir_clone) { for dir in access_paths.leaf_paths(&dir_clone) {
let mut it = WalkDir::new(&dir).into_iter(); let mut it = WalkDir::new(&dir).into_iter();
it.next();
while let Some(Ok(entry)) = it.next() { while let Some(Ok(entry)) = it.next() {
if !running.load(Ordering::SeqCst) { if !running.load(atomic::Ordering::SeqCst) {
break; break;
} }
let entry_path = entry.path(); let entry_path = entry.path();

View File

@@ -201,3 +201,15 @@ fn auth_partial_index(
); );
Ok(()) Ok(())
} }
#[rstest]
fn no_auth_propfind_dir(
#[with(&["--auth", "user:pass@/:rw", "--auth", "@/dir-assets", "-A"])] server: TestServer,
) -> Result<(), Error> {
let resp = fetch!(b"PROPFIND", server.url()).send()?;
assert_eq!(resp.status(), 207);
let body = resp.text()?;
assert!(body.contains("<D:href>/dir-assets/</D:href>"));
assert!(body.contains("<D:href>/dir1/</D:href>"));
Ok(())
}

View File

@@ -46,7 +46,7 @@ pub fn tmpdir() -> TempDir {
let tmpdir = assert_fs::TempDir::new().expect("Couldn't create a temp dir for tests"); let tmpdir = assert_fs::TempDir::new().expect("Couldn't create a temp dir for tests");
for file in FILES { for file in FILES {
if *file == BIN_FILE { if *file == BIN_FILE {
tmpdir.child(file).write_binary(b"bin\0\0123").unwrap(); tmpdir.child(file).write_binary(b"bin\0\x00123").unwrap();
} else { } else {
tmpdir tmpdir
.child(file) .child(file)
@@ -58,7 +58,7 @@ pub fn tmpdir() -> TempDir {
if *directory == DIR_ASSETS { if *directory == DIR_ASSETS {
tmpdir tmpdir
.child(format!("{}{}", directory, "index.html")) .child(format!("{}{}", directory, "index.html"))
.write_str("__ASSERTS_PREFIX__index.js;DATA = __INDEX_DATA__") .write_str("__ASSETS_PREFIX__index.js;DATA = __INDEX_DATA__")
.unwrap(); .unwrap();
} else { } else {
for file in FILES { for file in FILES {
@@ -68,7 +68,7 @@ pub fn tmpdir() -> TempDir {
if *file == BIN_FILE { if *file == BIN_FILE {
tmpdir tmpdir
.child(format!("{directory}{file}")) .child(format!("{directory}{file}"))
.write_binary(b"bin\0\0123") .write_binary(b"bin\0\x00123")
.unwrap(); .unwrap();
} else { } else {
tmpdir tmpdir

View File

@@ -123,6 +123,15 @@ fn get_dir_search3(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[rstest]
fn get_dir_search4(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
let resp = reqwest::blocking::get(format!("{}dir1?q=dir1&simple", server.url()))?;
assert_eq!(resp.status(), 200);
let text = resp.text().unwrap();
assert!(text.is_empty());
Ok(())
}
#[rstest] #[rstest]
fn head_dir_search(#[with(&["-A"])] server: TestServer) -> Result<(), Error> { fn head_dir_search(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
let resp = fetch!(b"HEAD", format!("{}?q={}", server.url(), "test.html")).send()?; let resp = fetch!(b"HEAD", format!("{}?q={}", server.url(), "test.html")).send()?;