diff options
author | cyqsimon <28627918+cyqsimon@users.noreply.github.com> | 2024-01-29 09:11:23 +0000 |
---|---|---|
committer | cyqsimon <28627918+cyqsimon@users.noreply.github.com> | 2024-01-29 09:11:23 +0000 |
commit | 7d9a11dcda864a314c741c57df05635014f7f593 (patch) | |
tree | b100a03dd6b6410d2c1106dd3827cdf1f178672c /src | |
parent | Bump deps (diff) | |
download | miniserve-7d9a11dcda864a314c741c57df05635014f7f593.tar.gz miniserve-7d9a11dcda864a314c741c57df05635014f7f593.zip |
Refactor errors
- Split `ContexualError` into `StartError` & `RuntimeError`
- Made sure every `RuntimeError` variant has an accurate status code
Diffstat (limited to '')
-rw-r--r-- | src/archive.rs | 71 | ||||
-rw-r--r-- | src/args.rs | 32 | ||||
-rw-r--r-- | src/auth.rs | 4 | ||||
-rw-r--r-- | src/errors.rs | 69 | ||||
-rw-r--r-- | src/file_op.rs | 66 | ||||
-rw-r--r-- | src/listing.rs | 4 | ||||
-rw-r--r-- | src/main.rs | 30 |
7 files changed, 142 insertions, 134 deletions
diff --git a/src/archive.rs b/src/archive.rs index e52fc49..a058f20 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -8,7 +8,7 @@ use strum::{Display, EnumIter, EnumString}; use tar::Builder; use zip::{write, ZipWriter}; -use crate::errors::ContextualError; +use crate::errors::RuntimeError; /// Available archive methods #[derive(Deserialize, Clone, Copy, EnumIter, EnumString, Display)] @@ -62,7 +62,7 @@ impl ArchiveMethod { dir: T, skip_symlinks: bool, out: W, - ) -> Result<(), ContextualError> + ) -> Result<(), RuntimeError> where T: AsRef<Path>, W: std::io::Write, @@ -77,17 +77,17 @@ impl ArchiveMethod { } /// Write a gzipped tarball of `dir` in `out`. -fn tar_gz<W>(dir: &Path, skip_symlinks: bool, out: W) -> Result<(), ContextualError> +fn tar_gz<W>(dir: &Path, skip_symlinks: bool, out: W) -> Result<(), RuntimeError> where W: std::io::Write, { - let mut out = Encoder::new(out).map_err(|e| ContextualError::IoError("GZIP".to_string(), e))?; + let mut out = Encoder::new(out).map_err(|e| RuntimeError::IoError("GZIP".to_string(), e))?; tar_dir(dir, skip_symlinks, &mut out)?; out.finish() .into_result() - .map_err(|e| ContextualError::IoError("GZIP finish".to_string(), e))?; + .map_err(|e| RuntimeError::IoError("GZIP finish".to_string(), e))?; Ok(()) } @@ -115,22 +115,22 @@ where /// ├── f /// └── g /// ``` -fn tar_dir<W>(dir: &Path, skip_symlinks: bool, out: W) -> Result<(), ContextualError> +fn tar_dir<W>(dir: &Path, skip_symlinks: bool, out: W) -> Result<(), RuntimeError> where W: std::io::Write, { let inner_folder = dir.file_name().ok_or_else(|| { - ContextualError::InvalidPathError("Directory name terminates in \"..\"".to_string()) + RuntimeError::InvalidPathError("Directory name terminates in \"..\"".to_string()) })?; let directory = inner_folder.to_str().ok_or_else(|| { - ContextualError::InvalidPathError( + RuntimeError::InvalidPathError( "Directory name contains invalid UTF-8 characters".to_string(), ) })?; tar(dir, directory.to_string(), skip_symlinks, out) - .map_err(|e| ContextualError::ArchiveCreationError("tarball".to_string(), Box::new(e))) + .map_err(|e| RuntimeError::ArchiveCreationError("tarball".to_string(), Box::new(e))) } /// Writes a tarball of `dir` in `out`. @@ -141,7 +141,7 @@ fn tar<W>( inner_folder: String, skip_symlinks: bool, out: W, -) -> Result<(), ContextualError> +) -> Result<(), RuntimeError> where W: std::io::Write, { @@ -153,7 +153,7 @@ where tar_builder .append_dir_all(inner_folder, src_dir) .map_err(|e| { - ContextualError::IoError( + RuntimeError::IoError( format!( "Failed to append the content of {} to the TAR archive", src_dir.to_str().unwrap_or("file") @@ -164,7 +164,7 @@ where // Finish the archive tar_builder.into_inner().map_err(|e| { - ContextualError::IoError("Failed to finish writing the TAR archive".to_string(), e) + RuntimeError::IoError("Failed to finish writing the TAR archive".to_string(), e) })?; Ok(()) @@ -197,28 +197,28 @@ fn create_zip_from_directory<W>( out: W, directory: &Path, skip_symlinks: bool, -) -> Result<(), ContextualError> +) -> Result<(), RuntimeError> where W: std::io::Write + std::io::Seek, { let options = write::FileOptions::default().compression_method(zip::CompressionMethod::Stored); let mut paths_queue: Vec<PathBuf> = vec![directory.to_path_buf()]; let zip_root_folder_name = directory.file_name().ok_or_else(|| { - ContextualError::InvalidPathError("Directory name terminates in \"..\"".to_string()) + RuntimeError::InvalidPathError("Directory name terminates in \"..\"".to_string()) })?; let mut zip_writer = ZipWriter::new(out); let mut buffer = Vec::new(); while !paths_queue.is_empty() { let next = paths_queue.pop().ok_or_else(|| { - ContextualError::ArchiveCreationDetailError("Could not get path from queue".to_string()) + RuntimeError::ArchiveCreationDetailError("Could not get path from queue".to_string()) })?; let current_dir = next.as_path(); let directory_entry_iterator = std::fs::read_dir(current_dir) - .map_err(|e| ContextualError::IoError("Could not read directory".to_string(), e))?; + .map_err(|e| RuntimeError::IoError("Could not read directory".to_string(), e))?; let zip_directory = Path::new(zip_root_folder_name).join( current_dir.strip_prefix(directory).map_err(|_| { - ContextualError::ArchiveCreationDetailError( + RuntimeError::ArchiveCreationDetailError( "Could not append base directory".to_string(), ) })?, @@ -228,37 +228,36 @@ where let entry_path = entry .ok() .ok_or_else(|| { - ContextualError::InvalidPathError( + RuntimeError::InvalidPathError( "Directory name terminates in \"..\"".to_string(), ) })? .path(); - let entry_metadata = std::fs::metadata(entry_path.clone()).map_err(|e| { - ContextualError::IoError("Could not get file metadata".to_string(), e) - })?; + let entry_metadata = std::fs::metadata(entry_path.clone()) + .map_err(|e| RuntimeError::IoError("Could not get file metadata".to_string(), e))?; if entry_metadata.file_type().is_symlink() && skip_symlinks { continue; } let current_entry_name = entry_path.file_name().ok_or_else(|| { - ContextualError::InvalidPathError("Invalid file or directory name".to_string()) + RuntimeError::InvalidPathError("Invalid file or directory name".to_string()) })?; if entry_metadata.is_file() { let mut f = File::open(&entry_path) - .map_err(|e| ContextualError::IoError("Could not open file".to_string(), e))?; + .map_err(|e| RuntimeError::IoError("Could not open file".to_string(), e))?; f.read_to_end(&mut buffer).map_err(|e| { - ContextualError::IoError("Could not read from file".to_string(), e) + RuntimeError::IoError("Could not read from file".to_string(), e) })?; let relative_path = zip_directory.join(current_entry_name).into_os_string(); zip_writer .start_file(relative_path.to_string_lossy(), options) .map_err(|_| { - ContextualError::ArchiveCreationDetailError( + RuntimeError::ArchiveCreationDetailError( "Could not add file path to ZIP".to_string(), ) })?; zip_writer.write(buffer.as_ref()).map_err(|_| { - ContextualError::ArchiveCreationDetailError( + RuntimeError::ArchiveCreationDetailError( "Could not write file to ZIP".to_string(), ) })?; @@ -268,7 +267,7 @@ where zip_writer .add_directory(relative_path.to_string_lossy(), options) .map_err(|_| { - ContextualError::ArchiveCreationDetailError( + RuntimeError::ArchiveCreationDetailError( "Could not add directory path to ZIP".to_string(), ) })?; @@ -278,9 +277,7 @@ where } zip_writer.finish().map_err(|_| { - ContextualError::ArchiveCreationDetailError( - "Could not finish writing ZIP archive".to_string(), - ) + RuntimeError::ArchiveCreationDetailError("Could not finish writing ZIP archive".to_string()) })?; Ok(()) } @@ -288,39 +285,39 @@ where /// Writes a zip of `dir` in `out`. /// /// The content of `src_dir` will be saved in the archive as the folder named . -fn zip_data<W>(src_dir: &Path, skip_symlinks: bool, mut out: W) -> Result<(), ContextualError> +fn zip_data<W>(src_dir: &Path, skip_symlinks: bool, mut out: W) -> Result<(), RuntimeError> where W: std::io::Write, { let mut data = Vec::new(); let memory_file = Cursor::new(&mut data); create_zip_from_directory(memory_file, src_dir, skip_symlinks).map_err(|e| { - ContextualError::ArchiveCreationError( + RuntimeError::ArchiveCreationError( "Failed to create the ZIP archive".to_string(), Box::new(e), ) })?; out.write_all(data.as_mut_slice()) - .map_err(|e| ContextualError::IoError("Failed to write the ZIP archive".to_string(), e))?; + .map_err(|e| RuntimeError::IoError("Failed to write the ZIP archive".to_string(), e))?; Ok(()) } -fn zip_dir<W>(dir: &Path, skip_symlinks: bool, out: W) -> Result<(), ContextualError> +fn zip_dir<W>(dir: &Path, skip_symlinks: bool, out: W) -> Result<(), RuntimeError> where W: std::io::Write, { let inner_folder = dir.file_name().ok_or_else(|| { - ContextualError::InvalidPathError("Directory name terminates in \"..\"".to_string()) + RuntimeError::InvalidPathError("Directory name terminates in \"..\"".to_string()) })?; inner_folder.to_str().ok_or_else(|| { - ContextualError::InvalidPathError( + RuntimeError::InvalidPathError( "Directory name contains invalid UTF-8 characters".to_string(), ) })?; zip_data(dir, skip_symlinks, out) - .map_err(|e| ContextualError::ArchiveCreationError("zip".to_string(), Box::new(e))) + .map_err(|e| RuntimeError::ArchiveCreationError("zip".to_string(), Box::new(e))) } diff --git a/src/args.rs b/src/args.rs index e42d2ff..7e3668b 100644 --- a/src/args.rs +++ b/src/args.rs @@ -5,7 +5,6 @@ use clap::{Parser, ValueEnum, ValueHint}; use http::header::{HeaderMap, HeaderName, HeaderValue}; use crate::auth; -use crate::errors::ContextualError; use crate::listing::{SortingMethod, SortingOrder}; use crate::renderer::ThemeSlug; @@ -301,10 +300,31 @@ fn parse_interface(src: &str) -> Result<IpAddr, std::net::AddrParseError> { src.parse::<IpAddr>() } +#[derive(Clone, Debug, thiserror::Error)] +pub enum AuthParseError { + /// Might occur if the HTTP credential string does not respect the expected format + #[error("Invalid format for credentials string. Expected username:password, username:sha256:hash or username:sha512:hash")] + InvalidAuthFormat, + + /// Might occur if the hash method is neither sha256 nor sha512 + #[error("{0} is not a valid hashing method. Expected sha256 or sha512")] + InvalidHashMethod(String), + + /// Might occur if the HTTP auth hash password is not a valid hex code + #[error("Invalid format for password hash. Expected hex code")] + InvalidPasswordHash, + + /// Might occur if the HTTP auth password exceeds 255 characters + #[error("HTTP password length exceeds 255 characters")] + PasswordTooLong, +} + /// Parse authentication requirement -pub fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> { +pub fn parse_auth(src: &str) -> Result<auth::RequiredAuth, AuthParseError> { + use AuthParseError as E; + let mut split = src.splitn(3, ':'); - let invalid_auth_format = Err(ContextualError::InvalidAuthFormat); + let invalid_auth_format = Err(E::InvalidAuthFormat); let username = match split.next() { Some(username) => username, @@ -319,19 +339,19 @@ pub fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> { }; let password = if let Some(hash_hex) = split.next() { - let hash_bin = hex::decode(hash_hex).map_err(|_| ContextualError::InvalidPasswordHash)?; + let hash_bin = hex::decode(hash_hex).map_err(|_| E::InvalidPasswordHash)?; match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash_bin), "sha512" => auth::RequiredAuthPassword::Sha512(hash_bin), - _ => return Err(ContextualError::InvalidHashMethod(second_part.to_owned())), + _ => return Err(E::InvalidHashMethod(second_part.to_owned())), } } else { // To make it Windows-compatible, the password needs to be shorter than 255 characters. // After 255 characters, Windows will truncate the value. // As for the username, the spec does not mention a limit in length if second_part.len() > 255 { - return Err(ContextualError::PasswordTooLongError); + return Err(E::PasswordTooLong); } auth::RequiredAuthPassword::Plain(second_part.to_owned()) diff --git a/src/auth.rs b/src/auth.rs index 92157c9..ef16b0c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -2,7 +2,7 @@ use actix_web::{dev::ServiceRequest, HttpMessage}; use actix_web_httpauth::extractors::basic::BasicAuth; use sha2::{Digest, Sha256, Sha512}; -use crate::errors::ContextualError; +use crate::errors::RuntimeError; #[derive(Clone, Debug)] /// HTTP Basic authentication parameters @@ -86,7 +86,7 @@ pub async fn handle_auth( if match_auth(&cred.into(), required_auth) { Ok(req) } else { - Err((ContextualError::InvalidHttpCredentials.into(), req)) + Err((RuntimeError::InvalidHttpCredentials.into(), req)) } } diff --git a/src/errors.rs b/src/errors.rs index 80d9b9e..36bc29e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -11,7 +11,23 @@ use thiserror::Error; use crate::{renderer::render_error, MiniserveConfig}; #[derive(Debug, Error)] -pub enum ContextualError { +pub enum StartError { + /// Any kind of IO errors + #[error("{0}\ncaused by: {1}")] + IoError(String, std::io::Error), + + /// In case miniserve was invoked without an interactive terminal and without an explicit path + #[error("Refusing to start as no explicit serve path was set and no interactive terminal was attached +Please set an explicit serve path like: `miniserve /my/path`")] + NoExplicitPathAndNoTerminal, + + /// In case miniserve was invoked with --no-symlinks but the serve path is a symlink + #[error("The -P|--no-symlinks option was provided but the serve path '{0}' is a symlink")] + NoSymlinksOptionWithSymlinkServePath(String), +} + +#[derive(Debug, Error)] +pub enum RuntimeError { /// Any kind of IO errors #[error("{0}\ncaused by: {1}")] IoError(String, std::io::Error), @@ -32,22 +48,6 @@ pub enum ContextualError { #[error("Invalid path\ncaused by: {0}")] InvalidPathError(String), - /// Might occur if the HTTP credential string does not respect the expected format - #[error("Invalid format for credentials string. Expected username:password, username:sha256:hash or username:sha512:hash")] - InvalidAuthFormat, - - /// Might occur if the hash method is neither sha256 nor sha512 - #[error("{0} is not a valid hashing method. Expected sha256 or sha512")] - InvalidHashMethod(String), - - /// Might occur if the HTTP auth hash password is not a valid hex code - #[error("Invalid format for password hash. Expected hex code")] - InvalidPasswordHash, - - /// Might occur if the HTTP auth password exceeds 255 characters - #[error("HTTP password length exceeds 255 characters")] - PasswordTooLongError, - /// Might occur if the user has insufficient permissions to create an entry in a given directory #[error("Insufficient permissions to create file in {0}")] InsufficientPermissionsError(String), @@ -58,7 +58,7 @@ pub enum ContextualError { /// Might occur when the creation of an archive fails #[error("An error occurred while creating the {0}\ncaused by: {1}")] - ArchiveCreationError(String, Box<ContextualError>), + ArchiveCreationError(String, Box<RuntimeError>), /// More specific archive creation failure reason #[error("{0}")] @@ -75,28 +75,25 @@ pub enum ContextualError { /// Might occur when trying to access a page that does not exist #[error("Route {0} could not be found")] RouteNotFoundError(String), - - /// In case miniserve was invoked without an interactive terminal and without an explicit path - #[error("Refusing to start as no explicit serve path was set and no interactive terminal was attached -Please set an explicit serve path like: `miniserve /my/path`")] - NoExplicitPathAndNoTerminal, - - /// In case miniserve was invoked with --no-symlinks but the serve path is a symlink - #[error("The -P|--no-symlinks option was provided but the serve path '{0}' is a symlink")] - NoSymlinksOptionWithSymlinkServePath(String), } -impl ResponseError for ContextualError { +impl ResponseError for RuntimeError { fn status_code(&self) -> StatusCode { + use RuntimeError as E; + use StatusCode as S; match self { - Self::ArchiveCreationError(_, err) => err.status_code(), - Self::RouteNotFoundError(_) => StatusCode::NOT_FOUND, - Self::InsufficientPermissionsError(_) => StatusCode::FORBIDDEN, - Self::InvalidHttpCredentials => StatusCode::UNAUTHORIZED, - Self::InvalidHttpRequestError(_) => StatusCode::BAD_REQUEST, - Self::DuplicateFileError => StatusCode::FORBIDDEN, - Self::UploadForbiddenError => StatusCode::FORBIDDEN, - _ => StatusCode::INTERNAL_SERVER_ERROR, + E::IoError(_, _) => S::INTERNAL_SERVER_ERROR, + E::MultipartError(_) => S::BAD_REQUEST, + E::DuplicateFileError => S::CONFLICT, + E::UploadForbiddenError => S::FORBIDDEN, + E::InvalidPathError(_) => S::BAD_REQUEST, + E::InsufficientPermissionsError(_) => S::FORBIDDEN, + E::ParseError(_, _) => S::BAD_REQUEST, + E::ArchiveCreationError(_, err) => err.status_code(), + E::ArchiveCreationDetailError(_) => S::INTERNAL_SERVER_ERROR, + E::InvalidHttpCredentials => S::UNAUTHORIZED, + E::InvalidHttpRequestError(_) => S::BAD_REQUEST, + E::RouteNotFoundError(_) => S::NOT_FOUND, } } diff --git a/src/file_op.rs b/src/file_op.rs index 35e56fa..e22e3e9 100644 --- a/src/file_op.rs +++ b/src/file_op.rs @@ -11,7 +11,7 @@ use tokio::fs::File; use tokio::io::AsyncWriteExt; use crate::{ - config::MiniserveConfig, errors::ContextualError, file_utils::contains_symlink, + config::MiniserveConfig, errors::RuntimeError, file_utils::contains_symlink, file_utils::sanitize_path, }; @@ -23,16 +23,16 @@ async fn save_file( field: actix_multipart::Field, file_path: PathBuf, overwrite_files: bool, -) -> Result<u64, ContextualError> { +) -> Result<u64, RuntimeError> { if !overwrite_files && file_path.exists() { - return Err(ContextualError::DuplicateFileError); + return Err(RuntimeError::DuplicateFileError); } let file = match File::create(&file_path).await { Err(err) if err.kind() == ErrorKind::PermissionDenied => Err( - ContextualError::InsufficientPermissionsError(file_path.display().to_string()), + RuntimeError::InsufficientPermissionsError(file_path.display().to_string()), ), - Err(err) => Err(ContextualError::IoError( + Err(err) => Err(RuntimeError::IoError( format!("Failed to create {}", file_path.display()), err, )), @@ -40,10 +40,10 @@ async fn save_file( }?; let (_, written_len) = field - .map_err(|x| ContextualError::MultipartError(x.to_string())) + .map_err(|x| RuntimeError::MultipartError(x.to_string())) .try_fold((file, 0u64), |(mut file, written_len), bytes| async move { file.write_all(bytes.as_ref()) - .map_err(|e| ContextualError::IoError("Failed to write to file".to_string(), e)) + .map_err(|e| RuntimeError::IoError("Failed to write to file".to_string(), e)) .await?; Ok((file, written_len + bytes.len() as u64)) }) @@ -60,14 +60,14 @@ async fn handle_multipart( allow_mkdir: bool, allow_hidden_paths: bool, allow_symlinks: bool, -) -> Result<u64, ContextualError> { +) -> Result<u64, RuntimeError> { let field_name = field.name().to_string(); match tokio::fs::metadata(&path).await { - Err(_) => Err(ContextualError::InsufficientPermissionsError( + Err(_) => Err(RuntimeError::InsufficientPermissionsError( path.display().to_string(), )), - Ok(metadata) if !metadata.is_dir() => Err(ContextualError::InvalidPathError(format!( + Ok(metadata) if !metadata.is_dir() => Err(RuntimeError::InvalidPathError(format!( "cannot upload file to {}, since it's not a directory", &path.display() ))), @@ -76,7 +76,7 @@ async fn handle_multipart( if field_name == "mkdir" { if !allow_mkdir { - return Err(ContextualError::InsufficientPermissionsError( + return Err(RuntimeError::InsufficientPermissionsError( path.display().to_string(), )); } @@ -89,7 +89,7 @@ async fn handle_multipart( match mkdir_path_bytes { Ok(Some(mkdir_path_bytes)) => { let mkdir_path = std::str::from_utf8(&mkdir_path_bytes).map_err(|e| { - ContextualError::ParseError( + RuntimeError::ParseError( "Failed to parse 'mkdir' path".to_string(), e.to_string(), ) @@ -99,7 +99,7 @@ async fn handle_multipart( user_given_path.push(&mkdir_path); } _ => { - return Err(ContextualError::ParseError( + return Err(RuntimeError::ParseError( "Failed to parse 'mkdir' path".to_string(), "".to_string(), )) @@ -111,22 +111,20 @@ async fn handle_multipart( .components() .any(|c| c == Component::ParentDir) { - return Err(ContextualError::InvalidPathError( + return Err(RuntimeError::InvalidPathError( "Cannot use '..' in mkdir path".to_string(), )); } // Hidden paths check sanitize_path(&user_given_path, allow_hidden_paths).ok_or_else(|| { - ContextualError::InvalidPathError("Cannot use hidden paths in mkdir path".to_string()) + RuntimeError::InvalidPathError("Cannot use hidden paths in mkdir path".to_string()) })?; // Ensure there are no illegal symlinks if !allow_symlinks { match contains_symlink(&absolute_path) { - Err(err) => Err(ContextualError::InsufficientPermissionsError( - err.to_string(), - ))?, - Ok(true) => Err(ContextualError::InsufficientPermissionsError(format!( + Err(err) => Err(RuntimeError::InsufficientPermissionsError(err.to_string()))?, + Ok(true) => Err(RuntimeError::InsufficientPermissionsError(format!( "{user_given_path:?} traverses through a symlink" )))?, Ok(false) => (), @@ -135,9 +133,9 @@ async fn handle_multipart( return match tokio::fs::create_dir_all(&absolute_path).await { Err(err) if err.kind() == ErrorKind::PermissionDenied => Err( - ContextualError::InsufficientPermissionsError(path.display().to_string()), + RuntimeError::InsufficientPermissionsError(path.display().to_string()), ), - Err(err) => Err(ContextualError::IoError( + Err(err) => Err(RuntimeError::IoError( format!("Failed to create {}", user_given_path.display()), err, )), @@ -146,24 +144,20 @@ async fn handle_multipart( } let filename = field.content_disposition().get_filename().ok_or_else(|| { - ContextualError::ParseError( + RuntimeError::ParseError( "HTTP header".to_string(), "Failed to retrieve the name of the file to upload".to_string(), ) })?; - let filename_path = - sanitize_path(Path::new(&filename), allow_hidden_paths).ok_or_else(|| { - ContextualError::InvalidPathError("Invalid file name to upload".to_string()) - })?; + let filename_path = sanitize_path(Path::new(&filename), allow_hidden_paths) + .ok_or_else(|| RuntimeError::InvalidPathError("Invalid file name to upload".to_string()))?; // Ensure there are no illegal symlinks in the file upload path if !allow_symlinks { match contains_symlink(&path) { - Err(err) => Err(ContextualError::InsufficientPermissionsError( - err.to_string(), - ))?, - Ok(true) => Err(ContextualError::InsufficientPermissionsError(format!( + Err(err) => Err(RuntimeError::InsufficientPermissionsError(err.to_string()))?, + Ok(true) => Err(RuntimeError::InsufficientPermissionsError(format!( "{path:?} traverses through a symlink" )))?, Ok(false) => (), @@ -188,13 +182,13 @@ pub async fn upload_file( req: HttpRequest, query: web::Query<FileOpQueryParameters>, payload: web::Payload, -) -> Result<HttpResponse, ContextualError> { +) -> Result<HttpResponse, RuntimeError> { let conf = req.app_data::<MiniserveConfig>().unwrap(); let upload_path = sanitize_path(&query.path, conf.show_hidden).ok_or_else(|| { - ContextualError::InvalidPathError("Invalid value for 'path' parameter".to_string()) + RuntimeError::InvalidPathError("Invalid value for 'path' parameter".to_string()) })?; let app_root_dir = conf.path.canonicalize().map_err(|e| { - ContextualError::IoError("Failed to resolve path served by miniserve".to_string(), e) + RuntimeError::IoError("Failed to resolve path served by miniserve".to_string(), e) })?; // Disallow paths outside of allowed directories @@ -205,7 +199,7 @@ pub async fn upload_file( .any(|s| upload_path.starts_with(s)); if !upload_allowed { - return Err(ContextualError::UploadForbiddenError); + return Err(RuntimeError::UploadForbiddenError); } // Disallow the target path to go outside of the served directory @@ -215,13 +209,13 @@ pub async fn upload_file( match non_canonicalized_target_dir.canonicalize() { Ok(path) if !conf.no_symlinks => Ok(path), Ok(path) if path.starts_with(&app_root_dir) => Ok(path), - _ => Err(ContextualError::InvalidHttpRequestError( + _ => Err(RuntimeError::InvalidHttpRequestError( "Invalid value for 'path' parameter".to_string(), )), }?; actix_multipart::Multipart::new(req.headers(), payload) - .map_err(|x| ContextualError::MultipartError(x.to_string())) + .map_err(|x| RuntimeError::MultipartError(x.to_string())) .and_then(|field| { handle_multipart( field, diff --git a/src/listing.rs b/src/listing.rs index faa0918..855abef 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -14,7 +14,7 @@ use strum::{Display, EnumString}; use crate::archive::ArchiveMethod; use crate::auth::CurrentUser; -use crate::errors::{self, ContextualError}; +use crate::errors::{self, RuntimeError}; use crate::renderer; use self::percent_encode_sets::PATH_SEGMENT; @@ -400,7 +400,7 @@ pub fn extract_query_parameters(req: &HttpRequest) -> ListingQueryParameters { match Query::<ListingQueryParameters>::from_query(req.query_string()) { Ok(Query(query_params)) => query_params, Err(e) => { - let err = ContextualError::ParseError("query parameters".to_string(), e.to_string()); + let err = RuntimeError::ParseError("query parameters".to_string(), e.to_string()); errors::log_error_chain(err.to_string()); ListingQueryParameters::default() } diff --git a/src/main.rs b/src/main.rs index ce21260..cdcd8cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ mod pipe; mod renderer; use crate::config::MiniserveConfig; -use crate::errors::ContextualError; +use crate::errors::{RuntimeError, StartError}; static STYLESHEET: &str = grass::include!("data/style.scss"); @@ -61,7 +61,7 @@ fn main() -> Result<()> { } #[actix_web::main(miniserve)] -async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { +async fn run(miniserve_config: MiniserveConfig) -> Result<(), StartError> { let log_level = if miniserve_config.verbose { simplelog::LevelFilter::Info } else { @@ -84,16 +84,17 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { .expect("Couldn't initialize logger"); if miniserve_config.no_symlinks && miniserve_config.path.is_symlink() { - return Err(ContextualError::NoSymlinksOptionWithSymlinkServePath( + return Err(StartError::NoSymlinksOptionWithSymlinkServePath( miniserve_config.path.to_string_lossy().to_string(), )); } let inside_config = miniserve_config.clone(); - let canon_path = miniserve_config.path.canonicalize().map_err(|e| { - ContextualError::IoError("Failed to resolve path to be served".to_string(), e) - })?; + let canon_path = miniserve_config + .path + .canonicalize() + .map_err(|e| StartError::IoError("Failed to resolve path to be served".to_string(), e))?; // warn if --index is specified but not found if let Some(ref index) = miniserve_config.index { @@ -118,7 +119,7 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { // running miniserve as a service but forgetting to set the path. This could be pretty // dangerous if given with an undesired context path (for instance /root or /). if !io::stdout().is_terminal() { - return Err(ContextualError::NoExplicitPathAndNoTerminal); + return Err(StartError::NoExplicitPathAndNoTerminal); } warn!("miniserve has been invoked without an explicit path so it will serve the current directory after a short delay."); @@ -128,12 +129,12 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { print!("Starting server in "); io::stdout() .flush() - .map_err(|e| ContextualError::IoError("Failed to write data".to_string(), e))?; + .map_err(|e| StartError::IoError("Failed to write data".to_string(), e))?; for c in "3… 2… 1… \n".chars() { print!("{c}"); io::stdout() .flush() - .map_err(|e| ContextualError::IoError("Failed to write data".to_string(), e))?; + .map_err(|e| StartError::IoError("Failed to write data".to_string(), e))?; thread::sleep(Duration::from_millis(500)); } } @@ -223,7 +224,7 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { let srv = socket_addresses.iter().try_fold(srv, |srv, addr| { let listener = create_tcp_listener(*addr) - .map_err(|e| ContextualError::IoError(format!("Failed to bind server to {addr}"), e))?; + .map_err(|e| StartError::IoError(format!("Failed to bind server to {addr}"), e))?; #[cfg(feature = "tls")] let srv = match &miniserve_config.tls_rustls_config { @@ -234,7 +235,7 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { #[cfg(not(feature = "tls"))] let srv = srv.listen(listener); - srv.map_err(|e| ContextualError::IoError(format!("Failed to bind server to {addr}"), e)) + srv.map_err(|e| StartError::IoError(format!("Failed to bind server to {addr}"), e)) })?; let srv = srv.shutdown_timeout(0).run(); @@ -274,8 +275,7 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { println!("Quit by pressing CTRL-C"); } - srv.await - .map_err(|e| ContextualError::IoError("".to_owned(), e)) + srv.await.map_err(|e| StartError::IoError("".to_owned(), e)) } /// Allows us to set low-level socket options @@ -378,8 +378,8 @@ fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) { } } -async fn error_404(req: HttpRequest) -> Result<HttpResponse, ContextualError> { - Err(ContextualError::RouteNotFoundError(req.path().to_string())) +async fn error_404(req: HttpRequest) -> Result<HttpResponse, RuntimeError> { + Err(RuntimeError::RouteNotFoundError(req.path().to_string())) } async fn favicon() -> impl Responder { |