mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-09 17:13:02 +03:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09bb738866 | ||
|
|
3612ef10d1 | ||
|
|
7ac2039a36 | ||
|
|
7f83de765a | ||
|
|
9b3779b13a | ||
|
|
11a52f29c4 | ||
|
|
10204c723f | ||
|
|
204421643d | ||
|
|
d9706d75ef | ||
|
|
40df0bd2f9 | ||
|
|
a53411b4d6 | ||
|
|
609017b2f5 | ||
|
|
7dc0b0e218 | ||
|
|
6be36b8e51 | ||
|
|
8be545d3da |
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@@ -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
|
||||||
|
|||||||
80
.github/workflows/release.yaml
vendored
80
.github/workflows/release.yaml
vendored
@@ -71,31 +71,41 @@ 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
|
||||||
run: |
|
run: |
|
||||||
@@ -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 }}
|
||||||
@@ -184,20 +190,18 @@ jobs:
|
|||||||
linux/arm/v7
|
linux/arm/v7
|
||||||
push: ${{ needs.release.outputs.rc == 'false' }}
|
push: ${{ needs.release.outputs.rc == 'false' }}
|
||||||
tags: ${{ github.repository }}:latest, ${{ github.repository }}:${{ github.ref_name }}
|
tags: ${{ github.repository }}:latest, ${{ github.repository }}:${{ github.ref_name }}
|
||||||
|
|
||||||
publish-crate:
|
publish-crate:
|
||||||
name: Publish to crates.io
|
name: Publish to crates.io
|
||||||
if: ${{ needs.release.outputs.rc == 'false' }}
|
if: ${{ needs.release.outputs.rc == 'false' }}
|
||||||
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
|
||||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -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
563
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@@ -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"
|
||||||
17
README.md
17
README.md
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/auth.rs
20
src/auth.rs
@@ -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
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user