aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/archive.rs106
-rw-r--r--src/args.rs15
-rw-r--r--src/auth.rs29
-rw-r--r--src/errors.rs135
-rw-r--r--src/file_upload.rs57
-rw-r--r--src/main.rs87
6 files changed, 251 insertions, 178 deletions
diff --git a/src/archive.rs b/src/archive.rs
index 4703c0d..00d2901 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -1,6 +1,5 @@
use actix_web::http::ContentEncoding;
use bytes::Bytes;
-use failure::ResultExt;
use libflate::gzip::Encoder;
use serde::Deserialize;
use std::io;
@@ -8,7 +7,7 @@ use std::path::PathBuf;
use strum_macros::{Display, EnumIter, EnumString};
use tar::Builder;
-use crate::errors;
+use crate::errors::{ContextualError, ContextualErrorKind};
/// Available compression methods
#[derive(Deserialize, Clone, EnumIter, EnumString, Display)]
@@ -47,45 +46,47 @@ pub fn create_archive(
method: &CompressionMethod,
dir: &PathBuf,
skip_symlinks: bool,
-) -> Result<(String, Bytes), errors::CompressionError> {
+) -> Result<(String, Bytes), ContextualError> {
match method {
CompressionMethod::TarGz => tgz_compress(&dir, skip_symlinks),
}
}
/// Compresses a given folder in .tar.gz format, and returns the result as a stream of bytes
-fn tgz_compress(
- dir: &PathBuf,
- skip_symlinks: bool,
-) -> Result<(String, Bytes), errors::CompressionError> {
+fn tgz_compress(dir: &PathBuf, skip_symlinks: bool) -> Result<(String, Bytes), ContextualError> {
let src_dir = dir.display().to_string();
let inner_folder = match dir.file_name() {
Some(directory_name) => match directory_name.to_str() {
Some(directory) => directory,
None => {
- return Err(errors::CompressionError::new(
- errors::CompressionErrorKind::InvalidUTF8DirectoryName,
- ))
+ // https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_str
+ return Err(ContextualError::new(ContextualErrorKind::InvalidPathError(
+ "Directory name contains invalid UTF-8 characters".to_string(),
+ )));
}
},
None => {
- return Err(errors::CompressionError::new(
- errors::CompressionErrorKind::InvalidDirectoryName,
- ))
+ // https://doc.rust-lang.org/std/path/struct.Path.html#method.file_name
+ return Err(ContextualError::new(ContextualErrorKind::InvalidPathError(
+ "Directory name terminates in \"..\"".to_string(),
+ )));
}
};
let dst_filename = format!("{}.tar", inner_folder);
let dst_tgz_filename = format!("{}.gz", dst_filename);
- let tar_content = tar(src_dir, inner_folder.to_string(), skip_symlinks).context(
- errors::CompressionErrorKind::TarBuildingError {
- message: "an error occured while writing the TAR archive".to_string(),
- },
- )?;
- let gz_data = gzip(&tar_content).context(errors::CompressionErrorKind::GZipBuildingError {
- message: "an error occured while writing the GZIP archive".to_string(),
+ let tar_content = tar(src_dir, inner_folder.to_string(), skip_symlinks).map_err(|e| {
+ ContextualError::new(ContextualErrorKind::ArchiveCreationError(
+ "tarball".to_string(),
+ Box::new(e),
+ ))
+ })?;
+ let gz_data = gzip(&tar_content).map_err(|e| {
+ ContextualError::new(ContextualErrorKind::ArchiveCreationError(
+ "GZIP archive".to_string(),
+ Box::new(e),
+ ))
})?;
-
let mut data = Bytes::new();
data.extend_from_slice(&gz_data);
@@ -97,44 +98,53 @@ fn tar(
src_dir: String,
inner_folder: String,
skip_symlinks: bool,
-) -> Result<Vec<u8>, errors::CompressionError> {
+) -> Result<Vec<u8>, ContextualError> {
let mut tar_builder = Builder::new(Vec::new());
tar_builder.follow_symlinks(!skip_symlinks);
// Recursively adds the content of src_dir into the archive stream
- tar_builder.append_dir_all(inner_folder, &src_dir).context(
- errors::CompressionErrorKind::TarBuildingError {
- message: format!(
- "failed to append the content of {} to the TAR archive",
- &src_dir
- ),
- },
- )?;
+ tar_builder
+ .append_dir_all(inner_folder, &src_dir)
+ .map_err(|e| {
+ ContextualError::new(ContextualErrorKind::IOError(
+ format!(
+ "Failed to append the content of {} to the TAR archive",
+ &src_dir
+ ),
+ e,
+ ))
+ })?;
- let tar_content =
- tar_builder
- .into_inner()
- .context(errors::CompressionErrorKind::TarBuildingError {
- message: "failed to finish writing the TAR archive".to_string(),
- })?;
+ let tar_content = tar_builder.into_inner().map_err(|e| {
+ ContextualError::new(ContextualErrorKind::IOError(
+ "Failed to finish writing the TAR archive".to_string(),
+ e,
+ ))
+ })?;
Ok(tar_content)
}
/// Compresses a stream of bytes using the GZIP algorithm, and returns the resulting stream
-fn gzip(mut data: &[u8]) -> Result<Vec<u8>, errors::CompressionError> {
- let mut encoder =
- Encoder::new(Vec::new()).context(errors::CompressionErrorKind::GZipBuildingError {
- message: "failed to create GZIP encoder".to_string(),
- })?;
- io::copy(&mut data, &mut encoder).context(errors::CompressionErrorKind::GZipBuildingError {
- message: "failed to write GZIP data".to_string(),
+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 data = encoder.finish().into_result().context(
- errors::CompressionErrorKind::GZipBuildingError {
- message: "failed to write GZIP trailer".to_string(),
- },
- )?;
Ok(data)
}
diff --git a/src/args.rs b/src/args.rs
index 825a4ac..8d2e105 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -3,6 +3,7 @@ use std::path::PathBuf;
use structopt::StructOpt;
use crate::auth;
+use crate::errors::{ContextualError, ContextualErrorKind};
use crate::themes;
/// Possible characters for random routes
@@ -76,15 +77,13 @@ 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<(String, String), String> {
+fn parse_auth(src: &str) -> Result<(String, String), ContextualError> {
let mut split = src.splitn(2, ':');
let username = match split.next() {
Some(username) => username,
None => {
- return Err(
- "Invalid credentials string, expected format is username:password".to_owned(),
- )
+ return Err(ContextualError::new(ContextualErrorKind::InvalidAuthFormat));
}
};
@@ -92,9 +91,7 @@ fn parse_auth(src: &str) -> Result<(String, String), String> {
// This allows empty passwords, as the spec does not forbid it
Some(password) => password,
None => {
- return Err(
- "Invalid credentials string, expected format is username:password".to_owned(),
- )
+ return Err(ContextualError::new(ContextualErrorKind::InvalidAuthFormat));
}
};
@@ -102,7 +99,9 @@ fn parse_auth(src: &str) -> Result<(String, String), String> {
// After 255 characters, Windows will truncate the value.
// As for the username, the spec does not mention a limit in length
if password.len() > 255 {
- return Err("Password length cannot exceed 255 characters".to_owned());
+ return Err(ContextualError::new(
+ ContextualErrorKind::PasswordTooLongError,
+ ));
}
Ok((username.to_owned(), password.to_owned()))
diff --git a/src/auth.rs b/src/auth.rs
index 10e7a4a..8cedaae 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -2,12 +2,9 @@ use actix_web::http::header;
use actix_web::middleware::{Middleware, Response};
use actix_web::{HttpRequest, HttpResponse, Result};
-pub struct Auth;
+use crate::errors::{ContextualError, ContextualErrorKind};
-/// HTTP Basic authentication errors
-pub enum BasicAuthError {
- Base64DecodeError,
-}
+pub struct Auth;
#[derive(Clone, Debug)]
/// HTTP Basic authentication parameters
@@ -19,9 +16,17 @@ pub struct BasicAuthParams {
/// Decode a HTTP basic auth string into a tuple of username and password.
pub fn parse_basic_auth(
authorization_header: &header::HeaderValue,
-) -> Result<BasicAuthParams, BasicAuthError> {
- let basic_removed = authorization_header.to_str().unwrap().replace("Basic ", "");
- let decoded = base64::decode(&basic_removed).map_err(|_| BasicAuthError::Base64DecodeError)?;
+) -> Result<BasicAuthParams, ContextualError> {
+ let basic_removed = authorization_header
+ .to_str()
+ .map_err(|e| {
+ ContextualError::new(ContextualErrorKind::ParseError(
+ "HTTP authentication header".to_string(),
+ e.to_string(),
+ ))
+ })?
+ .replace("Basic ", "");
+ let decoded = base64::decode(&basic_removed).map_err(ContextualErrorKind::Base64DecodeError)?;
let decoded_str = String::from_utf8_lossy(&decoded);
let credentials: Vec<&str> = decoded_str.splitn(2, ':').collect();
@@ -44,11 +49,11 @@ impl Middleware<crate::MiniserveConfig> for Auth {
if let Some(auth_headers) = req.headers().get(header::AUTHORIZATION) {
let auth_req = match parse_basic_auth(auth_headers) {
Ok(auth_req) => auth_req,
- Err(BasicAuthError::Base64DecodeError) => {
+ Err(err) => {
return Ok(Response::Done(HttpResponse::BadRequest().body(format!(
- "Error decoding basic auth base64: '{}'",
- auth_headers.to_str().unwrap()
- ))));
+ "An error occured during HTTP authentication\ncaused by: {}",
+ err
+ ))))
}
};
if auth_req.username != required_auth.username
diff --git a/src/errors.rs b/src/errors.rs
index 21d9e07..f2d185e 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,85 +1,69 @@
use failure::{Backtrace, Context, Fail};
use std::fmt::{self, Debug, Display};
-/// Kinds of errors which might happen during file upload
#[derive(Debug, Fail)]
-pub enum FileUploadErrorKind {
- /// This error will occur when file overriding is off and a file with same name already exists
- #[fail(display = "File with this name already exists")]
- FileExist,
- /// This error will occur when the server fails to process the HTTP header during file upload
- #[fail(display = "Failed to parse incoming request")]
- ParseError,
- /// This error will occur when we fail to process the multipart request
- #[fail(display = "Failed to process multipart request")]
+pub enum ContextualErrorKind {
+ /// Fully customized errors, not inheriting from any error
+ #[fail(display = "{}", _0)]
+ CustomError(String),
+
+ /// Any kind of IO errors
+ #[fail(display = "{}\ncaused by: {}", _0, _1)]
+ IOError(String, std::io::Error),
+
+ /// MultipartError, which might occur during file upload, when processing the multipart request fails
+ #[fail(display = "Failed to process multipart request\ncaused by: {}", _0)]
MultipartError(actix_web::error::MultipartError),
- /// This error may occur when trying to write the incoming file to disk
- #[fail(display = "Failed to create or write to file")]
- IOError(std::io::Error),
- /// This error will occur when we he have insuffictent permissions to create new file
- #[fail(display = "Insufficient permissions to create file")]
- InsufficientPermissions,
-}
-/// Kinds of errors which might happen during the generation of an archive
-#[derive(Debug, Fail)]
-pub enum CompressionErrorKind {
- /// This error will occur if the directory name could not be retrieved from the path
- /// See https://doc.rust-lang.org/std/path/struct.Path.html#method.file_name
- #[fail(display = "Invalid path: directory name terminates in \"..\"")]
- InvalidDirectoryName,
- /// This error will occur when trying to convert an OSString into a String, if the path
- /// contains invalid UTF-8 characters
- /// See https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_str
- #[fail(display = "Invalid path: directory name contains invalid UTF-8 characters")]
- InvalidUTF8DirectoryName,
- /// This error might occur while building a TAR archive, or while writing the termination sections
- /// See https://docs.rs/tar/0.4.22/tar/struct.Builder.html#method.append_dir_all
- /// and https://docs.rs/tar/0.4.22/tar/struct.Builder.html#method.into_inner
- #[fail(display = "Failed to create the TAR archive: {}", message)]
- TarBuildingError { message: String },
- /// This error might occur while building a GZIP archive, or while writing the GZIP trailer
- /// See https://docs.rs/libflate/0.1.21/libflate/gzip/struct.Encoder.html#method.finish
- #[fail(display = "Failed to create the GZIP archive: {}", message)]
- GZipBuildingError { message: String },
-}
+ /// This error might occur when decoding the HTTP authentication header.
+ #[fail(
+ display = "Failed to decode HTTP authentication header\ncaused by: {}",
+ _0
+ )]
+ Base64DecodeError(base64::DecodeError),
-/// Prints the full chain of error, up to the root cause.
-/// If RUST_BACKTRACE is set to 1, also prints the backtrace for each error
-pub fn print_error_chain(err: CompressionError) {
- log::error!("{}", &err);
- print_backtrace(&err);
- for cause in Fail::iter_causes(&err) {
- log::error!("caused by: {}", cause);
- print_backtrace(cause);
- }
-}
+ /// Any error related to an invalid path (failed to retrieve entry name, unexpected entry type, etc)
+ #[fail(display = "Invalid path\ncaused by: {}", _0)]
+ InvalidPathError(String),
-/// Prints the backtrace of an error
-/// RUST_BACKTRACE needs to be set to 1 to display the backtrace
-fn print_backtrace(err: &dyn Fail) {
- if let Some(backtrace) = err.backtrace() {
- let backtrace = backtrace.to_string();
- if backtrace != "" {
- log::error!("{}", backtrace);
- }
- }
+ /// This error might occur if the HTTP credential string does not respect the expected format
+ #[fail(display = "Invalid format for credentials string. Expected is username:format")]
+ InvalidAuthFormat,
+
+ /// This error might occur if the HTTP auth password exceeds 255 characters
+ #[fail(display = "HTTP password length exceeds 255 characters")]
+ PasswordTooLongError,
+
+ /// This error might occur if the user has unsufficient permissions to create an entry in a given directory
+ #[fail(display = "Insufficient permissions to create file in {}", _0)]
+ InsufficientPermissionsError(String),
+
+ /// Any error related to parsing.
+ #[fail(display = "Failed to parse {}\ncaused by: {}", _0, _1)]
+ ParseError(String, String),
+
+ /// This error might occur when the creation of an archive fails
+ #[fail(
+ display = "An error occured while creating the {}\ncaused by: {}",
+ _0, _1
+ )]
+ ArchiveCreationError(String, Box<ContextualError>),
}
/// Based on https://boats.gitlab.io/failure/error-errorkind.html
-pub struct CompressionError {
- inner: Context<CompressionErrorKind>,
+pub struct ContextualError {
+ inner: Context<ContextualErrorKind>,
}
-impl CompressionError {
- pub fn new(kind: CompressionErrorKind) -> CompressionError {
- CompressionError {
+impl ContextualError {
+ pub fn new(kind: ContextualErrorKind) -> ContextualError {
+ ContextualError {
inner: Context::new(kind),
}
}
}
-impl Fail for CompressionError {
+impl Fail for ContextualError {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
@@ -89,28 +73,35 @@ impl Fail for CompressionError {
}
}
-impl Display for CompressionError {
+impl Display for ContextualError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner, f)
}
}
-impl Debug for CompressionError {
+impl Debug for ContextualError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(&self.inner, f)
}
}
-impl From<Context<CompressionErrorKind>> for CompressionError {
- fn from(inner: Context<CompressionErrorKind>) -> CompressionError {
- CompressionError { inner }
+impl From<Context<ContextualErrorKind>> for ContextualError {
+ fn from(inner: Context<ContextualErrorKind>) -> ContextualError {
+ ContextualError { inner }
}
}
-impl From<CompressionErrorKind> for CompressionError {
- fn from(kind: CompressionErrorKind) -> CompressionError {
- CompressionError {
+impl From<ContextualErrorKind> for ContextualError {
+ fn from(kind: ContextualErrorKind) -> ContextualError {
+ ContextualError {
inner: Context::new(kind),
}
}
}
+
+/// This allows to create CustomErrors more simply
+impl From<String> for ContextualError {
+ fn from(msg: String) -> ContextualError {
+ ContextualError::new(ContextualErrorKind::CustomError(msg))
+ }
+}
diff --git a/src/file_upload.rs b/src/file_upload.rs
index 534083c..960f831 100644
--- a/src/file_upload.rs
+++ b/src/file_upload.rs
@@ -1,5 +1,3 @@
-use crate::errors::FileUploadErrorKind;
-use crate::renderer::file_upload_error;
use actix_web::{
dev, http::header, multipart, FromRequest, FutureResponse, HttpMessage, HttpRequest,
HttpResponse, Query,
@@ -12,6 +10,9 @@ use std::{
path::{Component, PathBuf},
};
+use crate::errors::ContextualErrorKind;
+use crate::renderer::file_upload_error;
+
/// Query parameters
#[derive(Debug, Deserialize)]
struct QueryParameters {
@@ -23,24 +24,32 @@ fn save_file(
field: multipart::Field<dev::Payload>,
file_path: PathBuf,
overwrite_files: bool,
-) -> Box<Future<Item = i64, Error = FileUploadErrorKind>> {
+) -> Box<Future<Item = i64, Error = ContextualErrorKind>> {
if !overwrite_files && file_path.exists() {
- return Box::new(future::err(FileUploadErrorKind::FileExist));
+ return Box::new(future::err(ContextualErrorKind::CustomError(
+ "File already exists, and the overwrite_files option has not been set".to_string(),
+ )));
}
+
let mut file = match std::fs::File::create(file_path) {
Ok(file) => file,
Err(e) => {
- return Box::new(future::err(FileUploadErrorKind::IOError(e)));
+ return Box::new(future::err(ContextualErrorKind::IOError(
+ "Failed to create file".to_string(),
+ e,
+ )));
}
};
Box::new(
field
- .map_err(FileUploadErrorKind::MultipartError)
+ .map_err(ContextualErrorKind::MultipartError)
.fold(0i64, move |acc, bytes| {
let rt = file
.write_all(bytes.as_ref())
.map(|_| acc + bytes.len() as i64)
- .map_err(FileUploadErrorKind::IOError);
+ .map_err(|e| {
+ ContextualErrorKind::IOError("Failed to write to file".to_string(), e)
+ });
future::result(rt)
}),
)
@@ -51,44 +60,56 @@ fn handle_multipart(
item: multipart::MultipartItem<dev::Payload>,
mut file_path: PathBuf,
overwrite_files: bool,
-) -> Box<Stream<Item = i64, Error = FileUploadErrorKind>> {
+) -> Box<Stream<Item = i64, Error = ContextualErrorKind>> {
match item {
multipart::MultipartItem::Field(field) => {
let filename = field
.headers()
.get(header::CONTENT_DISPOSITION)
- .ok_or(FileUploadErrorKind::ParseError)
+ .ok_or(ContextualErrorKind::ParseError)
.and_then(|cd| {
header::ContentDisposition::from_raw(cd)
- .map_err(|_| FileUploadErrorKind::ParseError)
+ .map_err(|_| ContextualErrorKind::ParseError)
})
.and_then(|content_disposition| {
content_disposition
.get_filename()
- .ok_or(FileUploadErrorKind::ParseError)
+ .ok_or(ContextualErrorKind::ParseError)
.map(String::from)
});
- let err = |e: FileUploadErrorKind| Box::new(future::err(e).into_stream());
+ let err = |e: ContextualErrorKind| Box::new(future::err(e).into_stream());
match filename {
Ok(f) => {
match fs::metadata(&file_path) {
Ok(metadata) => {
- if !metadata.is_dir() || metadata.permissions().readonly() {
- return err(FileUploadErrorKind::InsufficientPermissions);
+ if !metadata.is_dir() {
+ return err(ContextualErrorKind::InvalidPathError(format!(
+ "cannot upload file to {}, since it's not a directory",
+ &file_path.display()
+ )));
+ } else if metadata.permissions().readonly() {
+ return err(ContextualErrorKind::InsufficientPermissionsError(
+ file_path.display().to_string(),
+ ));
}
}
Err(_) => {
- return err(FileUploadErrorKind::InsufficientPermissions);
+ return err(ContextualErrorKind::InsufficientPermissionsError(
+ file_path.display().to_string(),
+ ));
}
}
file_path = file_path.join(f);
Box::new(save_file(field, file_path, overwrite_files).into_stream())
}
- Err(e) => err(e),
+ Err(e) => err(e(
+ "HTTP header".to_string(),
+ "Failed to retrieve the name of the file to upload".to_string(),
+ )),
}
}
multipart::MultipartItem::Nested(mp) => Box::new(
- mp.map_err(FileUploadErrorKind::MultipartError)
+ mp.map_err(ContextualErrorKind::MultipartError)
.map(move |item| handle_multipart(item, file_path.clone(), overwrite_files))
.flatten(),
),
@@ -134,7 +155,7 @@ pub fn upload_file(req: &HttpRequest<crate::MiniserveConfig>) -> FutureResponse<
let overwrite_files = req.state().overwrite_files;
Box::new(
req.multipart()
- .map_err(FileUploadErrorKind::MultipartError)
+ .map_err(ContextualErrorKind::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 42a43b5..fec3913 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,6 +19,8 @@ mod listing;
mod renderer;
mod themes;
+use crate::errors::{ContextualError, ContextualErrorKind};
+
#[derive(Clone)]
/// Configuration of the Miniserve application
pub struct MiniserveConfig {
@@ -57,10 +59,18 @@ pub struct MiniserveConfig {
}
fn main() {
+ match run() {
+ Ok(()) => (),
+ Err(e) => eprintln!("\n{}\n", Paint::red(e)),
+ }
+}
+
+fn run() -> Result<(), ContextualError> {
if cfg!(windows) && !Paint::enable_windows_ascii() {
Paint::disable();
}
+ let sys = actix::System::new("miniserve");
let miniserve_config = args::parse_args();
let _ = if miniserve_config.verbose {
@@ -73,16 +83,20 @@ fn main() {
&& miniserve_config
.path
.symlink_metadata()
- .expect("Can't get file metadata")
+ .map_err(|e| {
+ ContextualError::new(ContextualErrorKind::IOError(
+ "Failed to retrieve symlink's metadata".to_string(),
+ e,
+ ))
+ })?
.file_type()
.is_symlink()
{
- log::error!("The no-symlinks option cannot be used with a symlink path");
- return;
+ return Err(ContextualError::from(
+ "The no-symlinks option cannot be used with a symlink path".to_string(),
+ ));
}
- let sys = actix::System::new("miniserve");
-
let inside_config = miniserve_config.clone();
let interfaces = miniserve_config
@@ -102,7 +116,12 @@ fn main() {
})
.collect::<Vec<String>>();
- let canon_path = miniserve_config.path.canonicalize().unwrap();
+ let canon_path = miniserve_config.path.canonicalize().map_err(|e| {
+ ContextualError::new(ContextualErrorKind::IOError(
+ "Failed to resolve path to be served".to_string(),
+ e,
+ ))
+ })?;
let path_string = canon_path.to_string_lossy();
println!(
@@ -116,10 +135,20 @@ fn main() {
" Invoke with -h|--help to see options or invoke as `miniserve .` to hide this advice."
);
print!("Starting server in ");
- io::stdout().flush().unwrap();
+ io::stdout().flush().map_err(|e| {
+ ContextualError::new(ContextualErrorKind::IOError(
+ "Failed to write data".to_string(),
+ e,
+ ))
+ })?;
for c in "3… 2… 1… \n".chars() {
print!("{}", c);
- io::stdout().flush().unwrap();
+ io::stdout().flush().map_err(|e| {
+ ContextualError::new(ContextualErrorKind::IOError(
+ "Failed to write data".to_string(),
+ e,
+ ))
+ })?;
thread::sleep(Duration::from_millis(500));
}
}
@@ -148,12 +177,6 @@ fn main() {
));
}
}
- println!(
- "Serving path {path} at {addresses}",
- path = Color::Yellow.paint(path_string).bold(),
- addresses = addresses,
- );
- println!("\nQuit by pressing CTRL-C");
let socket_addresses = interfaces
.iter()
@@ -167,10 +190,18 @@ fn main() {
})
.collect::<Result<Vec<SocketAddr>, _>>();
- // 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
- let socket_addresses = socket_addresses.expect("Failed to parse string as socket address");
+ let socket_addresses = match socket_addresses {
+ Ok(addresses) => addresses,
+ Err(e) => {
+ // 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(
+ "string as socket address".to_string(),
+ e.to_string(),
+ )));
+ }
+ };
server::new(move || {
App::with_state(inside_config.clone())
@@ -179,10 +210,26 @@ fn main() {
.configure(configure_app)
})
.bind(socket_addresses.as_slice())
- .expect("Couldn't bind server")
+ .map_err(|e| {
+ ContextualError::new(ContextualErrorKind::IOError(
+ "Failed to bind server".to_string(),
+ e,
+ ))
+ })?
.shutdown_timeout(0)
.start();
+
+ println!(
+ "Serving path {path} at {addresses}",
+ path = Color::Yellow.paint(path_string).bold(),
+ addresses = addresses,
+ );
+
+ println!("\nQuit by pressing CTRL-C");
+
let _ = sys.run();
+
+ Ok(())
}
/// Configures the Actix application
@@ -204,7 +251,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
let u_r = upload_route.clone();
Some(
fs::StaticFiles::new(path)
- .expect("Couldn't create path")
+ .expect("Failed to setup static file handler")
.show_files_listing()
.files_listing_renderer(move |dir, req| {
listing::directory_listing(