diff options
author | Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com> | 2021-05-12 11:53:29 +0000 |
---|---|---|
committer | Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com> | 2021-08-30 17:48:49 +0000 |
commit | 79963079fae23668cb0694834faa5d43d0d99f3b (patch) | |
tree | 033bce5f4ddbe68a639229598f1fca8737cf8257 /src/errors.rs | |
parent | Bump deps (diff) | |
download | miniserve-79963079fae23668cb0694834faa5d43d0d99f3b.tar.gz miniserve-79963079fae23668cb0694834faa5d43d0d99f3b.zip |
Fix clippy::too_many_arguments and rework error ..
... page rendering
Too many arguments are moved around and many of them are already stored
in MiniserveConfig. Many of these are used to render error pages.
To fix this issue, it was necessary to rework error page rendering:
1. Implement `ResponseError` for `ContextualError` so that it can be
returned from service handlers as is and will then be automatically
logged to the console and converted into an error response.
2. At service handler level, all error responses are now rendered as
plain text.
3. 'error_page_middleware' is now responsible for the rendering of the
final error page from plain text reponses.
Signed-off-by: Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>
Diffstat (limited to '')
-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); |