]> OzVa Git service - ozva-cloud/commitdiff
chore: use anyhow to handle error
authorsigoden <sigoden@gmail.com>
Tue, 21 Feb 2023 09:23:24 +0000 (17:23 +0800)
committersigoden <sigoden@gmail.com>
Tue, 21 Feb 2023 09:23:24 +0000 (17:23 +0800)
Cargo.lock
Cargo.toml
src/args.rs
src/auth.rs
src/log_http.rs
src/main.rs
src/server.rs
src/tls.rs
src/utils.rs

index 1f0dba2d55e2b014e6c5b26e12dbee6ca7fdbc1a..845053a34e13a578377b3aa0955da5845dded03c 100644 (file)
@@ -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",
index 20facaaa4be60a1a8a74dfdf506a4b949260b4b3..2d5ce0e62c3c6f1ff86a3b56569d58a01913b919 100644 (file)
@@ -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"]
index a72095183f60db648cfcfcda39beced6a7278ed7..44ab591d466a31d9b5d1af1b2037bedfb971eea1 100644 (file)
@@ -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<Args> {
+    pub fn parse(matches: ArgMatches) -> Result<Args> {
         let port = *matches.get_one::<u16>("port").unwrap();
         let addrs = matches
             .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 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<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> {
+    fn parse_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
         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<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> {
+    fn parse_assets_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
         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)
     }
index 56f97eb7d44896fc118a160ccc37d325d4389ebc..018a7a7ced2fe7e492309f51b38e26026cf5ff84 100644 (file)
@@ -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<Self> {
+    pub fn new(raw_rules: &[&str], uri_prefix: &str) -> Result<Self> {
         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<String> {
         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<bool, ()> {
+fn validate_nonce(nonce: &[u8]) -> Result<bool> {
     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<bool, ()> {
             }
         }
     }
-    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<HashMap<&[u8], &[u8]>, ()> {
     Ok(ret)
 }
 
-fn create_nonce() -> String {
-    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+fn create_nonce() -> Result<String> {
+    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())
 }
index 9dc8acb125aced9879035fd8ac75773df2d5d3d8..505c30866f2d44e9afa7c301ffec64141282a046 100644 (file)
@@ -67,7 +67,7 @@ impl 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> {
         let mut elements = vec![];
         let mut is_var = false;
index 165acfba2c994b10298467cec3b33499a7b50e83..531d0d8e2862ab3cb580e5ca06e8a39147f19203 100644 (file)
@@ -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<T> = Result<T, Box<dyn std::error::Error>>;
-
 #[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::<Shell>("completions") {
@@ -74,8 +76,8 @@ async fn run() -> BoxResult<()> {
 fn serve(
     args: Arc<Args>,
     running: Arc<AtomicBool>,
-) -> BoxResult<Vec<JoinHandle<Result<(), hyper::Error>>>> {
-    let inner = Arc::new(Server::new(args.clone(), running));
+) -> Result<Vec<JoinHandle<Result<(), hyper::Error>>>> {
+    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<AddrIncoming> {
+fn create_addr_incoming(addr: SocketAddr) -> Result<AddrIncoming> {
     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<AddrIncoming> {
     Ok(incoming)
 }
 
-fn print_listening(args: Arc<Args>) -> BoxResult<()> {
+fn print_listening(args: Arc<Args>) -> 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<Args>) -> 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<Args>) -> BoxResult<()> {
     Ok(())
 }
 
-fn handle_err<T>(err: Box<dyn std::error::Error>) -> T {
-    eprintln!("error: {err}");
-    std::process::exit(1);
-}
-
 async fn shutdown_signal() {
     tokio::signal::ctrl_c()
         .await
index 2ea89ff12d9c2ec3380cd209f65dfb224b05ea0f..1b1dabdf7ec67f901ca0857ab586996d3fc7f9bf 100644 (file)
@@ -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<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 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<Self>, req: Request) -> BoxResult<Response> {
+    pub async fn handle(self: Arc<Self>, req: Request) -> Result<Response> {
         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<String>,
         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<String>,
         res: &mut Response,
-    ) -> BoxResult<()> {
+    ) -> Result<()> {
         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() {
             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<String>,
         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<HeaderValue>,
         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<HeaderValue>,
         res: &mut Response,
-    ) -> BoxResult<bool> {
+    ) -> Result<bool> {
         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<HeaderValue>,
         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<String>,
         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<HeaderValue>,
         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#"<?xml version="1.0" encoding="utf-8"?>
@@ -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#"<D:response>
 <D:href>{req_path}</D:href>
@@ -878,7 +873,7 @@ impl Server {
         head_only: bool,
         user: Option<String>,
         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<Vec<PathItem>> {
+    async fn list_dir(&self, entry_path: &Path, base_path: &Path) -> Result<Vec<PathItem>> {
         let mut paths: Vec<PathItem> = 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<P: AsRef<Path>>(
-        &self,
-        path: P,
-        base_path: P,
-    ) -> BoxResult<Option<PathItem>> {
+    async fn to_pathitem<P: AsRef<Path>>(&self, path: P, base_path: P) -> Result<Option<PathItem>> {
         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<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 fs::symlink_metadata(parent).await.is_err() {
             fs::create_dir_all(&parent).await?;
@@ -1260,7 +1251,7 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
     dir: &Path,
     hidden: &[String],
     running: Arc<AtomicBool>,
-) -> 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::<ETag>().unwrap();
+    let etag = format!(r#""{timestamp}-{size}""#).parse::<ETag>().ok()?;
     let last_modified = LastModified::from(mtime);
     Some((etag, last_modified))
 }
@@ -1338,11 +1329,11 @@ fn parse_range(headers: &HeaderMap<HeaderValue>) -> Option<RangeValue> {
     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<u64> = if let Some(end) = sp_range.next() {
             if end.is_empty() {
                 None
index 06de8020266796aeb00ad0cd64a17eaf150cbd3c..fac543ce8fa93940e3ae292df5de7681d657dbc2 100644 (file)
@@ -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<T: AsRef<Path>>(
-    filename: T,
-) -> Result<Vec<Certificate>, Box<dyn std::error::Error>> {
+pub fn load_certs<T: AsRef<Path>>(filename: T) -> Result<Vec<Certificate>> {
     // 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<T: AsRef<Path>>(
-    filename: T,
-) -> Result<PrivateKey, Box<dyn std::error::Error>> {
+pub fn load_private_key<T: AsRef<Path>>(filename: T) -> Result<PrivateKey> {
     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<T: AsRef<Path>>(
             | 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))
 }
index 995cfd699c766b57b9c778b22b250f8549ce7e10..4a8a6fea17dfc072bdee16a022358f6e0fb2f5ba 100644 (file)
@@ -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<Duration> {
+    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 {