aboutsummaryrefslogtreecommitdiffstats
path: root/src/errors.rs
diff options
context:
space:
mode:
authorAli MJ Al-Nasrawy <alimjalnasrawy@gmail.com>2021-05-12 11:53:29 +0000
committerAli MJ Al-Nasrawy <alimjalnasrawy@gmail.com>2021-08-30 17:48:49 +0000
commit79963079fae23668cb0694834faa5d43d0d99f3b (patch)
tree033bce5f4ddbe68a639229598f1fca8737cf8257 /src/errors.rs
parentBump deps (diff)
downloadminiserve-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 '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);