aboutsummaryrefslogtreecommitdiffstats
path: root/src/errors.rs
diff options
context:
space:
mode:
authorSven-Hendrik Haase <svenstaro@gmail.com>2021-08-31 12:07:37 +0000
committerGitHub <noreply@github.com>2021-08-31 12:07:37 +0000
commit1e4230e8c4b462611c680f28c9fc8c80474dbca4 (patch)
tree9c1b8b2a7c472d939ddf7327e597fcc4213a3451 /src/errors.rs
parentBump deps (diff)
parentUse selected theme in error page (diff)
downloadminiserve-1e4230e8c4b462611c680f28c9fc8c80474dbca4.tar.gz
miniserve-1e4230e8c4b462611c680f28c9fc8c80474dbca4.zip
Merge pull request #529 from aliemjay/src-refactor
[Refactor] Fix clippy::too_many_arguments and rework error page rendering
Diffstat (limited to 'src/errors.rs')
-rw-r--r--src/errors.rs97
1 files changed, 94 insertions, 3 deletions
diff --git a/src/errors.rs b/src/errors.rs
index f079657..25d0529 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,3 +1,11 @@
+use crate::{renderer::render_error, MiniserveConfig};
+use actix_web::{
+ body::AnyBody,
+ dev::{ResponseHead, Service, ServiceRequest, ServiceResponse},
+ http::{header, StatusCode},
+ HttpRequest, HttpResponse, ResponseError,
+};
+use futures::prelude::*;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -50,9 +58,9 @@ pub enum ContextualError {
#[error("{0}")]
ArchiveCreationDetailError(String),
- /// Might occur when the HTTP authentication fails
- #[error("An error occured during HTTP authentication\ncaused by: {0}")]
- HttpAuthenticationError(Box<ContextualError>),
+ /// Might occur when the HTTP credentials are not provided
+ #[error("Access requires HTTP authentication")]
+ RequireHttpCredentials,
/// Might occur when the HTTP credentials are not correct
#[error("Invalid credentials for HTTP authentication")]
@@ -76,6 +84,89 @@ Please set an explicit serve path like: `miniserve /my/path`")]
NoSymlinksOptionWithSymlinkServePath(String),
}
+impl ResponseError for ContextualError {
+ fn status_code(&self) -> StatusCode {
+ match self {
+ Self::ArchiveCreationError(_, err) => err.status_code(),
+ Self::RouteNotFoundError(_) => StatusCode::NOT_FOUND,
+ Self::InsufficientPermissionsError(_) => StatusCode::FORBIDDEN,
+ Self::InvalidHttpCredentials | Self::RequireHttpCredentials => StatusCode::UNAUTHORIZED,
+ Self::InvalidHttpRequestError(_) => StatusCode::BAD_REQUEST,
+ _ => StatusCode::INTERNAL_SERVER_ERROR,
+ }
+ }
+
+ fn error_response(&self) -> HttpResponse {
+ if let Self::RequireHttpCredentials = self {
+ } else {
+ log_error_chain(self.to_string());
+ }
+
+ let mut resp = HttpResponse::build(self.status_code());
+ if let Self::RequireHttpCredentials | Self::InvalidHttpCredentials = self {
+ resp.append_header((
+ header::WWW_AUTHENTICATE,
+ header::HeaderValue::from_static("Basic realm=\"miniserve\""),
+ ));
+ }
+
+ resp.content_type("text/plain; charset=utf-8")
+ .body(self.to_string())
+ }
+}
+
+/// Middleware to convert plain-text error responses to user-friendly web pages
+pub fn error_page_middleware<S>(
+ req: ServiceRequest,
+ srv: &S,
+) -> impl Future<Output = actix_web::Result<ServiceResponse>> + 'static
+where
+ S: Service<ServiceRequest, Response = ServiceResponse, Error = actix_web::Error>,
+ S::Future: 'static,
+{
+ let fut = srv.call(req);
+
+ async {
+ let res = fut.await?;
+
+ if (res.status().is_client_error() || res.status().is_server_error())
+ && res.headers().get(header::CONTENT_TYPE).map(AsRef::as_ref)
+ == Some(b"text/plain; charset=utf-8")
+ {
+ let req = res.request().clone();
+ Ok(res.map_body(|head, body| map_error_page(&req, head, body)))
+ } else {
+ Ok(res)
+ }
+ }
+}
+
+fn map_error_page(req: &HttpRequest, head: &mut ResponseHead, body: AnyBody) -> AnyBody {
+ let error_msg = match &body {
+ AnyBody::Bytes(bytes) => match std::str::from_utf8(bytes) {
+ Ok(msg) => msg,
+ _ => return body,
+ },
+ _ => return body,
+ };
+
+ let conf = req.app_data::<MiniserveConfig>().unwrap();
+ let return_address = req
+ .headers()
+ .get(header::REFERER)
+ .and_then(|h| h.to_str().ok())
+ .unwrap_or("/");
+
+ head.headers.insert(
+ header::CONTENT_TYPE,
+ header::HeaderValue::from_static("text/html; charset=utf-8"),
+ );
+
+ render_error(error_msg, head.status, conf, return_address)
+ .into_string()
+ .into()
+}
+
pub fn log_error_chain(description: String) {
for cause in description.lines() {
log::error!("{}", cause);