aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/archive.rs71
-rw-r--r--src/args.rs32
-rw-r--r--src/auth.rs4
-rw-r--r--src/errors.rs69
-rw-r--r--src/file_op.rs66
-rw-r--r--src/listing.rs4
-rw-r--r--src/main.rs30
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 {