mirror of
https://github.com/sigoden/dufs
synced 2024-10-06 16:00:12 +00:00
chore: use anyhow to handle error
This commit is contained in:
parent
2064d7803a
commit
8f4cbb4826
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -32,6 +32,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assert_cmd"
|
name = "assert_cmd"
|
||||||
version = "2.0.8"
|
version = "2.0.8"
|
||||||
|
@ -413,6 +419,7 @@ name = "dufs"
|
||||||
version = "0.31.0"
|
version = "0.31.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alphanumeric-sort",
|
"alphanumeric-sort",
|
||||||
|
"anyhow",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"assert_fs",
|
"assert_fs",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
|
|
|
@ -40,7 +40,8 @@ async-stream = "0.3"
|
||||||
walkdir = "2.3"
|
walkdir = "2.3"
|
||||||
form_urlencoded = "1.0"
|
form_urlencoded = "1.0"
|
||||||
alphanumeric-sort = "1.4"
|
alphanumeric-sort = "1.4"
|
||||||
content_inspector = "0.2.4"
|
content_inspector = "0.2"
|
||||||
|
anyhow = "1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tls"]
|
default = ["tls"]
|
||||||
|
|
18
src/args.rs
18
src/args.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
use clap::builder::PossibleValuesParser;
|
use clap::builder::PossibleValuesParser;
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||||
use clap_complete::{generate, Generator, Shell};
|
use clap_complete::{generate, Generator, Shell};
|
||||||
|
@ -13,7 +14,6 @@ use crate::log_http::{LogHttp, DEFAULT_LOG_FORMAT};
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use crate::tls::{load_certs, load_private_key};
|
use crate::tls::{load_certs, load_private_key};
|
||||||
use crate::utils::encode_uri;
|
use crate::utils::encode_uri;
|
||||||
use crate::BoxResult;
|
|
||||||
|
|
||||||
pub fn build_cli() -> Command {
|
pub fn build_cli() -> Command {
|
||||||
let app = Command::new(env!("CARGO_CRATE_NAME"))
|
let app = Command::new(env!("CARGO_CRATE_NAME"))
|
||||||
|
@ -257,7 +257,7 @@ impl Args {
|
||||||
///
|
///
|
||||||
/// If a parsing error occurred, exit the process and print out informative
|
/// If a parsing error occurred, 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) -> Result<Args> {
|
||||||
let port = *matches.get_one::<u16>("port").unwrap();
|
let port = *matches.get_one::<u16>("port").unwrap();
|
||||||
let addrs = matches
|
let addrs = matches
|
||||||
.get_many::<String>("bind")
|
.get_many::<String>("bind")
|
||||||
|
@ -346,7 +346,7 @@ impl Args {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_addrs(addrs: &[&str]) -> BoxResult<Vec<BindAddr>> {
|
fn parse_addrs(addrs: &[&str]) -> Result<Vec<BindAddr>> {
|
||||||
let mut bind_addrs = vec![];
|
let mut bind_addrs = vec![];
|
||||||
let mut invalid_addrs = vec![];
|
let mut invalid_addrs = vec![];
|
||||||
for addr in addrs {
|
for addr in addrs {
|
||||||
|
@ -364,15 +364,15 @@ impl Args {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !invalid_addrs.is_empty() {
|
if !invalid_addrs.is_empty() {
|
||||||
return Err(format!("Invalid bind address `{}`", invalid_addrs.join(",")).into());
|
bail!("Invalid bind address `{}`", invalid_addrs.join(","));
|
||||||
}
|
}
|
||||||
Ok(bind_addrs)
|
Ok(bind_addrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> {
|
fn parse_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(format!("Path `{}` doesn't exist", path.display()).into());
|
bail!("Path `{}` doesn't exist", path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
env::current_dir()
|
env::current_dir()
|
||||||
|
@ -380,13 +380,13 @@ impl Args {
|
||||||
p.push(path); // If path is absolute, it replaces the current path.
|
p.push(path); // If path is absolute, it replaces the current path.
|
||||||
std::fs::canonicalize(p)
|
std::fs::canonicalize(p)
|
||||||
})
|
})
|
||||||
.map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into())
|
.map_err(|err| anyhow!("Failed to access path `{}`: {}", path.display(), err,))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_assets_path<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> {
|
fn parse_assets_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
|
||||||
let path = Self::parse_path(path)?;
|
let path = Self::parse_path(path)?;
|
||||||
if !path.join("index.html").exists() {
|
if !path.join("index.html").exists() {
|
||||||
return Err(format!("Path `{}` doesn't contains index.html", path.display()).into());
|
bail!("Path `{}` doesn't contains index.html", path.display());
|
||||||
}
|
}
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
39
src/auth.rs
39
src/auth.rs
|
@ -1,16 +1,13 @@
|
||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use headers::HeaderValue;
|
use headers::HeaderValue;
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use md5::Context;
|
use md5::Context;
|
||||||
use std::{
|
use std::collections::HashMap;
|
||||||
collections::HashMap,
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::utils::encode_uri;
|
use crate::utils::{encode_uri, unix_now};
|
||||||
use crate::BoxResult;
|
|
||||||
|
|
||||||
const REALM: &str = "DUFS";
|
const REALM: &str = "DUFS";
|
||||||
const DIGEST_AUTH_TIMEOUT: u32 = 86400;
|
const DIGEST_AUTH_TIMEOUT: u32 = 86400;
|
||||||
|
@ -37,14 +34,14 @@ pub struct PathControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccessControl {
|
impl AccessControl {
|
||||||
pub fn new(raw_rules: &[&str], uri_prefix: &str) -> BoxResult<Self> {
|
pub fn new(raw_rules: &[&str], uri_prefix: &str) -> Result<Self> {
|
||||||
let mut rules = HashMap::default();
|
let mut rules = HashMap::default();
|
||||||
if raw_rules.is_empty() {
|
if raw_rules.is_empty() {
|
||||||
return Ok(Self { rules });
|
return Ok(Self { rules });
|
||||||
}
|
}
|
||||||
for rule in raw_rules {
|
for rule in raw_rules {
|
||||||
let parts: Vec<&str> = rule.split('@').collect();
|
let parts: Vec<&str> = rule.split('@').collect();
|
||||||
let create_err = || format!("Invalid auth `{rule}`").into();
|
let create_err = || anyhow!("Invalid auth `{rule}`");
|
||||||
match parts.as_slice() {
|
match parts.as_slice() {
|
||||||
[path, readwrite] => {
|
[path, readwrite] => {
|
||||||
let control = PathControl {
|
let control = PathControl {
|
||||||
|
@ -197,19 +194,17 @@ pub enum AuthMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthMethod {
|
impl AuthMethod {
|
||||||
pub fn www_auth(&self, stale: bool) -> String {
|
pub fn www_auth(&self, stale: bool) -> Result<String> {
|
||||||
match self {
|
match self {
|
||||||
AuthMethod::Basic => {
|
AuthMethod::Basic => Ok(format!("Basic realm=\"{REALM}\"")),
|
||||||
format!("Basic realm=\"{REALM}\"")
|
|
||||||
}
|
|
||||||
AuthMethod::Digest => {
|
AuthMethod::Digest => {
|
||||||
let str_stale = if stale { "stale=true," } else { "" };
|
let str_stale = if stale { "stale=true," } else { "" };
|
||||||
format!(
|
Ok(format!(
|
||||||
"Digest realm=\"{}\",nonce=\"{}\",{}qop=\"auth\"",
|
"Digest realm=\"{}\",nonce=\"{}\",{}qop=\"auth\"",
|
||||||
REALM,
|
REALM,
|
||||||
create_nonce(),
|
create_nonce()?,
|
||||||
str_stale
|
str_stale
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,16 +329,16 @@ impl AuthMethod {
|
||||||
|
|
||||||
/// Check if a nonce is still valid.
|
/// Check if a nonce is still valid.
|
||||||
/// Return an error if it was never valid
|
/// Return an error if it was never valid
|
||||||
fn validate_nonce(nonce: &[u8]) -> Result<bool, ()> {
|
fn validate_nonce(nonce: &[u8]) -> Result<bool> {
|
||||||
if nonce.len() != 34 {
|
if nonce.len() != 34 {
|
||||||
return Err(());
|
bail!("invalid nonce");
|
||||||
}
|
}
|
||||||
//parse hex
|
//parse hex
|
||||||
if let Ok(n) = std::str::from_utf8(nonce) {
|
if let Ok(n) = std::str::from_utf8(nonce) {
|
||||||
//get time
|
//get time
|
||||||
if let Ok(secs_nonce) = u32::from_str_radix(&n[..8], 16) {
|
if let Ok(secs_nonce) = u32::from_str_radix(&n[..8], 16) {
|
||||||
//check time
|
//check time
|
||||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
let now = unix_now()?;
|
||||||
let secs_now = now.as_secs() as u32;
|
let secs_now = now.as_secs() as u32;
|
||||||
|
|
||||||
if let Some(dur) = secs_now.checked_sub(secs_nonce) {
|
if let Some(dur) = secs_now.checked_sub(secs_nonce) {
|
||||||
|
@ -357,7 +352,7 @@ fn validate_nonce(nonce: &[u8]) -> Result<bool, ()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(())
|
bail!("invalid nonce");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_prefix<'a>(search: &'a [u8], prefix: &[u8]) -> Option<&'a [u8]> {
|
fn strip_prefix<'a>(search: &'a [u8], prefix: &[u8]) -> Option<&'a [u8]> {
|
||||||
|
@ -413,12 +408,12 @@ fn to_headermap(header: &[u8]) -> Result<HashMap<&[u8], &[u8]>, ()> {
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_nonce() -> String {
|
fn create_nonce() -> Result<String> {
|
||||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
let now = unix_now()?;
|
||||||
let secs = now.as_secs() as u32;
|
let secs = now.as_secs() as u32;
|
||||||
let mut h = NONCESTARTHASH.clone();
|
let mut h = NONCESTARTHASH.clone();
|
||||||
h.consume(secs.to_be_bytes());
|
h.consume(secs.to_be_bytes());
|
||||||
|
|
||||||
let n = format!("{:08x}{:032x}", secs, h.compute());
|
let n = format!("{:08x}{:032x}", secs, h.compute());
|
||||||
n[..34].to_string()
|
Ok(n[..34].to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl LogHttp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for LogHttp {
|
impl FromStr for LogHttp {
|
||||||
type Err = Box<dyn std::error::Error>;
|
type Err = anyhow::Error;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut elements = vec![];
|
let mut elements = vec![];
|
||||||
let mut is_var = false;
|
let mut is_var = false;
|
||||||
|
|
31
src/main.rs
31
src/main.rs
|
@ -18,6 +18,7 @@ use crate::server::{Request, Server};
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use crate::tls::{TlsAcceptor, TlsStream};
|
use crate::tls::{TlsAcceptor, TlsStream};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use std::net::{IpAddr, SocketAddr, TcpListener as StdTcpListener};
|
use std::net::{IpAddr, SocketAddr, TcpListener as StdTcpListener};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -33,15 +34,16 @@ use hyper::service::{make_service_fn, service_fn};
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use rustls::ServerConfig;
|
use rustls::ServerConfig;
|
||||||
|
|
||||||
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
run().await.unwrap_or_else(handle_err)
|
run().await.unwrap_or_else(|err| {
|
||||||
|
eprintln!("error: {err}");
|
||||||
|
std::process::exit(1);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run() -> BoxResult<()> {
|
async fn run() -> Result<()> {
|
||||||
logger::init().map_err(|e| format!("Failed to init logger, {e}"))?;
|
logger::init().map_err(|e| anyhow!("Failed to init logger, {e}"))?;
|
||||||
let cmd = build_cli();
|
let cmd = build_cli();
|
||||||
let matches = cmd.get_matches();
|
let matches = cmd.get_matches();
|
||||||
if let Some(generator) = matches.get_one::<Shell>("completions") {
|
if let Some(generator) = matches.get_one::<Shell>("completions") {
|
||||||
|
@ -74,8 +76,8 @@ async fn run() -> BoxResult<()> {
|
||||||
fn serve(
|
fn serve(
|
||||||
args: Arc<Args>,
|
args: Arc<Args>,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
) -> BoxResult<Vec<JoinHandle<Result<(), hyper::Error>>>> {
|
) -> Result<Vec<JoinHandle<Result<(), hyper::Error>>>> {
|
||||||
let inner = Arc::new(Server::new(args.clone(), running));
|
let inner = Arc::new(Server::init(args.clone(), running)?);
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
let port = args.port;
|
let port = args.port;
|
||||||
for bind_addr in args.addrs.iter() {
|
for bind_addr in args.addrs.iter() {
|
||||||
|
@ -92,7 +94,7 @@ fn serve(
|
||||||
match bind_addr {
|
match bind_addr {
|
||||||
BindAddr::Address(ip) => {
|
BindAddr::Address(ip) => {
|
||||||
let incoming = create_addr_incoming(SocketAddr::new(*ip, port))
|
let incoming = create_addr_incoming(SocketAddr::new(*ip, port))
|
||||||
.map_err(|e| format!("Failed to bind `{ip}:{port}`, {e}"))?;
|
.map_err(|e| anyhow!("Failed to bind `{ip}:{port}`, {e}"))?;
|
||||||
match args.tls.as_ref() {
|
match args.tls.as_ref() {
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
Some((certs, key)) => {
|
Some((certs, key)) => {
|
||||||
|
@ -132,7 +134,7 @@ fn serve(
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let listener = tokio::net::UnixListener::bind(path)
|
let listener = tokio::net::UnixListener::bind(path)
|
||||||
.map_err(|e| format!("Failed to bind `{}`, {}", path.display(), e))?;
|
.map_err(|e| anyhow!("Failed to bind `{}`, {e}", path.display()))?;
|
||||||
let acceptor = unix::UnixAcceptor::from_listener(listener);
|
let acceptor = unix::UnixAcceptor::from_listener(listener);
|
||||||
let new_service = make_service_fn(move |_| serve_func(None));
|
let new_service = make_service_fn(move |_| serve_func(None));
|
||||||
let server = tokio::spawn(hyper::Server::builder(acceptor).serve(new_service));
|
let server = tokio::spawn(hyper::Server::builder(acceptor).serve(new_service));
|
||||||
|
@ -144,7 +146,7 @@ fn serve(
|
||||||
Ok(handles)
|
Ok(handles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_addr_incoming(addr: SocketAddr) -> BoxResult<AddrIncoming> {
|
fn create_addr_incoming(addr: SocketAddr) -> Result<AddrIncoming> {
|
||||||
use socket2::{Domain, Protocol, Socket, Type};
|
use socket2::{Domain, Protocol, Socket, Type};
|
||||||
let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?;
|
let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?;
|
||||||
if addr.is_ipv6() {
|
if addr.is_ipv6() {
|
||||||
|
@ -159,7 +161,7 @@ fn create_addr_incoming(addr: SocketAddr) -> BoxResult<AddrIncoming> {
|
||||||
Ok(incoming)
|
Ok(incoming)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_listening(args: Arc<Args>) -> BoxResult<()> {
|
fn print_listening(args: Arc<Args>) -> Result<()> {
|
||||||
let mut bind_addrs = vec![];
|
let mut bind_addrs = vec![];
|
||||||
let (mut ipv4, mut ipv6) = (false, false);
|
let (mut ipv4, mut ipv6) = (false, false);
|
||||||
for bind_addr in args.addrs.iter() {
|
for bind_addr in args.addrs.iter() {
|
||||||
|
@ -180,7 +182,7 @@ fn print_listening(args: Arc<Args>) -> BoxResult<()> {
|
||||||
}
|
}
|
||||||
if ipv4 || ipv6 {
|
if ipv4 || ipv6 {
|
||||||
let ifaces = 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| anyhow!("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();
|
||||||
if ipv4 && local_ip.is_ipv4() {
|
if ipv4 && local_ip.is_ipv4() {
|
||||||
|
@ -221,11 +223,6 @@ fn print_listening(args: Arc<Args>) -> BoxResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_err<T>(err: Box<dyn std::error::Error>) -> T {
|
|
||||||
eprintln!("error: {err}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn shutdown_signal() {
|
async fn shutdown_signal() {
|
||||||
tokio::signal::ctrl_c()
|
tokio::signal::ctrl_c()
|
||||||
.await
|
.await
|
||||||
|
|
133
src/server.rs
133
src/server.rs
|
@ -1,12 +1,13 @@
|
||||||
use crate::streamer::Streamer;
|
use crate::streamer::Streamer;
|
||||||
use crate::utils::{decode_uri, encode_uri, get_file_name, glob, try_get_file_name};
|
use crate::utils::{decode_uri, encode_uri, get_file_name, glob, try_get_file_name};
|
||||||
use crate::{Args, BoxResult};
|
use crate::Args;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use xml::escape::escape_str_pcdata;
|
use xml::escape::escape_str_pcdata;
|
||||||
|
|
||||||
use async_zip::write::ZipFileWriter;
|
use async_zip::write::ZipFileWriter;
|
||||||
use async_zip::{Compression, ZipEntryBuilder};
|
use async_zip::{Compression, ZipEntryBuilder};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{LocalResult, TimeZone, Utc};
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use headers::{
|
use headers::{
|
||||||
AcceptRanges, AccessControlAllowCredentials, AccessControlAllowOrigin, Connection,
|
AcceptRanges, AccessControlAllowCredentials, AccessControlAllowOrigin, Connection,
|
||||||
|
@ -54,7 +55,7 @@ pub struct Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(args: Arc<Args>, running: Arc<AtomicBool>) -> Self {
|
pub fn init(args: Arc<Args>, running: Arc<AtomicBool>) -> Result<Self> {
|
||||||
let assets_prefix = format!("{}__dufs_v{}_", args.uri_prefix, env!("CARGO_PKG_VERSION"));
|
let assets_prefix = format!("{}__dufs_v{}_", args.uri_prefix, env!("CARGO_PKG_VERSION"));
|
||||||
let single_file_req_paths = if args.path_is_file {
|
let single_file_req_paths = if args.path_is_file {
|
||||||
vec![
|
vec![
|
||||||
|
@ -70,16 +71,16 @@ impl Server {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
let html = match args.assets_path.as_ref() {
|
let html = match args.assets_path.as_ref() {
|
||||||
Some(path) => Cow::Owned(std::fs::read_to_string(path.join("index.html")).unwrap()),
|
Some(path) => Cow::Owned(std::fs::read_to_string(path.join("index.html"))?),
|
||||||
None => Cow::Borrowed(INDEX_HTML),
|
None => Cow::Borrowed(INDEX_HTML),
|
||||||
};
|
};
|
||||||
Self {
|
Ok(Self {
|
||||||
args,
|
args,
|
||||||
running,
|
running,
|
||||||
single_file_req_paths,
|
single_file_req_paths,
|
||||||
assets_prefix,
|
assets_prefix,
|
||||||
html,
|
html,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn call(
|
pub async fn call(
|
||||||
|
@ -121,7 +122,7 @@ impl Server {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle(self: Arc<Self>, req: Request) -> BoxResult<Response> {
|
pub async fn handle(self: Arc<Self>, req: Request) -> Result<Response> {
|
||||||
let mut res = Response::default();
|
let mut res = Response::default();
|
||||||
|
|
||||||
let req_path = req.uri().path();
|
let req_path = req.uri().path();
|
||||||
|
@ -140,7 +141,7 @@ impl Server {
|
||||||
self.args.auth_method.clone(),
|
self.args.auth_method.clone(),
|
||||||
);
|
);
|
||||||
if guard_type.is_reject() {
|
if guard_type.is_reject() {
|
||||||
self.auth_reject(&mut res);
|
self.auth_reject(&mut res)?;
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,12 +357,7 @@ impl Server {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_upload(
|
async fn handle_upload(&self, path: &Path, mut req: Request, res: &mut Response) -> Result<()> {
|
||||||
&self,
|
|
||||||
path: &Path,
|
|
||||||
mut req: Request,
|
|
||||||
res: &mut Response,
|
|
||||||
) -> BoxResult<()> {
|
|
||||||
ensure_path_parent(path).await?;
|
ensure_path_parent(path).await?;
|
||||||
|
|
||||||
let mut file = match fs::File::create(&path).await {
|
let mut file = match fs::File::create(&path).await {
|
||||||
|
@ -386,7 +382,7 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_delete(&self, path: &Path, is_dir: bool, res: &mut Response) -> BoxResult<()> {
|
async fn handle_delete(&self, path: &Path, is_dir: bool, res: &mut Response) -> Result<()> {
|
||||||
match is_dir {
|
match is_dir {
|
||||||
true => fs::remove_dir_all(path).await?,
|
true => fs::remove_dir_all(path).await?,
|
||||||
false => fs::remove_file(path).await?,
|
false => fs::remove_file(path).await?,
|
||||||
|
@ -404,7 +400,7 @@ impl Server {
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
if exist {
|
if exist {
|
||||||
paths = match self.list_dir(path, path).await {
|
paths = match self.list_dir(path, path).await {
|
||||||
|
@ -425,9 +421,12 @@ impl Server {
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
let mut paths: Vec<PathItem> = vec![];
|
let mut paths: Vec<PathItem> = vec![];
|
||||||
let search = query_params.get("q").unwrap().to_lowercase();
|
let search = query_params
|
||||||
|
.get("q")
|
||||||
|
.ok_or_else(|| anyhow!("invalid q"))?
|
||||||
|
.to_lowercase();
|
||||||
if !search.is_empty() {
|
if !search.is_empty() {
|
||||||
let path_buf = path.to_path_buf();
|
let path_buf = path.to_path_buf();
|
||||||
let hidden = Arc::new(self.args.hidden.to_vec());
|
let hidden = Arc::new(self.args.hidden.to_vec());
|
||||||
|
@ -477,12 +476,7 @@ impl Server {
|
||||||
self.send_index(path, paths, true, query_params, head_only, user, res)
|
self.send_index(path, paths, true, query_params, head_only, user, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_zip_dir(
|
async fn handle_zip_dir(&self, path: &Path, head_only: bool, res: &mut Response) -> Result<()> {
|
||||||
&self,
|
|
||||||
path: &Path,
|
|
||||||
head_only: bool,
|
|
||||||
res: &mut Response,
|
|
||||||
) -> BoxResult<()> {
|
|
||||||
let (mut writer, reader) = tokio::io::duplex(BUF_SIZE);
|
let (mut writer, reader) = tokio::io::duplex(BUF_SIZE);
|
||||||
let filename = try_get_file_name(path)?;
|
let filename = try_get_file_name(path)?;
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
|
@ -490,8 +484,7 @@ impl Server {
|
||||||
HeaderValue::from_str(&format!(
|
HeaderValue::from_str(&format!(
|
||||||
"attachment; filename=\"{}.zip\"",
|
"attachment; filename=\"{}.zip\"",
|
||||||
encode_uri(filename),
|
encode_uri(filename),
|
||||||
))
|
))?,
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert("content-type", HeaderValue::from_static("application/zip"));
|
.insert("content-type", HeaderValue::from_static("application/zip"));
|
||||||
|
@ -519,7 +512,7 @@ impl Server {
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
let index_path = path.join(INDEX_NAME);
|
let index_path = path.join(INDEX_NAME);
|
||||||
if fs::metadata(&index_path)
|
if fs::metadata(&index_path)
|
||||||
.await
|
.await
|
||||||
|
@ -544,7 +537,7 @@ impl Server {
|
||||||
headers: &HeaderMap<HeaderValue>,
|
headers: &HeaderMap<HeaderValue>,
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
if path.extension().is_none() {
|
if path.extension().is_none() {
|
||||||
let path = self.args.path.join(INDEX_NAME);
|
let path = self.args.path.join(INDEX_NAME);
|
||||||
self.handle_send_file(&path, headers, head_only, res)
|
self.handle_send_file(&path, headers, head_only, res)
|
||||||
|
@ -560,7 +553,7 @@ impl Server {
|
||||||
req_path: &str,
|
req_path: &str,
|
||||||
headers: &HeaderMap<HeaderValue>,
|
headers: &HeaderMap<HeaderValue>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<bool> {
|
) -> Result<bool> {
|
||||||
if let Some(name) = req_path.strip_prefix(&self.assets_prefix) {
|
if let Some(name) = req_path.strip_prefix(&self.assets_prefix) {
|
||||||
match self.args.assets_path.as_ref() {
|
match self.args.assets_path.as_ref() {
|
||||||
Some(assets_path) => {
|
Some(assets_path) => {
|
||||||
|
@ -606,7 +599,7 @@ impl Server {
|
||||||
headers: &HeaderMap<HeaderValue>,
|
headers: &HeaderMap<HeaderValue>,
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),);
|
let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),);
|
||||||
let (mut file, meta) = (file?, meta?);
|
let (mut file, meta) = (file?, meta?);
|
||||||
let mut use_range = true;
|
let mut use_range = true;
|
||||||
|
@ -657,8 +650,7 @@ impl Server {
|
||||||
let filename = try_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),))?,
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
res.headers_mut().typed_insert(AcceptRanges::bytes());
|
res.headers_mut().typed_insert(AcceptRanges::bytes());
|
||||||
|
@ -677,9 +669,9 @@ impl Server {
|
||||||
*res.status_mut() = StatusCode::PARTIAL_CONTENT;
|
*res.status_mut() = StatusCode::PARTIAL_CONTENT;
|
||||||
let content_range = format!("bytes {}-{}/{}", range.start, end, size);
|
let content_range = format!("bytes {}-{}/{}", range.start, end, size);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CONTENT_RANGE, content_range.parse().unwrap());
|
.insert(CONTENT_RANGE, content_range.parse()?);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CONTENT_LENGTH, format!("{part_size}").parse().unwrap());
|
.insert(CONTENT_LENGTH, format!("{part_size}").parse()?);
|
||||||
if head_only {
|
if head_only {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -687,11 +679,11 @@ impl Server {
|
||||||
} else {
|
} else {
|
||||||
*res.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
|
*res.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CONTENT_RANGE, format!("bytes */{size}").parse().unwrap());
|
.insert(CONTENT_RANGE, format!("bytes */{size}").parse()?);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CONTENT_LENGTH, format!("{size}").parse().unwrap());
|
.insert(CONTENT_LENGTH, format!("{size}").parse()?);
|
||||||
if head_only {
|
if head_only {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -707,7 +699,7 @@ impl Server {
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),);
|
let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),);
|
||||||
let (file, meta) = (file?, meta?);
|
let (file, meta) = (file?, meta?);
|
||||||
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
|
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
|
||||||
|
@ -729,7 +721,7 @@ impl Server {
|
||||||
let output = self
|
let output = self
|
||||||
.html
|
.html
|
||||||
.replace("__ASSERTS_PREFIX__", &self.assets_prefix)
|
.replace("__ASSERTS_PREFIX__", &self.assets_prefix)
|
||||||
.replace("__INDEX_DATA__", &serde_json::to_string(&data).unwrap());
|
.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));
|
||||||
if head_only {
|
if head_only {
|
||||||
|
@ -744,7 +736,7 @@ impl Server {
|
||||||
path: &Path,
|
path: &Path,
|
||||||
headers: &HeaderMap<HeaderValue>,
|
headers: &HeaderMap<HeaderValue>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
let depth: u32 = match headers.get("depth") {
|
let depth: u32 = match headers.get("depth") {
|
||||||
Some(v) => match v.to_str().ok().and_then(|v| v.parse().ok()) {
|
Some(v) => match v.to_str().ok().and_then(|v| v.parse().ok()) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
|
@ -755,7 +747,10 @@ impl Server {
|
||||||
},
|
},
|
||||||
None => 1,
|
None => 1,
|
||||||
};
|
};
|
||||||
let mut paths = vec![self.to_pathitem(path, &self.args.path).await?.unwrap()];
|
let mut paths = match self.to_pathitem(path, &self.args.path).await? {
|
||||||
|
Some(v) => vec![v],
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
if depth != 0 {
|
if depth != 0 {
|
||||||
match self.list_dir(path, &self.args.path).await {
|
match self.list_dir(path, &self.args.path).await {
|
||||||
Ok(child) => paths.extend(child),
|
Ok(child) => paths.extend(child),
|
||||||
|
@ -776,7 +771,7 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_propfind_file(&self, path: &Path, res: &mut Response) -> BoxResult<()> {
|
async fn handle_propfind_file(&self, path: &Path, res: &mut Response) -> Result<()> {
|
||||||
if let Some(pathitem) = self.to_pathitem(path, &self.args.path).await? {
|
if let Some(pathitem) = self.to_pathitem(path, &self.args.path).await? {
|
||||||
res_multistatus(res, &pathitem.to_dav_xml(self.args.uri_prefix.as_str()));
|
res_multistatus(res, &pathitem.to_dav_xml(self.args.uri_prefix.as_str()));
|
||||||
} else {
|
} else {
|
||||||
|
@ -785,13 +780,13 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_mkcol(&self, path: &Path, res: &mut Response) -> BoxResult<()> {
|
async fn handle_mkcol(&self, path: &Path, res: &mut Response) -> Result<()> {
|
||||||
fs::create_dir_all(path).await?;
|
fs::create_dir_all(path).await?;
|
||||||
*res.status_mut() = StatusCode::CREATED;
|
*res.status_mut() = StatusCode::CREATED;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_copy(&self, path: &Path, req: &Request, res: &mut Response) -> BoxResult<()> {
|
async fn handle_copy(&self, path: &Path, req: &Request, res: &mut Response) -> Result<()> {
|
||||||
let dest = match self.extract_dest(req, res) {
|
let dest = match self.extract_dest(req, res) {
|
||||||
Some(dest) => dest,
|
Some(dest) => dest,
|
||||||
None => {
|
None => {
|
||||||
|
@ -813,7 +808,7 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_move(&self, path: &Path, req: &Request, res: &mut Response) -> BoxResult<()> {
|
async fn handle_move(&self, path: &Path, req: &Request, res: &mut Response) -> Result<()> {
|
||||||
let dest = match self.extract_dest(req, res) {
|
let dest = match self.extract_dest(req, res) {
|
||||||
Some(dest) => dest,
|
Some(dest) => dest,
|
||||||
None => {
|
None => {
|
||||||
|
@ -829,7 +824,7 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_lock(&self, req_path: &str, auth: bool, res: &mut Response) -> BoxResult<()> {
|
async fn handle_lock(&self, req_path: &str, auth: bool, res: &mut Response) -> Result<()> {
|
||||||
let token = if auth {
|
let token = if auth {
|
||||||
format!("opaquelocktoken:{}", Uuid::new_v4())
|
format!("opaquelocktoken:{}", Uuid::new_v4())
|
||||||
} else {
|
} else {
|
||||||
|
@ -841,7 +836,7 @@ impl Server {
|
||||||
HeaderValue::from_static("application/xml; charset=utf-8"),
|
HeaderValue::from_static("application/xml; charset=utf-8"),
|
||||||
);
|
);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert("lock-token", format!("<{token}>").parse().unwrap());
|
.insert("lock-token", format!("<{token}>").parse()?);
|
||||||
|
|
||||||
*res.body_mut() = Body::from(format!(
|
*res.body_mut() = Body::from(format!(
|
||||||
r#"<?xml version="1.0" encoding="utf-8"?>
|
r#"<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
@ -853,7 +848,7 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_proppatch(&self, req_path: &str, res: &mut Response) -> BoxResult<()> {
|
async fn handle_proppatch(&self, req_path: &str, res: &mut Response) -> Result<()> {
|
||||||
let output = format!(
|
let output = format!(
|
||||||
r#"<D:response>
|
r#"<D:response>
|
||||||
<D:href>{req_path}</D:href>
|
<D:href>{req_path}</D:href>
|
||||||
|
@ -878,7 +873,7 @@ impl Server {
|
||||||
head_only: bool,
|
head_only: bool,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
res: &mut Response,
|
res: &mut Response,
|
||||||
) -> BoxResult<()> {
|
) -> 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| {
|
||||||
|
@ -938,13 +933,13 @@ impl Server {
|
||||||
let output = if query_params.contains_key("json") {
|
let output = if query_params.contains_key("json") {
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.typed_insert(ContentType::from(mime_guess::mime::APPLICATION_JSON));
|
.typed_insert(ContentType::from(mime_guess::mime::APPLICATION_JSON));
|
||||||
serde_json::to_string_pretty(&data).unwrap()
|
serde_json::to_string_pretty(&data)?
|
||||||
} else {
|
} else {
|
||||||
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("__ASSERTS_PREFIX__", &self.assets_prefix)
|
||||||
.replace("__INDEX_DATA__", &serde_json::to_string(&data).unwrap())
|
.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));
|
||||||
|
@ -955,13 +950,13 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_reject(&self, res: &mut Response) {
|
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(false)?;
|
||||||
set_webdav_headers(res);
|
set_webdav_headers(res);
|
||||||
res.headers_mut().typed_insert(Connection::close());
|
res.headers_mut().typed_insert(Connection::close());
|
||||||
res.headers_mut()
|
res.headers_mut().insert(WWW_AUTHENTICATE, value.parse()?);
|
||||||
.insert(WWW_AUTHENTICATE, value.parse().unwrap());
|
|
||||||
*res.status_mut() = StatusCode::UNAUTHORIZED;
|
*res.status_mut() = StatusCode::UNAUTHORIZED;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_root_contained(&self, path: &Path) -> bool {
|
async fn is_root_contained(&self, path: &Path) -> bool {
|
||||||
|
@ -1038,7 +1033,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_dir(&self, entry_path: &Path, base_path: &Path) -> BoxResult<Vec<PathItem>> {
|
async fn list_dir(&self, entry_path: &Path, base_path: &Path) -> Result<Vec<PathItem>> {
|
||||||
let mut paths: Vec<PathItem> = vec![];
|
let mut paths: Vec<PathItem> = vec![];
|
||||||
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 {
|
||||||
|
@ -1054,11 +1049,7 @@ impl Server {
|
||||||
Ok(paths)
|
Ok(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn to_pathitem<P: AsRef<Path>>(
|
async fn to_pathitem<P: AsRef<Path>>(&self, path: P, base_path: P) -> Result<Option<PathItem>> {
|
||||||
&self,
|
|
||||||
path: P,
|
|
||||||
base_path: P,
|
|
||||||
) -> BoxResult<Option<PathItem>> {
|
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let (meta, meta2) = tokio::join!(fs::metadata(&path), fs::symlink_metadata(&path));
|
let (meta, meta2) = tokio::join!(fs::metadata(&path), fs::symlink_metadata(&path));
|
||||||
let (meta, meta2) = (meta?, meta2?);
|
let (meta, meta2) = (meta?, meta2?);
|
||||||
|
@ -1078,7 +1069,7 @@ impl Server {
|
||||||
PathType::Dir | PathType::SymlinkDir => None,
|
PathType::Dir | PathType::SymlinkDir => None,
|
||||||
PathType::File | PathType::SymlinkFile => Some(meta.len()),
|
PathType::File | PathType::SymlinkFile => Some(meta.len()),
|
||||||
};
|
};
|
||||||
let rel_path = path.strip_prefix(base_path).unwrap();
|
let rel_path = path.strip_prefix(base_path)?;
|
||||||
let name = normalize_path(rel_path);
|
let name = normalize_path(rel_path);
|
||||||
Ok(Some(PathItem {
|
Ok(Some(PathItem {
|
||||||
path_type,
|
path_type,
|
||||||
|
@ -1140,10 +1131,10 @@ impl PathItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_dav_xml(&self, prefix: &str) -> String {
|
pub fn to_dav_xml(&self, prefix: &str) -> String {
|
||||||
let mtime = Utc
|
let mtime = match Utc.timestamp_millis_opt(self.mtime as i64) {
|
||||||
.timestamp_millis_opt(self.mtime as i64)
|
LocalResult::Single(v) => v.to_rfc2822(),
|
||||||
.unwrap()
|
_ => String::new(),
|
||||||
.to_rfc2822();
|
};
|
||||||
let mut href = encode_uri(&format!("{}{}", prefix, &self.name));
|
let mut href = encode_uri(&format!("{}{}", prefix, &self.name));
|
||||||
if self.is_dir() && !href.ends_with('/') {
|
if self.is_dir() && !href.ends_with('/') {
|
||||||
href.push('/');
|
href.push('/');
|
||||||
|
@ -1198,7 +1189,7 @@ enum PathType {
|
||||||
|
|
||||||
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()
|
.unwrap_or_default()
|
||||||
.as_millis() as u64
|
.as_millis() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1211,7 +1202,7 @@ fn normalize_path<P: AsRef<Path>>(path: P) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_path_parent(path: &Path) -> BoxResult<()> {
|
async fn ensure_path_parent(path: &Path) -> Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
if fs::symlink_metadata(parent).await.is_err() {
|
if fs::symlink_metadata(parent).await.is_err() {
|
||||||
fs::create_dir_all(&parent).await?;
|
fs::create_dir_all(&parent).await?;
|
||||||
|
@ -1260,7 +1251,7 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
hidden: &[String],
|
hidden: &[String],
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
) -> BoxResult<()> {
|
) -> Result<()> {
|
||||||
let mut writer = ZipFileWriter::new(writer);
|
let mut writer = ZipFileWriter::new(writer);
|
||||||
let hidden = Arc::new(hidden.to_vec());
|
let hidden = Arc::new(hidden.to_vec());
|
||||||
let hidden = hidden.clone();
|
let hidden = hidden.clone();
|
||||||
|
@ -1323,7 +1314,7 @@ fn extract_cache_headers(meta: &Metadata) -> Option<(ETag, LastModified)> {
|
||||||
let mtime = meta.modified().ok()?;
|
let mtime = meta.modified().ok()?;
|
||||||
let timestamp = to_timestamp(&mtime);
|
let timestamp = to_timestamp(&mtime);
|
||||||
let size = meta.len();
|
let size = meta.len();
|
||||||
let etag = format!(r#""{timestamp}-{size}""#).parse::<ETag>().unwrap();
|
let etag = format!(r#""{timestamp}-{size}""#).parse::<ETag>().ok()?;
|
||||||
let last_modified = LastModified::from(mtime);
|
let last_modified = LastModified::from(mtime);
|
||||||
Some((etag, last_modified))
|
Some((etag, last_modified))
|
||||||
}
|
}
|
||||||
|
@ -1338,11 +1329,11 @@ fn parse_range(headers: &HeaderMap<HeaderValue>) -> Option<RangeValue> {
|
||||||
let range_hdr = headers.get(RANGE)?;
|
let range_hdr = headers.get(RANGE)?;
|
||||||
let hdr = range_hdr.to_str().ok()?;
|
let hdr = range_hdr.to_str().ok()?;
|
||||||
let mut sp = hdr.splitn(2, '=');
|
let mut sp = hdr.splitn(2, '=');
|
||||||
let units = sp.next().unwrap();
|
let units = sp.next()?;
|
||||||
if units == "bytes" {
|
if units == "bytes" {
|
||||||
let range = sp.next()?;
|
let range = sp.next()?;
|
||||||
let mut sp_range = range.splitn(2, '-');
|
let mut sp_range = range.splitn(2, '-');
|
||||||
let start: u64 = sp_range.next().unwrap().parse().ok()?;
|
let start: u64 = sp_range.next()?.parse().ok()?;
|
||||||
let end: Option<u64> = if let Some(end) = sp_range.next() {
|
let end: Option<u64> = if let Some(end) = sp_range.next() {
|
||||||
if end.is_empty() {
|
if end.is_empty() {
|
||||||
None
|
None
|
||||||
|
|
22
src/tls.rs
22
src/tls.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
use core::task::{Context, Poll};
|
use core::task::{Context, Poll};
|
||||||
use futures::ready;
|
use futures::ready;
|
||||||
use hyper::server::accept::Accept;
|
use hyper::server::accept::Accept;
|
||||||
|
@ -124,33 +125,30 @@ impl Accept for TlsAcceptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load public certificate from file.
|
// Load public certificate from file.
|
||||||
pub fn load_certs<T: AsRef<Path>>(
|
pub fn load_certs<T: AsRef<Path>>(filename: T) -> Result<Vec<Certificate>> {
|
||||||
filename: T,
|
|
||||||
) -> Result<Vec<Certificate>, Box<dyn std::error::Error>> {
|
|
||||||
// Open certificate file.
|
// Open certificate file.
|
||||||
let cert_file = fs::File::open(filename.as_ref())
|
let cert_file = fs::File::open(filename.as_ref())
|
||||||
.map_err(|e| format!("Failed to access `{}`, {}", filename.as_ref().display(), e))?;
|
.map_err(|e| anyhow!("Failed to access `{}`, {e}", filename.as_ref().display()))?;
|
||||||
let mut reader = io::BufReader::new(cert_file);
|
let mut reader = io::BufReader::new(cert_file);
|
||||||
|
|
||||||
// Load and return certificate.
|
// Load and return certificate.
|
||||||
let certs = rustls_pemfile::certs(&mut reader).map_err(|_| "Failed to load certificate")?;
|
let certs =
|
||||||
|
rustls_pemfile::certs(&mut reader).map_err(|_| anyhow!("Failed to load certificate"))?;
|
||||||
if certs.is_empty() {
|
if certs.is_empty() {
|
||||||
return Err("No supported certificate in file".into());
|
bail!("No supported certificate in file");
|
||||||
}
|
}
|
||||||
Ok(certs.into_iter().map(Certificate).collect())
|
Ok(certs.into_iter().map(Certificate).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load private key from file.
|
// Load private key from file.
|
||||||
pub fn load_private_key<T: AsRef<Path>>(
|
pub fn load_private_key<T: AsRef<Path>>(filename: T) -> Result<PrivateKey> {
|
||||||
filename: T,
|
|
||||||
) -> Result<PrivateKey, Box<dyn std::error::Error>> {
|
|
||||||
let key_file = fs::File::open(filename.as_ref())
|
let key_file = fs::File::open(filename.as_ref())
|
||||||
.map_err(|e| format!("Failed to access `{}`, {}", filename.as_ref().display(), e))?;
|
.map_err(|e| anyhow!("Failed to access `{}`, {e}", filename.as_ref().display()))?;
|
||||||
let mut reader = io::BufReader::new(key_file);
|
let mut reader = io::BufReader::new(key_file);
|
||||||
|
|
||||||
// Load and return a single private key.
|
// Load and return a single private key.
|
||||||
let keys = rustls_pemfile::read_all(&mut reader)
|
let keys = rustls_pemfile::read_all(&mut reader)
|
||||||
.map_err(|e| format!("There was a problem with reading private key: {e:?}"))?
|
.map_err(|e| anyhow!("There was a problem with reading private key: {e}"))?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find_map(|item| match item {
|
.find_map(|item| match item {
|
||||||
rustls_pemfile::Item::RSAKey(key)
|
rustls_pemfile::Item::RSAKey(key)
|
||||||
|
@ -158,7 +156,7 @@ pub fn load_private_key<T: AsRef<Path>>(
|
||||||
| rustls_pemfile::Item::ECKey(key) => Some(key),
|
| rustls_pemfile::Item::ECKey(key) => Some(key),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.ok_or("No supported private key in file")?;
|
.ok_or_else(|| anyhow!("No supported private key in file"))?;
|
||||||
|
|
||||||
Ok(PrivateKey(keys))
|
Ok(PrivateKey(keys))
|
||||||
}
|
}
|
||||||
|
|
18
src/utils.rs
18
src/utils.rs
|
@ -1,5 +1,15 @@
|
||||||
use crate::BoxResult;
|
use anyhow::{anyhow, Result};
|
||||||
use std::{borrow::Cow, path::Path};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
path::Path,
|
||||||
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn unix_now() -> Result<Duration> {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|err| anyhow!("Invalid system time, {err}"))
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
@ -18,10 +28,10 @@ pub fn get_file_name(path: &Path) -> &str {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get_file_name(path: &Path) -> BoxResult<&str> {
|
pub fn try_get_file_name(path: &Path) -> Result<&str> {
|
||||||
path.file_name()
|
path.file_name()
|
||||||
.and_then(|v| v.to_str())
|
.and_then(|v| v.to_str())
|
||||||
.ok_or_else(|| format!("Failed to get file name of `{}`", path.display()).into())
|
.ok_or_else(|| anyhow!("Failed to get file name of `{}`", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn glob(source: &str, target: &str) -> bool {
|
pub fn glob(source: &str, target: &str) -> bool {
|
||||||
|
|
Loading…
Reference in a new issue