diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | src/archive.rs | 55 | ||||
-rw-r--r-- | src/args.rs | 18 | ||||
-rw-r--r-- | src/auth.rs | 13 | ||||
-rw-r--r-- | src/errors.rs | 63 | ||||
-rw-r--r-- | src/file_upload.rs | 47 | ||||
-rw-r--r-- | src/main.rs | 41 | ||||
-rw-r--r-- | stale_outputs_checked | 0 |
8 files changed, 68 insertions, 171 deletions
@@ -46,7 +46,7 @@ Sometimes this is just a more practical and quick way than doing things properly - Easy to use - Just works: Correct MIME types handling out of the box - Single binary drop-in with no extra dependencies required -- Authentication support with username and password +- Authentication support with username and password (and hashed password) - Mega fast and highly parallel (thanks to [Rust](https://www.rust-lang.org/) and [Actix](https://actix.rs/)) - Folder download (compressed in .tar.gz) - File uploading diff --git a/src/archive.rs b/src/archive.rs index b5788f5..02300c5 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use strum_macros::{Display, EnumIter, EnumString}; use tar::Builder; -use crate::errors::{ContextualError, ContextualErrorKind}; +use crate::errors::ContextualError; /// Available compression methods #[derive(Deserialize, Clone, EnumIter, EnumString, Display)] @@ -62,17 +62,11 @@ fn tgz_compress(dir: &PathBuf, skip_symlinks: bool) -> Result<(String, Bytes), C let mut tgz_data = Bytes::new(); let tar_data = tar(src_dir, directory.to_string(), skip_symlinks).map_err(|e| { - ContextualError::new(ContextualErrorKind::ArchiveCreationError( - "tarball".to_string(), - Box::new(e), - )) + ContextualError::ArchiveCreationError("tarball".to_string(), Box::new(e)) })?; let gz_data = gzip(&tar_data).map_err(|e| { - ContextualError::new(ContextualErrorKind::ArchiveCreationError( - "GZIP archive".to_string(), - Box::new(e), - )) + ContextualError::ArchiveCreationError("GZIP archive".to_string(), Box::new(e)) })?; tgz_data.extend_from_slice(&gz_data); @@ -80,15 +74,15 @@ fn tgz_compress(dir: &PathBuf, skip_symlinks: bool) -> Result<(String, Bytes), C Ok((dst_tgz_filename, tgz_data)) } else { // https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_str - Err(ContextualError::new(ContextualErrorKind::InvalidPathError( + Err(ContextualError::InvalidPathError( "Directory name contains invalid UTF-8 characters".to_string(), - ))) + )) } } else { // https://doc.rust-lang.org/std/path/struct.Path.html#method.file_name - Err(ContextualError::new(ContextualErrorKind::InvalidPathError( + Err(ContextualError::InvalidPathError( "Directory name terminates in \"..\"".to_string(), - ))) + )) } } @@ -105,20 +99,17 @@ fn tar( tar_builder .append_dir_all(inner_folder, &src_dir) .map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( + ContextualError::IOError( format!( "Failed to append the content of {} to the TAR archive", &src_dir ), e, - )) + ) })?; let tar_content = tar_builder.into_inner().map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to finish writing the TAR archive".to_string(), - e, - )) + ContextualError::IOError("Failed to finish writing the TAR archive".to_string(), e) })?; Ok(tar_content) @@ -126,24 +117,14 @@ fn tar( /// Compresses a stream of bytes using the GZIP algorithm, and returns the resulting stream fn gzip(mut data: &[u8]) -> Result<Vec<u8>, ContextualError> { - let mut encoder = Encoder::new(Vec::new()).map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to create GZIP encoder".to_string(), - e, - )) - })?; - io::copy(&mut data, &mut encoder).map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to write GZIP data".to_string(), - e, - )) - })?; - let data = encoder.finish().into_result().map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to write GZIP trailer".to_string(), - e, - )) - })?; + let mut encoder = Encoder::new(Vec::new()) + .map_err(|e| ContextualError::IOError("Failed to create GZIP encoder".to_string(), e))?; + io::copy(&mut data, &mut encoder) + .map_err(|e| ContextualError::IOError("Failed to write GZIP data".to_string(), e))?; + let data = encoder + .finish() + .into_result() + .map_err(|e| ContextualError::IOError("Failed to write GZIP trailer".to_string(), e))?; Ok(data) } diff --git a/src/args.rs b/src/args.rs index 9ccb7a6..d37e429 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use structopt::StructOpt; use crate::auth; -use crate::errors::{ContextualError, ContextualErrorKind}; +use crate::errors::ContextualError; use crate::themes; /// Possible characters for random routes @@ -80,7 +80,7 @@ fn parse_interface(src: &str) -> Result<IpAddr, std::net::AddrParseError> { /// Checks wether the auth string is valid, i.e. it follows the syntax username:password fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> { let mut split = src.splitn(3, ':'); - let invalid_auth_format = Err(ContextualError::new(ContextualErrorKind::InvalidAuthFormat)); + let invalid_auth_format = Err(ContextualError::InvalidAuthFormat); let username = match split.next() { Some(username) => username, @@ -98,28 +98,20 @@ fn parse_auth(src: &str) -> Result<auth::RequiredAuth, ContextualError> { let hash_bin = if let Ok(hash_bin) = hex::decode(hash_hex) { hash_bin } else { - return Err(ContextualError::new( - ContextualErrorKind::InvalidPasswordHash, - )); + return Err(ContextualError::InvalidPasswordHash); }; match second_part { "sha256" => auth::RequiredAuthPassword::Sha256(hash_bin.to_owned()), "sha512" => auth::RequiredAuthPassword::Sha512(hash_bin.to_owned()), - _ => { - return Err(ContextualError::new( - ContextualErrorKind::InvalidHashMethod(second_part.to_owned()), - )) - } + _ => return Err(ContextualError::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::new( - ContextualErrorKind::PasswordTooLongError, - )); + return Err(ContextualError::PasswordTooLongError); } auth::RequiredAuthPassword::Plain(second_part.to_owned()) diff --git a/src/auth.rs b/src/auth.rs index a42bb53..c786d4b 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -3,7 +3,7 @@ use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; use sha2::{Digest, Sha256, Sha512}; -use crate::errors::{ContextualError, ContextualErrorKind}; +use crate::errors::ContextualError; pub struct Auth; @@ -36,13 +36,10 @@ pub fn parse_basic_auth( let basic_removed = authorization_header .to_str() .map_err(|e| { - ContextualError::new(ContextualErrorKind::ParseError( - "HTTP authentication header".to_string(), - e.to_string(), - )) + ContextualError::ParseError("HTTP authentication header".to_string(), e.to_string()) })? .replace("Basic ", ""); - let decoded = base64::decode(&basic_removed).map_err(ContextualErrorKind::Base64DecodeError)?; + let decoded = base64::decode(&basic_removed).map_err(ContextualError::Base64DecodeError)?; let decoded_str = String::from_utf8_lossy(&decoded); let credentials: Vec<&str> = decoded_str.splitn(2, ':').collect(); @@ -97,9 +94,7 @@ impl Middleware<crate::MiniserveConfig> for Auth { let auth_req = match parse_basic_auth(auth_headers) { Ok(auth_req) => auth_req, Err(err) => { - let auth_err = ContextualError::new( - ContextualErrorKind::HTTPAuthenticationError(Box::new(err)), - ); + let auth_err = ContextualError::HTTPAuthenticationError(Box::new(err)); return Ok(Response::Done( HttpResponse::BadRequest().body(auth_err.to_string()), )); diff --git a/src/errors.rs b/src/errors.rs index bf45b73..8264de0 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,8 +1,7 @@ -use failure::{Backtrace, Context, Fail}; -use std::fmt::{self, Debug, Display}; +use failure::Fail; #[derive(Debug, Fail)] -pub enum ContextualErrorKind { +pub enum ContextualError { /// Fully customized errors, not inheriting from any error #[fail(display = "{}", _0)] CustomError(String), @@ -33,10 +32,7 @@ pub enum ContextualErrorKind { InvalidAuthFormat, /// This error might occure if the hash method is neither sha256 nor sha512 - #[fail( - display = "Invalid hashing method {}. Expected sha256 or sha512", - _0 - )] + #[fail(display = "Invalid hashing method {}. Expected sha256 or sha512", _0)] InvalidHashMethod(String), /// This error might occur if the HTTP auth hash password is not a valid hex code @@ -80,58 +76,9 @@ pub fn log_error_chain(description: String) { } } -/// Based on https://boats.gitlab.io/failure/error-errorkind.html -pub struct ContextualError { - inner: Context<ContextualErrorKind>, -} - -impl ContextualError { - pub fn new(kind: ContextualErrorKind) -> ContextualError { - ContextualError { - inner: Context::new(kind), - } - } -} - -impl Fail for ContextualError { - fn cause(&self) -> Option<&Fail> { - self.inner.cause() - } - - fn backtrace(&self) -> Option<&Backtrace> { - self.inner.backtrace() - } -} - -impl Display for ContextualError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.inner, f) - } -} - -impl Debug for ContextualError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Debug::fmt(&self.inner, f) - } -} - -impl From<Context<ContextualErrorKind>> for ContextualError { - fn from(inner: Context<ContextualErrorKind>) -> ContextualError { - ContextualError { inner } - } -} - -impl From<ContextualErrorKind> for ContextualError { - fn from(kind: ContextualErrorKind) -> ContextualError { - ContextualError { - inner: Context::new(kind), - } - } -} - -/// This allows to create CustomErrors more simply +/// This makes creating CustomErrors easier impl From<String> for ContextualError { fn from(msg: String) -> ContextualError { - ContextualError::new(ContextualErrorKind::CustomError(msg)) + ContextualError::CustomError(msg) } } diff --git a/src/file_upload.rs b/src/file_upload.rs index f8c5019..46a3a1f 100644 --- a/src/file_upload.rs +++ b/src/file_upload.rs @@ -9,7 +9,7 @@ use std::{ path::{Component, PathBuf}, }; -use crate::errors::{self, ContextualError, ContextualErrorKind}; +use crate::errors::{self, ContextualError}; use crate::listing::{QueryParameters, SortingMethod, SortingOrder}; use crate::renderer; use crate::themes::ColorScheme; @@ -19,9 +19,9 @@ fn save_file( field: multipart::Field<dev::Payload>, file_path: PathBuf, overwrite_files: bool, -) -> Box<Future<Item = i64, Error = ContextualErrorKind>> { +) -> Box<Future<Item = i64, Error = ContextualError>> { if !overwrite_files && file_path.exists() { - return Box::new(future::err(ContextualErrorKind::CustomError( + return Box::new(future::err(ContextualError::CustomError( "File already exists, and the overwrite_files option has not been set".to_string(), ))); } @@ -29,7 +29,7 @@ fn save_file( let mut file = match std::fs::File::create(&file_path) { Ok(file) => file, Err(e) => { - return Box::new(future::err(ContextualErrorKind::IOError( + return Box::new(future::err(ContextualError::IOError( format!("Failed to create {}", file_path.display()), e, ))); @@ -37,13 +37,13 @@ fn save_file( }; Box::new( field - .map_err(ContextualErrorKind::MultipartError) + .map_err(ContextualError::MultipartError) .fold(0i64, move |acc, bytes| { let rt = file .write_all(bytes.as_ref()) .map(|_| acc + bytes.len() as i64) .map_err(|e| { - ContextualErrorKind::IOError("Failed to write to file".to_string(), e) + ContextualError::IOError("Failed to write to file".to_string(), e) }); future::result(rt) }), @@ -55,41 +55,41 @@ fn handle_multipart( item: multipart::MultipartItem<dev::Payload>, mut file_path: PathBuf, overwrite_files: bool, -) -> Box<Stream<Item = i64, Error = ContextualErrorKind>> { +) -> Box<Stream<Item = i64, Error = ContextualError>> { match item { multipart::MultipartItem::Field(field) => { let filename = field .headers() .get(header::CONTENT_DISPOSITION) - .ok_or(ContextualErrorKind::ParseError) + .ok_or(ContextualError::ParseError) .and_then(|cd| { header::ContentDisposition::from_raw(cd) - .map_err(|_| ContextualErrorKind::ParseError) + .map_err(|_| ContextualError::ParseError) }) .and_then(|content_disposition| { content_disposition .get_filename() - .ok_or(ContextualErrorKind::ParseError) + .ok_or(ContextualError::ParseError) .map(String::from) }); - let err = |e: ContextualErrorKind| Box::new(future::err(e).into_stream()); + let err = |e: ContextualError| Box::new(future::err(e).into_stream()); match filename { Ok(f) => { match fs::metadata(&file_path) { Ok(metadata) => { if !metadata.is_dir() { - return err(ContextualErrorKind::InvalidPathError(format!( + return err(ContextualError::InvalidPathError(format!( "cannot upload file to {}, since it's not a directory", &file_path.display() ))); } else if metadata.permissions().readonly() { - return err(ContextualErrorKind::InsufficientPermissionsError( + return err(ContextualError::InsufficientPermissionsError( file_path.display().to_string(), )); } } Err(_) => { - return err(ContextualErrorKind::InsufficientPermissionsError( + return err(ContextualError::InsufficientPermissionsError( file_path.display().to_string(), )); } @@ -104,7 +104,7 @@ fn handle_multipart( } } multipart::MultipartItem::Nested(mp) => Box::new( - mp.map_err(ContextualErrorKind::MultipartError) + mp.map_err(ContextualError::MultipartError) .map(move |item| handle_multipart(item, file_path.clone(), overwrite_files)) .flatten(), ), @@ -145,9 +145,9 @@ pub fn upload_file( (path.clone(), sort_param, order_param, theme_param) } } else { - let err = ContextualError::new(ContextualErrorKind::InvalidHTTPRequestError( + let err = ContextualError::InvalidHTTPRequestError( "Missing query parameter 'path'".to_string(), - )); + ); return Box::new(create_error_response( &err.to_string(), &return_path, @@ -159,8 +159,7 @@ pub fn upload_file( } } Err(e) => { - let err = - ContextualError::new(ContextualErrorKind::InvalidHTTPRequestError(e.to_string())); + let err = ContextualError::InvalidHTTPRequestError(e.to_string()); return Box::new(create_error_response( &err.to_string(), &return_path, @@ -175,10 +174,10 @@ pub fn upload_file( let app_root_dir = match req.state().path.canonicalize() { Ok(dir) => dir, Err(e) => { - let err = ContextualError::new(ContextualErrorKind::IOError( + let err = ContextualError::IOError( "Failed to resolve path served by miniserve".to_string(), e, - )); + ); return Box::new(create_error_response( &err.to_string(), &return_path, @@ -194,9 +193,9 @@ pub fn upload_file( let target_dir = match &app_root_dir.clone().join(path).canonicalize() { Ok(path) if path.starts_with(&app_root_dir) => path.clone(), _ => { - let err = ContextualError::new(ContextualErrorKind::InvalidHTTPRequestError( + let err = ContextualError::InvalidHTTPRequestError( "Invalid value for 'path' parameter".to_string(), - )); + ); return Box::new(create_error_response( &err.to_string(), &return_path, @@ -210,7 +209,7 @@ pub fn upload_file( let overwrite_files = req.state().overwrite_files; Box::new( req.multipart() - .map_err(ContextualErrorKind::MultipartError) + .map_err(ContextualError::MultipartError) .map(move |item| handle_multipart(item, target_dir.clone(), overwrite_files)) .flatten() .collect() diff --git a/src/main.rs b/src/main.rs index bd71763..c5c81f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ mod listing; mod renderer; mod themes; -use crate::errors::{ContextualError, ContextualErrorKind}; +use crate::errors::ContextualError; #[derive(Clone)] /// Configuration of the Miniserve application @@ -84,10 +84,7 @@ fn run() -> Result<(), ContextualError> { .path .symlink_metadata() .map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to retrieve symlink's metadata".to_string(), - e, - )) + ContextualError::IOError("Failed to retrieve symlink's metadata".to_string(), e) })? .file_type() .is_symlink() @@ -117,10 +114,7 @@ fn run() -> Result<(), ContextualError> { .collect::<Vec<String>>(); let canon_path = miniserve_config.path.canonicalize().map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to resolve path to be served".to_string(), - e, - )) + ContextualError::IOError("Failed to resolve path to be served".to_string(), e) })?; let path_string = canon_path.to_string_lossy(); @@ -135,20 +129,14 @@ fn run() -> Result<(), ContextualError> { " Invoke with -h|--help to see options or invoke as `miniserve .` to hide this advice." ); print!("Starting server in "); - io::stdout().flush().map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to write data".to_string(), - e, - )) - })?; + io::stdout() + .flush() + .map_err(|e| ContextualError::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::new(ContextualErrorKind::IOError( - "Failed to write data".to_string(), - e, - )) - })?; + io::stdout() + .flush() + .map_err(|e| ContextualError::IOError("Failed to write data".to_string(), e))?; thread::sleep(Duration::from_millis(500)); } } @@ -196,10 +184,10 @@ fn run() -> Result<(), ContextualError> { // Note that this should never fail, since CLI parsing succeeded // This means the format of each IP address is valid, and so is the port // Valid IpAddr + valid port == valid SocketAddr - return Err(ContextualError::new(ContextualErrorKind::ParseError( + return Err(ContextualError::ParseError( "string as socket address".to_string(), e.to_string(), - ))); + )); } }; @@ -210,12 +198,7 @@ fn run() -> Result<(), ContextualError> { .configure(configure_app) }) .bind(socket_addresses.as_slice()) - .map_err(|e| { - ContextualError::new(ContextualErrorKind::IOError( - "Failed to bind server".to_string(), - e, - )) - })? + .map_err(|e| ContextualError::IOError("Failed to bind server".to_string(), e))? .shutdown_timeout(0) .start(); diff --git a/stale_outputs_checked b/stale_outputs_checked deleted file mode 100644 index e69de29..0000000 --- a/stale_outputs_checked +++ /dev/null |