mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-10 01:20:38 +03:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
870e92e306 | ||
|
|
261c8b6ee5 | ||
|
|
5ce7bde05c | ||
|
|
63a7b530bb | ||
|
|
7481db5071 | ||
|
|
b0cc901416 | ||
|
|
ce154d9ebc | ||
|
|
7c4c264206 | ||
|
|
c1e0c6bb2f | ||
|
|
f138915f20 |
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -145,7 +145,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
push: ${{ needs.release.outputs.rc == 'false' }}
|
push: ${{ needs.release.outputs.rc == 'false' }}
|
||||||
tags: sigoden/duf:latest, sigoden/duf:${{ 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
|
||||||
|
|||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -2,6 +2,34 @@
|
|||||||
|
|
||||||
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.14.0] - 2022-06-07
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Send index page with content-type ([#26](https://github.com/sigoden/duf/issues/26))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Support ipv6 ([#25](https://github.com/sigoden/duf/issues/25))
|
||||||
|
- Add favicon ([#27](https://github.com/sigoden/duf/issues/27))
|
||||||
|
|
||||||
|
## [0.13.2] - 2022-06-06
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Filename xml escaping
|
||||||
|
- Escape path-prefix/url-prefix different
|
||||||
|
|
||||||
|
## [0.13.1] - 2022-06-05
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Escape filename ([#21](https://github.com/sigoden/duf/issues/21))
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Use logger ([#22](https://github.com/sigoden/duf/issues/22))
|
||||||
|
|
||||||
## [0.13.0] - 2022-06-05
|
## [0.13.0] - 2022-06-05
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
90
Cargo.lock
generated
90
Cargo.lock
generated
@@ -286,18 +286,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "duf"
|
name = "duf"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-walkdir",
|
"async-walkdir",
|
||||||
"async_zip",
|
"async_zip",
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"get_if_addrs",
|
"get_if_addrs",
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"log",
|
||||||
"md5",
|
"md5",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@@ -309,7 +311,19 @@ dependencies = [
|
|||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
|
||||||
|
dependencies = [
|
||||||
|
"humantime",
|
||||||
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -329,13 +343,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.23"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
|
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"libc",
|
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -540,9 +552,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.7"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
|
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@@ -572,6 +584,12 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.19"
|
version = "0.14.19"
|
||||||
@@ -661,9 +679,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lzma-sys"
|
name = "lzma-sys"
|
||||||
version = "0.1.17"
|
version = "0.1.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bdb4b7c3eddad11d3af9e86c487607d2d2442d185d848575365c4856ba96d619"
|
checksum = "e06754c4acf47d49c727d5665ca9fb828851cda315ed3bd51edd148ef78a8772"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -700,9 +718,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.1"
|
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 = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
|
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
@@ -971,9 +989,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.95"
|
version = "1.0.96"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
|
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1019,9 +1037,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.18.2"
|
version = "1.19.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
|
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1038,9 +1056,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.7.0"
|
version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1060,9 +1078,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
|
checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -1071,9 +1089,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c"
|
checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -1097,21 +1115,9 @@ checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-attributes"
|
|
||||||
version = "0.1.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.26"
|
version = "0.1.26"
|
||||||
@@ -1154,6 +1160,12 @@ version = "0.7.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -1344,10 +1356,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xz2"
|
name = "xml-rs"
|
||||||
version = "0.1.6"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c179869f34fc7c01830d3ce7ea2086bc3a07e0d35289b667d0a8bf910258926c"
|
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xz2"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lzma-sys",
|
"lzma-sys",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "duf"
|
name = "duf"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["sigoden <sigoden@gmail.com>"]
|
authors = ["sigoden <sigoden@gmail.com>"]
|
||||||
description = "Duf is a simple file server."
|
description = "Duf is a simple file server."
|
||||||
@@ -34,6 +34,10 @@ rustls-pemfile = "1"
|
|||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
uuid = { version = "1.1.1", features = ["v4", "fast-rng"] }
|
uuid = { version = "1.1.1", features = ["v4", "fast-rng"] }
|
||||||
|
urlencoding = "2.1.0"
|
||||||
|
xml-rs = "0.8"
|
||||||
|
env_logger = { version = "0.9.0", default-features = false, features = ["humantime"] }
|
||||||
|
log = "0.4.17"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
BIN
assets/favicon.ico
Executable file
BIN
assets/favicon.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
25
src/args.rs
25
src/args.rs
@@ -1,7 +1,7 @@
|
|||||||
use clap::crate_description;
|
use clap::crate_description;
|
||||||
use clap::{Arg, ArgMatches};
|
use clap::{Arg, ArgMatches};
|
||||||
use rustls::{Certificate, PrivateKey};
|
use rustls::{Certificate, PrivateKey};
|
||||||
use std::net::SocketAddr;
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{env, fs, io};
|
use std::{env, fs, io};
|
||||||
|
|
||||||
@@ -111,8 +111,7 @@ pub fn matches() -> ArgMatches {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub address: String,
|
pub addr: SocketAddr,
|
||||||
pub port: u16,
|
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub path_prefix: String,
|
pub path_prefix: String,
|
||||||
pub uri_prefix: String,
|
pub uri_prefix: String,
|
||||||
@@ -133,8 +132,9 @@ impl Args {
|
|||||||
/// If a parsing error ocurred, exit the process and print out informative
|
/// If a parsing error ocurred, exit the process and print out informative
|
||||||
/// error message to user.
|
/// error message to user.
|
||||||
pub fn parse(matches: ArgMatches) -> BoxResult<Args> {
|
pub fn parse(matches: ArgMatches) -> BoxResult<Args> {
|
||||||
let address = matches.value_of("address").unwrap_or_default().to_owned();
|
let ip = matches.value_of("address").unwrap_or_default();
|
||||||
let port = matches.value_of_t::<u16>("port")?;
|
let port = matches.value_of_t::<u16>("port")?;
|
||||||
|
let addr = to_addr(ip, port)?;
|
||||||
let path = Args::parse_path(matches.value_of_os("path").unwrap_or_default())?;
|
let path = Args::parse_path(matches.value_of_os("path").unwrap_or_default())?;
|
||||||
let path_prefix = matches
|
let path_prefix = matches
|
||||||
.value_of("path-prefix")
|
.value_of("path-prefix")
|
||||||
@@ -166,8 +166,7 @@ impl Args {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Args {
|
Ok(Args {
|
||||||
address,
|
addr,
|
||||||
port,
|
|
||||||
path,
|
path,
|
||||||
path_prefix,
|
path_prefix,
|
||||||
uri_prefix,
|
uri_prefix,
|
||||||
@@ -197,17 +196,15 @@ impl Args {
|
|||||||
})
|
})
|
||||||
.map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into())
|
.map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct socket address from arguments.
|
|
||||||
pub fn address(&self) -> BoxResult<SocketAddr> {
|
|
||||||
format!("{}:{}", self.address, self.port)
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| format!("Invalid bind address `{}:{}`", self.address, self.port).into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_addr(ip: &str, port: u16) -> BoxResult<SocketAddr> {
|
||||||
|
let ip: IpAddr = ip.parse()?;
|
||||||
|
Ok(SocketAddr::new(ip, port))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load public certificate from file.
|
// Load public certificate from file.
|
||||||
pub fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
|
fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
|
||||||
// Open certificate file.
|
// Open certificate file.
|
||||||
let certfile =
|
let certfile =
|
||||||
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
|
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
|
||||||
@@ -222,7 +219,7 @@ pub fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load private key from file.
|
// Load private key from file.
|
||||||
pub fn load_private_key(filename: &str) -> BoxResult<PrivateKey> {
|
fn load_private_key(filename: &str) -> BoxResult<PrivateKey> {
|
||||||
// Open keyfile.
|
// Open keyfile.
|
||||||
let keyfile =
|
let keyfile =
|
||||||
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
|
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
|
||||||
|
|||||||
16
src/main.rs
16
src/main.rs
@@ -2,8 +2,14 @@ mod args;
|
|||||||
mod auth;
|
mod auth;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
|
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use crate::args::{matches, Args};
|
use crate::args::{matches, Args};
|
||||||
use crate::server::serve;
|
use crate::server::serve;
|
||||||
|
|
||||||
@@ -13,6 +19,16 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run() -> BoxResult<()> {
|
async fn run() -> BoxResult<()> {
|
||||||
|
if env::var("RUST_LOG").is_err() {
|
||||||
|
env::set_var("RUST_LOG", "info")
|
||||||
|
}
|
||||||
|
env_logger::builder()
|
||||||
|
.format(|buf, record| {
|
||||||
|
let timestamp = buf.timestamp();
|
||||||
|
writeln!(buf, "[{} {}] {}", timestamp, record.level(), record.args())
|
||||||
|
})
|
||||||
|
.init();
|
||||||
|
|
||||||
let args = Args::parse(matches())?;
|
let args = Args::parse(matches())?;
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
ret = serve(args) => {
|
ret = serve(args) => {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use crate::auth::{generate_www_auth, valid_digest};
|
use crate::auth::{generate_www_auth, valid_digest};
|
||||||
use crate::{Args, BoxResult};
|
use crate::{Args, BoxResult};
|
||||||
|
use xml::escape::escape_str_pcdata;
|
||||||
|
|
||||||
use async_walkdir::WalkDir;
|
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::{Local, TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use get_if_addrs::get_if_addrs;
|
use get_if_addrs::get_if_addrs;
|
||||||
@@ -24,7 +25,7 @@ use rustls::ServerConfig;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::fs::Metadata;
|
use std::fs::Metadata;
|
||||||
use std::net::IpAddr;
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
@@ -43,6 +44,7 @@ type Response = hyper::Response<Body>;
|
|||||||
const INDEX_HTML: &str = include_str!("../assets/index.html");
|
const INDEX_HTML: &str = include_str!("../assets/index.html");
|
||||||
const INDEX_CSS: &str = include_str!("../assets/index.css");
|
const INDEX_CSS: &str = include_str!("../assets/index.css");
|
||||||
const INDEX_JS: &str = include_str!("../assets/index.js");
|
const INDEX_JS: &str = include_str!("../assets/index.js");
|
||||||
|
const FAVICON_ICO: &[u8] = include_bytes!("../assets/favicon.ico");
|
||||||
const INDEX_NAME: &str = "index.html";
|
const INDEX_NAME: &str = "index.html";
|
||||||
const BUF_SIZE: usize = 1024 * 16;
|
const BUF_SIZE: usize = 1024 * 16;
|
||||||
|
|
||||||
@@ -55,7 +57,6 @@ macro_rules! status {
|
|||||||
|
|
||||||
pub async fn serve(args: Args) -> BoxResult<()> {
|
pub async fn serve(args: Args) -> BoxResult<()> {
|
||||||
let args = Arc::new(args);
|
let args = Arc::new(args);
|
||||||
let socket_addr = args.address()?;
|
|
||||||
let inner = Arc::new(InnerService::new(args.clone()));
|
let inner = Arc::new(InnerService::new(args.clone()));
|
||||||
match args.tls.clone() {
|
match args.tls.clone() {
|
||||||
Some((certs, key)) => {
|
Some((certs, key)) => {
|
||||||
@@ -65,7 +66,7 @@ pub async fn serve(args: Args) -> BoxResult<()> {
|
|||||||
.with_single_cert(certs, key)?;
|
.with_single_cert(certs, key)?;
|
||||||
let tls_acceptor = TlsAcceptor::from(Arc::new(config));
|
let tls_acceptor = TlsAcceptor::from(Arc::new(config));
|
||||||
let arc_acceptor = Arc::new(tls_acceptor);
|
let arc_acceptor = Arc::new(tls_acceptor);
|
||||||
let listener = TcpListener::bind(&socket_addr).await?;
|
let listener = TcpListener::bind(&args.addr).await?;
|
||||||
let incoming = tokio_stream::wrappers::TcpListenerStream::new(listener);
|
let incoming = tokio_stream::wrappers::TcpListenerStream::new(listener);
|
||||||
let incoming =
|
let incoming =
|
||||||
hyper::server::accept::from_stream(incoming.filter_map(|socket| async {
|
hyper::server::accept::from_stream(incoming.filter_map(|socket| async {
|
||||||
@@ -86,11 +87,11 @@ pub async fn serve(args: Args) -> BoxResult<()> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
print_listening(args.address.as_str(), args.port, &args.uri_prefix, true);
|
print_listening(&args.addr, &args.uri_prefix, true);
|
||||||
server.await?;
|
server.await?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let server = hyper::Server::try_bind(&socket_addr)?.serve(make_service_fn(move |_| {
|
let server = hyper::Server::try_bind(&args.addr)?.serve(make_service_fn(move |_| {
|
||||||
let inner = inner.clone();
|
let inner = inner.clone();
|
||||||
async move {
|
async move {
|
||||||
Ok::<_, Infallible>(service_fn(move |req| {
|
Ok::<_, Infallible>(service_fn(move |req| {
|
||||||
@@ -99,7 +100,7 @@ pub async fn serve(args: Args) -> BoxResult<()> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
print_listening(args.address.as_str(), args.port, &args.uri_prefix, false);
|
print_listening(&args.addr, &args.uri_prefix, false);
|
||||||
server.await?;
|
server.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,20 +121,16 @@ impl InnerService {
|
|||||||
let uri = req.uri().clone();
|
let uri = req.uri().clone();
|
||||||
let cors = self.args.cors;
|
let cors = self.args.cors;
|
||||||
|
|
||||||
let timestamp = Local::now().format("%d/%b/%Y %H:%M:%S");
|
|
||||||
let mut res = match self.handle(req).await {
|
let mut res = match self.handle(req).await {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
println!(r#"[{}] "{} {}" - {}"#, timestamp, method, uri, res.status());
|
info!(r#""{} {}" - {}"#, method, uri, res.status());
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let mut res = Response::default();
|
let mut res = Response::default();
|
||||||
let status = StatusCode::INTERNAL_SERVER_ERROR;
|
let status = StatusCode::INTERNAL_SERVER_ERROR;
|
||||||
status!(res, status);
|
status!(res, status);
|
||||||
eprintln!(
|
error!(r#""{} {}" - {} {}"#, method, uri, status, err);
|
||||||
r#"[{}] "{} {}" - {} {}"#,
|
|
||||||
timestamp, method, uri, status, err
|
|
||||||
);
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -178,6 +175,12 @@ impl InnerService {
|
|||||||
status!(res, StatusCode::NOT_FOUND);
|
status!(res, StatusCode::NOT_FOUND);
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
if is_miss && path.ends_with("favicon.ico") {
|
||||||
|
*res.body_mut() = Body::from(FAVICON_ICO);
|
||||||
|
res.headers_mut()
|
||||||
|
.insert("content-type", "image/x-icon".parse().unwrap());
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
let headers = req.headers();
|
let headers = req.headers();
|
||||||
|
|
||||||
@@ -363,14 +366,18 @@ impl InnerService {
|
|||||||
let path = path.to_owned();
|
let path = path.to_owned();
|
||||||
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).await {
|
||||||
eprintln!("Failed to zip {}, {}", path.display(), e);
|
error!("Failed to zip {}, {}", path.display(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let stream = ReaderStream::new(reader);
|
let stream = ReaderStream::new(reader);
|
||||||
*res.body_mut() = Body::wrap_stream(stream);
|
*res.body_mut() = Body::wrap_stream(stream);
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
CONTENT_DISPOSITION,
|
CONTENT_DISPOSITION,
|
||||||
HeaderValue::from_str(&format!("attachment; filename=\"{}.zip\"", filename,)).unwrap(),
|
HeaderValue::from_str(&format!(
|
||||||
|
"attachment; filename=\"{}.zip\"",
|
||||||
|
encode_uri(filename),
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -666,7 +673,8 @@ impl InnerService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
*res.body_mut() = output.into();
|
*res.body_mut() = output.into();
|
||||||
|
res.headers_mut()
|
||||||
|
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,7 +829,10 @@ impl PathItem {
|
|||||||
<D:status>HTTP/1.1 200 OK</D:status>
|
<D:status>HTTP/1.1 200 OK</D:status>
|
||||||
</D:propstat>
|
</D:propstat>
|
||||||
</D:response>"#,
|
</D:response>"#,
|
||||||
prefix, self.name, self.base_name, mtime
|
escape_str_pcdata(prefix),
|
||||||
|
escape_str_pcdata(&self.name),
|
||||||
|
escape_str_pcdata(&self.base_name),
|
||||||
|
mtime
|
||||||
),
|
),
|
||||||
PathType::File | PathType::SymlinkFile => format!(
|
PathType::File | PathType::SymlinkFile => format!(
|
||||||
r#"<D:response>
|
r#"<D:response>
|
||||||
@@ -836,9 +847,9 @@ impl PathItem {
|
|||||||
<D:status>HTTP/1.1 200 OK</D:status>
|
<D:status>HTTP/1.1 200 OK</D:status>
|
||||||
</D:propstat>
|
</D:propstat>
|
||||||
</D:response>"#,
|
</D:response>"#,
|
||||||
prefix,
|
escape_str_pcdata(prefix),
|
||||||
self.name,
|
escape_str_pcdata(&self.name),
|
||||||
self.base_name,
|
escape_str_pcdata(&self.base_name),
|
||||||
self.size.unwrap_or_default(),
|
self.size.unwrap_or_default(),
|
||||||
mtime
|
mtime
|
||||||
),
|
),
|
||||||
@@ -970,35 +981,48 @@ fn to_content_range(range: &Range, complete_length: u64) -> Option<ContentRange>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_listening(address: &str, port: u16, prefix: &str, tls: bool) {
|
fn print_listening(addr: &SocketAddr, prefix: &str, tls: bool) {
|
||||||
let prefix = prefix.trim_end_matches('/');
|
let prefix = encode_uri(prefix.trim_end_matches('/'));
|
||||||
let addrs = retrieve_listening_addrs(address);
|
let addrs = retrieve_listening_addrs(addr);
|
||||||
let protocol = if tls { "https" } else { "http" };
|
let protocol = if tls { "https" } else { "http" };
|
||||||
if addrs.len() == 1 {
|
if addrs.len() == 1 {
|
||||||
eprintln!(
|
eprintln!("Listening on {}://{}{}", protocol, addr, prefix);
|
||||||
"Listening on {}://{}:{}{}",
|
|
||||||
protocol, addrs[0], port, prefix
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Listening on:");
|
eprintln!("Listening on:");
|
||||||
for addr in addrs {
|
for addr in addrs {
|
||||||
eprintln!(" {}://{}:{}{}", protocol, addr, port, prefix);
|
eprintln!(" {}://{}{}", protocol, addr, prefix);
|
||||||
}
|
}
|
||||||
eprintln!();
|
eprintln!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn retrieve_listening_addrs(address: &str) -> Vec<String> {
|
fn retrieve_listening_addrs(addr: &SocketAddr) -> Vec<SocketAddr> {
|
||||||
if address == "0.0.0.0" {
|
let ip = addr.ip();
|
||||||
|
let port = addr.port();
|
||||||
|
if ip.is_unspecified() {
|
||||||
if let Ok(interfaces) = get_if_addrs() {
|
if let Ok(interfaces) = get_if_addrs() {
|
||||||
let mut ifaces: Vec<IpAddr> = interfaces
|
let mut ifaces: Vec<IpAddr> = interfaces
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| v.ip())
|
.map(|v| v.ip())
|
||||||
.filter(|v| v.is_ipv4())
|
.filter(|v| {
|
||||||
|
if ip.is_ipv4() {
|
||||||
|
v.is_ipv4()
|
||||||
|
} else {
|
||||||
|
v.is_ipv6()
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
ifaces.sort();
|
ifaces.sort();
|
||||||
return ifaces.into_iter().map(|v| v.to_string()).collect();
|
return ifaces
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| SocketAddr::new(v, port))
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vec![address.to_owned()]
|
vec![addr.to_owned()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_uri(v: &str) -> String {
|
||||||
|
let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
|
||||||
|
parts.join("/")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user