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