diff options
Diffstat (limited to 'src/errors.rs')
-rw-r--r-- | src/errors.rs | 97 |
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); |