mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-09 17:13:02 +03:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ed0d885fe | ||
|
|
542e9a4ec5 | ||
|
|
5ee2c5504c | ||
|
|
fd02a53823 | ||
|
|
6554c1c308 | ||
|
|
fe71600bd2 | ||
|
|
9cfeee0df0 | ||
|
|
eb7a536a3f |
72
.github/workflows/release.yaml
vendored
72
.github/workflows/release.yaml
vendored
@@ -7,33 +7,67 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Publish to Github Reelases
|
name: Publish to Github Relases
|
||||||
outputs:
|
outputs:
|
||||||
rc: ${{ steps.check-tag.outputs.rc }}
|
rc: ${{ steps.check-tag.outputs.rc }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
|
||||||
- aarch64-unknown-linux-musl
|
|
||||||
- aarch64-apple-darwin
|
|
||||||
- x86_64-apple-darwin
|
|
||||||
- x86_64-pc-windows-msvc
|
|
||||||
- x86_64-unknown-linux-musl
|
|
||||||
include:
|
include:
|
||||||
- target: aarch64-unknown-linux-musl
|
- target: aarch64-unknown-linux-musl
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
use-cross: true
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
use-cross: true
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: aarch64-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
|
cargo-flags: ""
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
|
cargo-flags: ""
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
use-cross: true
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: i686-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: i686-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: armv7-unknown-linux-musleabihf
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: arm-unknown-linux-musleabihf
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: mips-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
- target: mipsel-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
- target: mips64-unknown-linux-gnuabi64
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
- target: mips64el-unknown-linux-gnuabi64
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -59,14 +93,7 @@ jobs:
|
|||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
profile: minimal # minimal component installation (ie, no documentation)
|
profile: minimal # minimal component installation (ie, no documentation)
|
||||||
|
|
||||||
- name: Install prerequisites
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
case ${{ matrix.target }} in
|
|
||||||
aarch64-unknown-linux-musl) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Show Version Information (Rust, cargo, GCC)
|
- name: Show Version Information (Rust, cargo, GCC)
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -82,7 +109,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
use-cross: ${{ matrix.use-cross }}
|
use-cross: ${{ matrix.use-cross }}
|
||||||
command: build
|
command: build
|
||||||
args: --locked --release --target=${{ matrix.target }}
|
args: --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }}
|
||||||
|
|
||||||
- name: Build Archive
|
- name: Build Archive
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -133,6 +160,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: release
|
needs: release
|
||||||
steps:
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
@@ -141,9 +170,16 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
|
build-args: |
|
||||||
|
REPO=${{ github.repository }}
|
||||||
|
VER=${{ github.ref_name }}
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
linux/arm64
|
||||||
|
linux/386
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
|||||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -2,7 +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.21.0] - 2022-06-21
|
## [0.23.0] - 2022-06-29
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Use feature to conditional support tls ([#77](https://github.com/sigoden/dufs/issues/77))
|
||||||
|
|
||||||
|
### Ci
|
||||||
|
|
||||||
|
- Support more platforms ([#76](https://github.com/sigoden/dufs/issues/76))
|
||||||
|
|
||||||
|
## [0.22.0] - 2022-06-26
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Support hiding folders with --hidden ([#73](https://github.com/sigoden/dufs/issues/73))
|
||||||
|
|
||||||
|
## [0.21.0] - 2022-06-23
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|||||||
84
Cargo.lock
generated
84
Cargo.lock
generated
@@ -23,7 +23,7 @@ version = "0.12.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -148,7 +148,7 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -374,12 +374,6 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "c_linked_list"
|
|
||||||
version = "1.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cache-padded"
|
name = "cache-padded"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -411,7 +405,7 @@ dependencies = [
|
|||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"time",
|
"time",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -572,7 +566,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dufs"
|
name = "dufs"
|
||||||
version = "0.21.0"
|
version = "0.23.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"assert_fs",
|
"assert_fs",
|
||||||
@@ -584,9 +578,9 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"diqwest",
|
"diqwest",
|
||||||
"futures",
|
"futures",
|
||||||
"get_if_addrs",
|
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"if-addrs",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"md5",
|
"md5",
|
||||||
@@ -813,12 +807,6 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gcc"
|
|
||||||
version = "0.3.55"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
@@ -829,28 +817,6 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "get_if_addrs"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "abddb55a898d32925f3148bd281174a68eeb68bbfd9a5938a57b18f506ee4ef7"
|
|
||||||
dependencies = [
|
|
||||||
"c_linked_list",
|
|
||||||
"get_if_addrs-sys",
|
|
||||||
"libc",
|
|
||||||
"winapi 0.2.8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "get_if_addrs-sys"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d04f9fb746cf36b191c00f3ede8bde9c8e64f9f4b05ae2694a9ccf5e3f5ab48"
|
|
||||||
dependencies = [
|
|
||||||
"gcc",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@@ -1083,6 +1049,16 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "if-addrs"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ignore"
|
name = "ignore"
|
||||||
version = "0.4.18"
|
version = "0.4.18"
|
||||||
@@ -1437,7 +1413,7 @@ version = "0.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1560,7 +1536,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wepoll-ffi",
|
"wepoll-ffi",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1760,7 +1736,7 @@ 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 = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1818,7 +1794,7 @@ dependencies = [
|
|||||||
"spin",
|
"spin",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2059,7 +2035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2116,7 +2092,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"remove_dir_all",
|
"remove_dir_all",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2137,7 +2113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2192,7 +2168,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2226,7 +2202,7 @@ dependencies = [
|
|||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2427,7 +2403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"same-file",
|
"same-file",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2563,12 +2539,6 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.2.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -2591,7 +2561,7 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2649,7 +2619,7 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dufs"
|
name = "dufs"
|
||||||
version = "0.21.0"
|
version = "0.23.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"
|
||||||
@@ -14,7 +14,6 @@ keywords = ["static", "file", "server", "webdav", "cli"]
|
|||||||
clap = { version = "3", default-features = false, features = ["std", "wrap_help"] }
|
clap = { version = "3", default-features = false, features = ["std", "wrap_help"] }
|
||||||
chrono = "0.4"
|
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-rustls = "0.23"
|
|
||||||
tokio-util = { version = "0.7", features = ["io-util"] }
|
tokio-util = { version = "0.7", features = ["io-util"] }
|
||||||
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.1"
|
||||||
@@ -26,9 +25,10 @@ async_zip = "0.0.7"
|
|||||||
async-walkdir = "0.2"
|
async-walkdir = "0.2"
|
||||||
headers = "0.3"
|
headers = "0.3"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
get_if_addrs = "0.5"
|
if-addrs = "0.7"
|
||||||
rustls = { version = "0.20", default-features = false, features = ["tls12"] }
|
rustls = { version = "0.20", default-features = false, features = ["tls12"], optional = true }
|
||||||
rustls-pemfile = "1"
|
rustls-pemfile = { version = "1", optional = true }
|
||||||
|
tokio-rustls = { version = "0.23", 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.1", features = ["v4", "fast-rng"] }
|
||||||
@@ -38,6 +38,10 @@ log = "0.4"
|
|||||||
socket2 = "0.4"
|
socket2 = "0.4"
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["tls"]
|
||||||
|
tls = ["rustls", "rustls-pemfile", "tokio-rustls"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2"
|
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 }
|
||||||
|
|||||||
24
Dockerfile
24
Dockerfile
@@ -1,10 +1,18 @@
|
|||||||
FROM rust:1.61 as builder
|
FROM alpine as builder
|
||||||
RUN rustup target add x86_64-unknown-linux-musl
|
ARG REPO VER TARGETPLATFORM
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y musl-tools
|
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
WORKDIR /app
|
TARGET="x86_64-unknown-linux-musl"; \
|
||||||
COPY . .
|
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||||
RUN cargo build --target x86_64-unknown-linux-musl --release
|
TARGET="aarch64-unknown-linux-musl"; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/386" ]; then \
|
||||||
|
TARGET="i686-unknown-linux-musl"; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
|
||||||
|
TARGET="armv7-unknown-linux-musleabihf"; \
|
||||||
|
fi && \
|
||||||
|
wget https://github.com/${REPO}/releases/download/${VER}/dufs-${VER}-${TARGET}.tar.gz && \
|
||||||
|
tar -xf dufs-${VER}-${TARGET}.tar.gz && \
|
||||||
|
mv dufs /bin/
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/dufs /bin/
|
COPY --from=builder /bin/dufs /bin/dufs
|
||||||
ENTRYPOINT ["/bin/dufs"]
|
ENTRYPOINT ["/bin/dufs"]
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Dufs (Old Name: Duf)
|
# Dufs
|
||||||
|
|
||||||
[](https://github.com/sigoden/dufs/actions/workflows/ci.yaml)
|
[](https://github.com/sigoden/dufs/actions/workflows/ci.yaml)
|
||||||
[](https://crates.io/crates/dufs)
|
[](https://crates.io/crates/dufs)
|
||||||
@@ -52,6 +52,7 @@ 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 an path prefix
|
||||||
|
--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]
|
||||||
-A, --allow-all Allow all operations
|
-A, --allow-all Allow all operations
|
||||||
@@ -61,7 +62,7 @@ OPTIONS:
|
|||||||
--allow-symlink Allow symlink to files/folders outside root directory
|
--allow-symlink Allow symlink to files/folders outside root directory
|
||||||
--enable-cors Enable CORS, sets `Access-Control-Allow-Origin: *`
|
--enable-cors Enable CORS, sets `Access-Control-Allow-Origin: *`
|
||||||
--render-index Serve index.html when requesting a directory, returns 404 if not found index.html
|
--render-index Serve index.html when requesting a directory, returns 404 if not found index.html
|
||||||
--render-try-index Serve index.html when requesting a directory, returns file listing if not found index.html
|
--render-try-index Serve index.html when requesting a directory, returns directory listing if not found index.html
|
||||||
--render-spa Serve SPA(Single Page Application)
|
--render-spa Serve SPA(Single Page Application)
|
||||||
--tls-cert <path> Path to an SSL/TLS certificate to serve with HTTPS
|
--tls-cert <path> Path to an SSL/TLS certificate to serve with HTTPS
|
||||||
--tls-key <path> Path to the SSL/TLS certificate's private key
|
--tls-key <path> Path to the SSL/TLS certificate's private key
|
||||||
@@ -125,6 +126,12 @@ Listen on a specific port
|
|||||||
dufs -p 80
|
dufs -p 80
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Hide directories from directory listings
|
||||||
|
|
||||||
|
```
|
||||||
|
dufs --hidden .git,.DS_Store
|
||||||
|
```
|
||||||
|
|
||||||
Use https
|
Use https
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -161,23 +168,23 @@ curl -X DELETE http://127.0.0.1:5000/path-to-file
|
|||||||
Dufs supports path level access control. You can control who can do what on which path with `--auth`/`-a`.
|
Dufs supports path level access control. You can control who can do what on which path with `--auth`/`-a`.
|
||||||
|
|
||||||
```
|
```
|
||||||
dufs -a <path>@<readwrite>[@<readonly>]
|
dufs -a <path>@<readwrite>[@<readonly>|@*]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `<path>`: Path to protected
|
- `<path>`: Protected url path
|
||||||
- `<readwrite>`: Account with readwrite permission, required
|
- `<readwrite>`: Account with upload/delete/view/download permission, required
|
||||||
- `<readonly>`: Account with readonly permission, optional
|
- `<readonly>`: Account with view/download permission, optional
|
||||||
|
|
||||||
> `<readonly>` can be `*` means `<path>` is public, everyone can access/download it.
|
> `*` means `<path>` is public, everyone can view/download it.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
dufs -a /@admin:pass@* -a /ui@designer:pass1 -A
|
dufs -a /@admin:pass@* -a /ui@designer:pass1 -A
|
||||||
```
|
```
|
||||||
- All files/folders are public to access/download.
|
- All files/folders are public to view/download.
|
||||||
- Account `admin:pass` can upload/delete/download any files/folders.
|
- Account `admin:pass` can upload/delete/view/download any files/folders.
|
||||||
- Account `designer:pass1` can upload/delete/download any files/folders in the `ui` folder.
|
- Account `designer:pass1` can upload/delete/view/download any files/folders in the `ui` folder.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
33
src/args.rs
33
src/args.rs
@@ -1,4 +1,5 @@
|
|||||||
use clap::{AppSettings, Arg, ArgMatches, Command};
|
use clap::{AppSettings, Arg, ArgMatches, Command};
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
use rustls::{Certificate, PrivateKey};
|
use rustls::{Certificate, PrivateKey};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
@@ -6,11 +7,12 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use crate::auth::AccessControl;
|
use crate::auth::AccessControl;
|
||||||
use crate::auth::AuthMethod;
|
use crate::auth::AuthMethod;
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
use crate::tls::{load_certs, load_private_key};
|
use crate::tls::{load_certs, load_private_key};
|
||||||
use crate::BoxResult;
|
use crate::BoxResult;
|
||||||
|
|
||||||
fn app() -> Command<'static> {
|
fn app() -> Command<'static> {
|
||||||
Command::new(env!("CARGO_CRATE_NAME"))
|
let app = Command::new(env!("CARGO_CRATE_NAME"))
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.author(env!("CARGO_PKG_AUTHORS"))
|
.author(env!("CARGO_PKG_AUTHORS"))
|
||||||
.about(concat!(
|
.about(concat!(
|
||||||
@@ -48,6 +50,12 @@ fn app() -> Command<'static> {
|
|||||||
.value_name("path")
|
.value_name("path")
|
||||||
.help("Specify an path prefix"),
|
.help("Specify an path prefix"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("hidden")
|
||||||
|
.long("hidden")
|
||||||
|
.help("Hide directories from directory listings, separated by `,`")
|
||||||
|
.value_name("value"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("auth")
|
Arg::new("auth")
|
||||||
.short('a')
|
.short('a')
|
||||||
@@ -104,13 +112,16 @@ fn app() -> Command<'static> {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::new("render-try-index")
|
Arg::new("render-try-index")
|
||||||
.long("render-try-index")
|
.long("render-try-index")
|
||||||
.help("Serve index.html when requesting a directory, returns file listing if not found index.html"),
|
.help("Serve index.html when requesting a directory, returns directory listing if not found index.html"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("render-spa")
|
Arg::new("render-spa")
|
||||||
.long("render-spa")
|
.long("render-spa")
|
||||||
.help("Serve SPA(Single Page Application)"),
|
.help("Serve SPA(Single Page Application)"),
|
||||||
)
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
let app = app
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("tls-cert")
|
Arg::new("tls-cert")
|
||||||
.long("tls-cert")
|
.long("tls-cert")
|
||||||
@@ -122,7 +133,9 @@ fn app() -> Command<'static> {
|
|||||||
.long("tls-key")
|
.long("tls-key")
|
||||||
.value_name("path")
|
.value_name("path")
|
||||||
.help("Path to the SSL/TLS certificate's private key"),
|
.help("Path to the SSL/TLS certificate's private key"),
|
||||||
)
|
);
|
||||||
|
|
||||||
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches() -> ArgMatches {
|
pub fn matches() -> ArgMatches {
|
||||||
@@ -137,6 +150,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 auth_method: AuthMethod,
|
pub auth_method: AuthMethod,
|
||||||
pub auth: AccessControl,
|
pub auth: AccessControl,
|
||||||
pub allow_upload: bool,
|
pub allow_upload: bool,
|
||||||
@@ -147,7 +161,10 @@ pub struct Args {
|
|||||||
pub render_spa: bool,
|
pub render_spa: bool,
|
||||||
pub render_try_index: bool,
|
pub render_try_index: bool,
|
||||||
pub enable_cors: bool,
|
pub enable_cors: bool,
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
pub tls: Option<(Vec<Certificate>, PrivateKey)>,
|
pub tls: Option<(Vec<Certificate>, PrivateKey)>,
|
||||||
|
#[cfg(not(feature = "tls"))]
|
||||||
|
pub tls: Option<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
@@ -173,6 +190,10 @@ impl Args {
|
|||||||
} else {
|
} else {
|
||||||
format!("/{}/", &path_prefix)
|
format!("/{}/", &path_prefix)
|
||||||
};
|
};
|
||||||
|
let hidden: String = matches
|
||||||
|
.value_of("hidden")
|
||||||
|
.map(|v| format!(",{},", v))
|
||||||
|
.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
|
||||||
.values_of("auth")
|
.values_of("auth")
|
||||||
@@ -190,6 +211,7 @@ impl Args {
|
|||||||
let render_index = matches.is_present("render-index");
|
let render_index = matches.is_present("render-index");
|
||||||
let render_try_index = matches.is_present("render-try-index");
|
let render_try_index = matches.is_present("render-try-index");
|
||||||
let render_spa = matches.is_present("render-spa");
|
let render_spa = matches.is_present("render-spa");
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
let tls = match (matches.value_of("tls-cert"), matches.value_of("tls-key")) {
|
let tls = match (matches.value_of("tls-cert"), matches.value_of("tls-key")) {
|
||||||
(Some(certs_file), Some(key_file)) => {
|
(Some(certs_file), Some(key_file)) => {
|
||||||
let certs = load_certs(certs_file)?;
|
let certs = load_certs(certs_file)?;
|
||||||
@@ -198,6 +220,8 @@ impl Args {
|
|||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
#[cfg(not(feature = "tls"))]
|
||||||
|
let tls = None;
|
||||||
|
|
||||||
Ok(Args {
|
Ok(Args {
|
||||||
addrs,
|
addrs,
|
||||||
@@ -206,6 +230,7 @@ impl Args {
|
|||||||
path_is_file,
|
path_is_file,
|
||||||
path_prefix,
|
path_prefix,
|
||||||
uri_prefix,
|
uri_prefix,
|
||||||
|
hidden,
|
||||||
auth_method,
|
auth_method,
|
||||||
auth,
|
auth,
|
||||||
enable_cors,
|
enable_cors,
|
||||||
|
|||||||
14
src/main.rs
14
src/main.rs
@@ -3,6 +3,7 @@ mod auth;
|
|||||||
mod logger;
|
mod logger;
|
||||||
mod server;
|
mod server;
|
||||||
mod streamer;
|
mod streamer;
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
mod tls;
|
mod tls;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ extern crate log;
|
|||||||
|
|
||||||
use crate::args::{matches, Args};
|
use crate::args::{matches, Args};
|
||||||
use crate::server::{Request, Server};
|
use crate::server::{Request, Server};
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
use crate::tls::{TlsAcceptor, TlsStream};
|
use crate::tls::{TlsAcceptor, TlsStream};
|
||||||
|
|
||||||
use std::net::{IpAddr, SocketAddr, TcpListener as StdTcpListener};
|
use std::net::{IpAddr, SocketAddr, TcpListener as StdTcpListener};
|
||||||
@@ -22,6 +24,7 @@ use tokio::task::JoinHandle;
|
|||||||
|
|
||||||
use hyper::server::conn::{AddrIncoming, AddrStream};
|
use hyper::server::conn::{AddrIncoming, AddrStream};
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
use rustls::ServerConfig;
|
use rustls::ServerConfig;
|
||||||
|
|
||||||
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
|
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
@@ -70,12 +73,13 @@ fn serve(args: Arc<Args>) -> BoxResult<Vec<JoinHandle<Result<(), hyper::Error>>>
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match args.tls.clone() {
|
match args.tls.as_ref() {
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
Some((certs, key)) => {
|
Some((certs, key)) => {
|
||||||
let config = ServerConfig::builder()
|
let config = ServerConfig::builder()
|
||||||
.with_safe_defaults()
|
.with_safe_defaults()
|
||||||
.with_no_client_auth()
|
.with_no_client_auth()
|
||||||
.with_single_cert(certs, key)?;
|
.with_single_cert(certs.clone(), key.clone())?;
|
||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
let accepter = TlsAcceptor::new(config.clone(), incoming);
|
let accepter = TlsAcceptor::new(config.clone(), incoming);
|
||||||
let new_service = make_service_fn(move |socket: &TlsStream| {
|
let new_service = make_service_fn(move |socket: &TlsStream| {
|
||||||
@@ -85,6 +89,10 @@ fn serve(args: Arc<Args>) -> BoxResult<Vec<JoinHandle<Result<(), hyper::Error>>>
|
|||||||
let server = tokio::spawn(hyper::Server::builder(accepter).serve(new_service));
|
let server = tokio::spawn(hyper::Server::builder(accepter).serve(new_service));
|
||||||
handles.push(server);
|
handles.push(server);
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "tls"))]
|
||||||
|
Some(_) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
let new_service = make_service_fn(move |socket: &AddrStream| {
|
let new_service = make_service_fn(move |socket: &AddrStream| {
|
||||||
let remote_addr = socket.remote_addr();
|
let remote_addr = socket.remote_addr();
|
||||||
@@ -128,7 +136,7 @@ fn print_listening(args: Arc<Args>) -> BoxResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ipv4 || ipv6 {
|
if ipv4 || ipv6 {
|
||||||
let ifaces = get_if_addrs::get_if_addrs()
|
let ifaces = if_addrs::get_if_addrs()
|
||||||
.map_err(|e| format!("Failed to get local interface addresses: {}", e))?;
|
.map_err(|e| format!("Failed to get local interface addresses: {}", e))?;
|
||||||
for iface in ifaces.into_iter() {
|
for iface in ifaces.into_iter() {
|
||||||
let local_ip = iface.ip();
|
let local_ip = iface.ip();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::streamer::Streamer;
|
use crate::streamer::Streamer;
|
||||||
use crate::utils::{decode_uri, encode_uri};
|
use crate::utils::{decode_uri, encode_uri, get_file_name, try_get_file_name};
|
||||||
use crate::{Args, BoxResult};
|
use crate::{Args, BoxResult};
|
||||||
|
use async_walkdir::{Filtering, WalkDir};
|
||||||
use xml::escape::escape_str_pcdata;
|
use xml::escape::escape_str_pcdata;
|
||||||
|
|
||||||
use async_walkdir::WalkDir;
|
|
||||||
use async_zip::write::{EntryOptions, ZipFileWriter};
|
use async_zip::write::{EntryOptions, ZipFileWriter};
|
||||||
use async_zip::Compression;
|
use async_zip::Compression;
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
@@ -162,7 +162,8 @@ impl Server {
|
|||||||
self.handle_zip_dir(path, head_only, &mut res).await?;
|
self.handle_zip_dir(path, head_only, &mut res).await?;
|
||||||
} else if allow_search && query.starts_with("q=") {
|
} else if allow_search && query.starts_with("q=") {
|
||||||
let q = decode_uri(&query[2..]).unwrap_or_default();
|
let q = decode_uri(&query[2..]).unwrap_or_default();
|
||||||
self.handle_query_dir(path, &q, head_only, &mut res).await?;
|
self.handle_search_dir(path, &q, head_only, &mut res)
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
self.handle_ls_dir(path, true, head_only, &mut res).await?;
|
self.handle_ls_dir(path, true, head_only, &mut res).await?;
|
||||||
}
|
}
|
||||||
@@ -322,28 +323,39 @@ impl Server {
|
|||||||
self.send_index(path, paths, exist, head_only, res)
|
self.send_index(path, paths, exist, head_only, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_query_dir(
|
async fn handle_search_dir(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
query: &str,
|
search: &str,
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> BoxResult<()> {
|
||||||
let mut paths: Vec<PathItem> = vec![];
|
let mut paths: Vec<PathItem> = vec![];
|
||||||
let mut walkdir = WalkDir::new(path);
|
let hidden = self.args.hidden.to_string();
|
||||||
while let Some(entry) = walkdir.next().await {
|
let search = search.to_string();
|
||||||
if let Ok(entry) = entry {
|
let mut walkdir = WalkDir::new(path).filter(move |entry| {
|
||||||
if !entry
|
let hidden_cloned = hidden.clone();
|
||||||
.file_name()
|
let search_cloned = search.clone();
|
||||||
.to_string_lossy()
|
async move {
|
||||||
|
let entry_path = entry.path();
|
||||||
|
let base_name = get_file_name(&entry_path);
|
||||||
|
if is_hidden(&hidden_cloned, base_name) {
|
||||||
|
return Filtering::IgnoreDir;
|
||||||
|
}
|
||||||
|
if !base_name
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
.contains(&query.to_lowercase())
|
.contains(&search_cloned.to_lowercase())
|
||||||
{
|
{
|
||||||
continue;
|
return Filtering::Ignore;
|
||||||
}
|
}
|
||||||
if fs::symlink_metadata(entry.path()).await.is_err() {
|
if fs::symlink_metadata(entry.path()).await.is_err() {
|
||||||
continue;
|
return Filtering::Ignore;
|
||||||
}
|
}
|
||||||
|
Filtering::Continue
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while let Some(entry) = walkdir.next().await {
|
||||||
|
if let Ok(entry) = entry {
|
||||||
if let Ok(Some(item)) = self.to_pathitem(entry.path(), path.to_path_buf()).await {
|
if let Ok(Some(item)) = self.to_pathitem(entry.path(), path.to_path_buf()).await {
|
||||||
paths.push(item);
|
paths.push(item);
|
||||||
}
|
}
|
||||||
@@ -359,7 +371,7 @@ impl Server {
|
|||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> BoxResult<()> {
|
||||||
let (mut writer, reader) = tokio::io::duplex(BUF_SIZE);
|
let (mut writer, reader) = tokio::io::duplex(BUF_SIZE);
|
||||||
let filename = get_file_name(path)?;
|
let filename = try_get_file_name(path)?;
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
CONTENT_DISPOSITION,
|
CONTENT_DISPOSITION,
|
||||||
HeaderValue::from_str(&format!(
|
HeaderValue::from_str(&format!(
|
||||||
@@ -374,8 +386,9 @@ impl Server {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let path = path.to_owned();
|
let path = path.to_owned();
|
||||||
|
let hidden = self.args.hidden.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = zip_dir(&mut writer, &path).await {
|
if let Err(e) = zip_dir(&mut writer, &path, &hidden).await {
|
||||||
error!("Failed to zip {}, {}", path.display(), e);
|
error!("Failed to zip {}, {}", path.display(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -513,7 +526,7 @@ impl Server {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filename = get_file_name(path)?;
|
let filename = try_get_file_name(path)?;
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
CONTENT_DISPOSITION,
|
CONTENT_DISPOSITION,
|
||||||
HeaderValue::from_str(&format!("inline; filename=\"{}\"", encode_uri(filename),))
|
HeaderValue::from_str(&format!("inline; filename=\"{}\"", encode_uri(filename),))
|
||||||
@@ -802,6 +815,10 @@ DATA = {}
|
|||||||
let mut rd = fs::read_dir(entry_path).await?;
|
let mut rd = fs::read_dir(entry_path).await?;
|
||||||
while let Ok(Some(entry)) = rd.next_entry().await {
|
while let Ok(Some(entry)) = rd.next_entry().await {
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
|
let base_name = get_file_name(&entry_path);
|
||||||
|
if is_hidden(&self.args.hidden, base_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let Ok(Some(item)) = self.to_pathitem(entry_path.as_path(), base_path).await {
|
if let Ok(Some(item)) = self.to_pathitem(entry_path.as_path(), base_path).await {
|
||||||
paths.push(item);
|
paths.push(item);
|
||||||
}
|
}
|
||||||
@@ -910,11 +927,8 @@ impl PathItem {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn base_name(&self) -> &str {
|
pub fn base_name(&self) -> &str {
|
||||||
Path::new(&self.name)
|
self.name.split('/').last().unwrap_or_default()
|
||||||
.file_name()
|
|
||||||
.and_then(|v| v.to_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,19 +992,30 @@ fn res_multistatus(res: &mut Response, content: &str) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn zip_dir<W: AsyncWrite + Unpin>(writer: &mut W, dir: &Path) -> BoxResult<()> {
|
async fn zip_dir<W: AsyncWrite + Unpin>(writer: &mut W, dir: &Path, hidden: &str) -> BoxResult<()> {
|
||||||
let mut writer = ZipFileWriter::new(writer);
|
let mut writer = ZipFileWriter::new(writer);
|
||||||
let mut walkdir = WalkDir::new(dir);
|
let hidden = hidden.to_string();
|
||||||
|
let mut walkdir = WalkDir::new(dir).filter(move |entry| {
|
||||||
|
let hidden = hidden.clone();
|
||||||
|
async move {
|
||||||
|
let entry_path = entry.path();
|
||||||
|
let base_name = get_file_name(&entry_path);
|
||||||
|
if is_hidden(&hidden, base_name) {
|
||||||
|
return Filtering::IgnoreDir;
|
||||||
|
}
|
||||||
|
let meta = match fs::symlink_metadata(entry.path()).await {
|
||||||
|
Ok(meta) => meta,
|
||||||
|
Err(_) => return Filtering::Ignore,
|
||||||
|
};
|
||||||
|
if !meta.is_file() {
|
||||||
|
return Filtering::Ignore;
|
||||||
|
}
|
||||||
|
Filtering::Continue
|
||||||
|
}
|
||||||
|
});
|
||||||
while let Some(entry) = walkdir.next().await {
|
while let Some(entry) = walkdir.next().await {
|
||||||
if let Ok(entry) = entry {
|
if let Ok(entry) = entry {
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
let meta = match fs::symlink_metadata(entry.path()).await {
|
|
||||||
Ok(meta) => meta,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
if !meta.is_file() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let filename = match entry_path.strip_prefix(dir).ok().and_then(|v| v.to_str()) {
|
let filename = match entry_path.strip_prefix(dir).ok().and_then(|v| v.to_str()) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => continue,
|
None => continue,
|
||||||
@@ -1061,10 +1086,8 @@ fn status_no_content(res: &mut Response) {
|
|||||||
*res.status_mut() = StatusCode::NO_CONTENT;
|
*res.status_mut() = StatusCode::NO_CONTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_file_name(path: &Path) -> BoxResult<&str> {
|
fn is_hidden(hidden: &str, file_name: &str) -> bool {
|
||||||
path.file_name()
|
hidden.contains(&format!(",{},", file_name))
|
||||||
.and_then(|v| v.to_str())
|
|
||||||
.ok_or_else(|| format!("Failed to get file name of `{}`", path.display()).into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_webdav_headers(res: &mut Response) {
|
fn set_webdav_headers(res: &mut Response) {
|
||||||
|
|||||||
15
src/utils.rs
15
src/utils.rs
@@ -1,4 +1,5 @@
|
|||||||
use std::borrow::Cow;
|
use crate::BoxResult;
|
||||||
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
pub fn encode_uri(v: &str) -> String {
|
pub fn encode_uri(v: &str) -> String {
|
||||||
let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
|
let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
|
||||||
@@ -10,3 +11,15 @@ pub fn decode_uri(v: &str) -> Option<Cow<str>> {
|
|||||||
.decode_utf8()
|
.decode_utf8()
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_file_name(path: &Path) -> &str {
|
||||||
|
path.file_name()
|
||||||
|
.and_then(|v| v.to_str())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_file_name(path: &Path) -> BoxResult<&str> {
|
||||||
|
path.file_name()
|
||||||
|
.and_then(|v| v.to_str())
|
||||||
|
.ok_or_else(|| format!("Failed to get file name of `{}`", path.display()).into())
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ fn allow_search(#[with(&["--allow-search"])] server: TestServer) -> Result<(), E
|
|||||||
let paths = utils::retrive_index_paths(&resp.text()?);
|
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||||
assert!(!paths.is_empty());
|
assert!(!paths.is_empty());
|
||||||
for p in paths {
|
for p in paths {
|
||||||
assert!(p.contains(&"test.html"));
|
assert!(p.contains("test.html"));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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> {
|
||||||
let resp = reqwest::blocking::get(format!("{}{}", server.url(), "xyz"))?;
|
let resp = reqwest::blocking::get(format!("{}{}", server.url(), "xyz"))?;
|
||||||
assert_index_resp!(resp);
|
assert_resp_paths!(resp);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,3 +59,35 @@ fn asset_ico(server: TestServer) -> Result<(), Error> {
|
|||||||
assert_eq!(resp.headers().get("content-type").unwrap(), "image/x-icon");
|
assert_eq!(resp.headers().get("content-type").unwrap(), "image/x-icon");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn assets_with_prefix(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
|
||||||
|
let ver = env!("CARGO_PKG_VERSION");
|
||||||
|
let resp = reqwest::blocking::get(format!("{}xyz/", server.url()))?;
|
||||||
|
let index_js = format!("/xyz/__dufs_v{}_index.js", ver);
|
||||||
|
let index_css = format!("/xyz/__dufs_v{}_index.css", ver);
|
||||||
|
let favicon_ico = format!("/xyz/__dufs_v{}_favicon.ico", ver);
|
||||||
|
let text = resp.text()?;
|
||||||
|
assert!(text.contains(&format!(r#"href="{}""#, index_css)));
|
||||||
|
assert!(text.contains(&format!(r#"href="{}""#, favicon_ico)));
|
||||||
|
assert!(text.contains(&format!(r#"src="{}""#, index_js)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn asset_js_with_prefix(
|
||||||
|
#[with(&["--path-prefix", "xyz"])] server: TestServer,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let url = format!(
|
||||||
|
"{}xyz/__dufs_v{}_index.js",
|
||||||
|
server.url(),
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
);
|
||||||
|
let resp = reqwest::blocking::get(url)?;
|
||||||
|
assert_eq!(resp.status(), 200);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("content-type").unwrap(),
|
||||||
|
"application/javascript"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ pub static DIR_NO_FOUND: &str = "dir-no-found/";
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub static DIR_NO_INDEX: &str = "dir-no-index/";
|
pub static DIR_NO_INDEX: &str = "dir-no-index/";
|
||||||
|
|
||||||
|
/// Directory names for testing hidden
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub static DIR_GIT: &str = ".git/";
|
||||||
|
|
||||||
/// Directory names for testing purpose
|
/// Directory names for testing purpose
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub static DIRECTORIES: &[&str] = &["dira/", "dirb/", "dirc/", DIR_NO_INDEX];
|
pub static DIRECTORIES: &[&str] = &["dira/", "dirb/", "dirc/", DIR_NO_INDEX, DIR_GIT];
|
||||||
|
|
||||||
/// Test fixture which creates a temporary directory with a few files and directories inside.
|
/// Test fixture which creates a temporary directory with a few files and directories inside.
|
||||||
/// The directories also contain files.
|
/// The directories also contain files.
|
||||||
|
|||||||
42
tests/hidden.rs
Normal file
42
tests/hidden.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
mod fixtures;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use fixtures::{server, Error, TestServer};
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(server(&[] as &[&str]), true)]
|
||||||
|
#[case(server(&["--hidden", ".git,index.html"]), false)]
|
||||||
|
fn hidden_get_dir(#[case] server: TestServer, #[case] exist: bool) -> Result<(), Error> {
|
||||||
|
let resp = reqwest::blocking::get(server.url())?;
|
||||||
|
assert_eq!(resp.status(), 200);
|
||||||
|
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||||
|
assert_eq!(paths.contains(".git/"), exist);
|
||||||
|
assert_eq!(paths.contains("index.html"), exist);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(server(&[] as &[&str]), true)]
|
||||||
|
#[case(server(&["--hidden", ".git,index.html"]), false)]
|
||||||
|
fn hidden_propfind_dir(#[case] server: TestServer, #[case] exist: bool) -> Result<(), Error> {
|
||||||
|
let resp = fetch!(b"PROPFIND", server.url()).send()?;
|
||||||
|
assert_eq!(resp.status(), 207);
|
||||||
|
let body = resp.text()?;
|
||||||
|
assert_eq!(body.contains("<D:href>/.git/</D:href>"), exist);
|
||||||
|
assert_eq!(body.contains("<D:href>/index.html</D:href>"), exist);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(server(&["--allow-search"] as &[&str]), true)]
|
||||||
|
#[case(server(&["--allow-search", "--hidden", ".git,test.html"]), false)]
|
||||||
|
fn hidden_search_dir(#[case] server: TestServer, #[case] exist: bool) -> Result<(), Error> {
|
||||||
|
let resp = reqwest::blocking::get(format!("{}?q={}", server.url(), "test.html"))?;
|
||||||
|
assert_eq!(resp.status(), 200);
|
||||||
|
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||||
|
for p in paths {
|
||||||
|
assert_eq!(p.contains("test.html"), exist);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use rstest::rstest;
|
|||||||
#[rstest]
|
#[rstest]
|
||||||
fn get_dir(server: TestServer) -> Result<(), Error> {
|
fn get_dir(server: TestServer) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(server.url())?;
|
let resp = reqwest::blocking::get(server.url())?;
|
||||||
assert_index_resp!(resp);
|
assert_resp_paths!(resp);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ fn get_dir_search(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
|||||||
let paths = utils::retrive_index_paths(&resp.text()?);
|
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||||
assert!(!paths.is_empty());
|
assert!(!paths.is_empty());
|
||||||
for p in paths {
|
for p in paths {
|
||||||
assert!(p.contains(&"test.html"));
|
assert!(p.contains("test.html"));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ fn get_dir_search2(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
|||||||
let paths = utils::retrive_index_paths(&resp.text()?);
|
let paths = utils::retrive_index_paths(&resp.text()?);
|
||||||
assert!(!paths.is_empty());
|
assert!(!paths.is_empty());
|
||||||
for p in paths {
|
for p in paths {
|
||||||
assert!(p.contains(&"😀.bin"));
|
assert!(p.contains("😀.bin"));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
mod fixtures;
|
mod fixtures;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use fixtures::{server, Error, TestServer, DIR_NO_FOUND, DIR_NO_INDEX};
|
use fixtures::{server, Error, TestServer, DIR_NO_FOUND, DIR_NO_INDEX, FILES};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -30,12 +30,12 @@ fn render_try_index(#[with(&["--render-try-index"])] server: TestServer) -> Resu
|
|||||||
#[rstest]
|
#[rstest]
|
||||||
fn render_try_index2(#[with(&["--render-try-index"])] server: TestServer) -> Result<(), Error> {
|
fn render_try_index2(#[with(&["--render-try-index"])] server: TestServer) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(format!("{}{}", server.url(), DIR_NO_INDEX))?;
|
let resp = reqwest::blocking::get(format!("{}{}", server.url(), DIR_NO_INDEX))?;
|
||||||
let files: Vec<&str> = self::fixtures::FILES
|
let files: Vec<&str> = FILES
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|v| **v != "index.html")
|
.filter(|v| **v != "index.html")
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
assert_index_resp!(resp, files);
|
assert_resp_paths!(resp, files);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fn tls_works(#[case] server: TestServer) -> Result<(), Error> {
|
|||||||
.danger_accept_invalid_certs(true)
|
.danger_accept_invalid_certs(true)
|
||||||
.build()?;
|
.build()?;
|
||||||
let resp = client.get(server.url()).send()?.error_for_status()?;
|
let resp = client.get(server.url()).send()?.error_for_status()?;
|
||||||
assert_index_resp!(resp);
|
assert_resp_paths!(resp);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use serde_json::Value;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_index_resp {
|
macro_rules! assert_resp_paths {
|
||||||
($resp:ident) => {
|
($resp:ident) => {
|
||||||
assert_index_resp!($resp, self::fixtures::FILES)
|
assert_resp_paths!($resp, self::fixtures::FILES)
|
||||||
};
|
};
|
||||||
($resp:ident, $files:expr) => {
|
($resp:ident, $files:expr) => {
|
||||||
assert_eq!($resp.status(), 200);
|
assert_eq!($resp.status(), 200);
|
||||||
|
|||||||
Reference in New Issue
Block a user