diff --git a/src/handling.rs b/src/handling.rs index f869605..0ab3979 100644 --- a/src/handling.rs +++ b/src/handling.rs @@ -59,41 +59,33 @@ impl Handler { HandlerCatchUnwind::new(fut_handle).await .unwrap_or_else(|err| { error!("Handler failed: {:?}", err); - Response::server_error("").unwrap() + Response::temporary_failure("") }) }, Self::StaticHandler(response) => { - let body = response.as_ref(); - match body { - None => Response::new(response.header().clone()), - Some(Body::Bytes(bytes)) => { - Response::new(response.header().clone()) - .with_body(bytes.clone()) - }, + match &response.body { + None => Response::new(response.status, &response.meta), + Some(Body::Bytes(bytes)) => Response::success(&response.meta, bytes.clone()), _ => { error!(concat!( "Cannot construct a static handler with a reader-based body! ", " We're sending a response so that the client doesn't crash, but", " given that this is a release build you should really fix this." )); - Response::server_error( + Response::permanent_failure( "Very bad server error, go tell the sysadmin to look at the logs." - ).unwrap() + ) } } }, #[cfg(feature = "serve_dir")] Self::FilesHandler(path) => { - let resp = if path.is_dir() { + if path.is_dir() { crate::util::serve_dir(path, request.trailing_segments()).await } else { let mime = crate::util::guess_mime_from_path(&path); crate::util::serve_file(path, &mime).await - }; - resp.unwrap_or_else(|e| { - warn!("Unexpected error serving from {}: {:?}", path.display(), e); - Response::server_error("").unwrap() - }) + } }, } } @@ -204,7 +196,7 @@ impl Future for HandlerCatchUnwind { Ok(res) => res, Err(e) => { error!("Handler panic! {:?}", e); - Poll::Ready(Response::server_error("")) + Poll::Ready(Ok(Response::temporary_failure(""))) } } } diff --git a/src/lib.rs b/src/lib.rs index 4a4756b..face42d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ use std::{ }; #[cfg(any(feature = "gemini_srv", feature = "user_management"))] use std::path::PathBuf; +#[cfg(feature="ratelimiting")] +use std::net::IpAddr; use tokio::{ io, io::BufReader, @@ -217,15 +219,12 @@ impl ServerInner { Ok(()) } - async fn send_response(&self, mut response: Response, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { - let maybe_body = response.take_body(); - let header = response.header(); - + async fn send_response(&self, response: Response, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { let use_complex_timeout = - header.status.is_success() && - maybe_body.is_some() && - header.meta.as_str() != "text/plain" && - header.meta.as_str() != "text/gemini" && + response.is_success() && + response.body.is_some() && + response.meta != "text/plain" && + response.meta != "text/gemini" && self.complex_timeout.is_some(); let send_general_timeout; @@ -244,13 +243,13 @@ impl ServerInner { opt_timeout(send_general_timeout, async { // Send the header - opt_timeout(send_header_timeout, send_response_header(response.header(), stream)) + opt_timeout(send_header_timeout, send_response_header(&response, stream)) .await .context("Timed out while sending response header")? .context("Failed to write response header")?; // Send the body - opt_timeout(send_body_timeout, maybe_send_response_body(maybe_body, stream)) + opt_timeout(send_body_timeout, send_response_body(response.body, stream)) .await .context("Timed out while sending response body")? .context("Failed to write response body")?; @@ -267,10 +266,7 @@ impl ServerInner { fn check_rate_limits(&self, addr: IpAddr, req: &Request) -> Option { if let Some((_, limiter)) = self.rate_limits.match_request(req) { if let Err(when) = limiter.check_key(addr) { - return Some(Response::new(ResponseHeader { - status: Status::SLOW_DOWN, - meta: Meta::new(when.as_secs().to_string()).unwrap() - })) + return Some(Response::slow_down(when.as_secs())) } } None @@ -682,11 +678,19 @@ impl Default for Server { } } -async fn send_response_header(header: &ResponseHeader, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { +async fn send_response_header(response: &Response, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { + + let meta = if response.meta.len() > 1024 { + warn!("Attempted to send response with META exceeding maximum length, truncating"); + &response.meta[..1024] + } else { + &response.meta[..] + }; + let header = format!( "{status} {meta}\r\n", - status = header.status.code(), - meta = header.meta.as_str(), + status = response.status, + meta = meta, ); stream.write_all(header.as_bytes()).await?; @@ -695,22 +699,17 @@ async fn send_response_header(header: &ResponseHeader, stream: &mut (impl AsyncW Ok(()) } -async fn maybe_send_response_body(maybe_body: Option, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { - if let Some(body) = maybe_body { - send_response_body(body, stream).await?; +async fn send_response_body(mut body: Option, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { + match &mut body { + Some(Body::Bytes(ref bytes)) => stream.write_all(&bytes).await?, + Some(Body::Reader(ref mut reader)) => { io::copy(reader, stream).await?; }, + None => {}, } - Ok(()) -} - -async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { - match body { - Body::Bytes(bytes) => stream.write_all(&bytes).await?, - Body::Reader(mut reader) => { io::copy(&mut reader, stream).await?; }, + if body.is_some() { + stream.flush().await?; } - stream.flush().await?; - Ok(()) } diff --git a/src/types.rs b/src/types.rs index 385fa8c..6fb59a0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,17 +1,8 @@ pub use uriparse::URIReference; -mod meta; -pub use self::meta::Meta; - mod request; pub use request::Request; -mod response_header; -pub use response_header::ResponseHeader; - -mod status; -pub use status::{Status, StatusCategory}; - mod response; pub use response::Response; diff --git a/src/types/meta.rs b/src/types/meta.rs deleted file mode 100644 index 80477fc..0000000 --- a/src/types/meta.rs +++ /dev/null @@ -1,130 +0,0 @@ -use anyhow::*; -use crate::util::Cowy; - - -#[derive(Debug,Clone,PartialEq,Eq,Default)] -pub struct Meta(String); - -impl Meta { - pub const MAX_LEN: usize = 1024; - - /// Creates a new "Meta" string. - /// Fails if `meta` contains `\n`. - pub fn new(meta: impl Cowy) -> Result { - ensure!(!meta.as_ref().contains('\n'), "Meta must not contain newlines"); - ensure!(meta.as_ref().len() <= Self::MAX_LEN, "Meta must not exceed {} bytes", Self::MAX_LEN); - - Ok(Self(meta.into())) - } - - /// Creates a new "Meta" string. - /// Truncates `meta` to before: - /// - the first occurrence of `\n` - /// - the character that makes `meta` exceed `Meta::MAX_LEN` - pub fn new_lossy(meta: impl Cowy) -> Self { - let meta = meta.as_ref(); - let truncate_pos = meta.char_indices().position(|(i, ch)| { - let is_newline = ch == '\n'; - let exceeds_limit = (i + ch.len_utf8()) > Self::MAX_LEN; - - is_newline || exceeds_limit - }); - - let meta: String = match truncate_pos { - None => meta.into(), - Some(truncate_pos) => meta.get(..truncate_pos).unwrap().into(), - }; - - Self(meta) - } - - pub fn empty() -> Self { - Self::default() - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::iter::repeat; - - #[test] - fn new_rejects_newlines() { - let meta = "foo\nbar"; - let meta = Meta::new(meta); - - assert!(meta.is_err()); - } - - #[test] - fn new_accepts_max_len() { - let meta: String = repeat('x').take(Meta::MAX_LEN).collect(); - let meta = Meta::new(meta); - - assert!(meta.is_ok()); - } - - #[test] - fn new_rejects_exceeding_max_len() { - let meta: String = repeat('x').take(Meta::MAX_LEN + 1).collect(); - let meta = Meta::new(meta); - - assert!(meta.is_err()); - } - - #[test] - fn new_lossy_truncates() { - let meta = "foo\r\nbar\nquux"; - let meta = Meta::new_lossy(meta); - - assert_eq!(meta.as_str(), "foo\r"); - } - - #[test] - fn new_lossy_no_truncate() { - let meta = "foo bar\r"; - let meta = Meta::new_lossy(meta); - - assert_eq!(meta.as_str(), "foo bar\r"); - } - - #[test] - fn new_lossy_empty() { - let meta = ""; - let meta = Meta::new_lossy(meta); - - assert_eq!(meta.as_str(), ""); - } - - #[test] - fn new_lossy_truncates_to_empty() { - let meta = "\n\n\n"; - let meta = Meta::new_lossy(meta); - - assert_eq!(meta.as_str(), ""); - } - - #[test] - fn new_lossy_truncates_to_max_len() { - let meta: String = repeat('x').take(Meta::MAX_LEN + 1).collect(); - let meta = Meta::new_lossy(meta); - - assert_eq!(meta.as_str().len(), Meta::MAX_LEN); - } - - #[test] - fn new_lossy_truncates_multi_byte_sequences() { - let mut meta: String = repeat('x').take(Meta::MAX_LEN - 1).collect(); - meta.push('🦀'); - - assert_eq!(meta.len(), Meta::MAX_LEN + 3); - - let meta = Meta::new_lossy(meta); - - assert_eq!(meta.as_str().len(), Meta::MAX_LEN - 1); - } -} diff --git a/src/types/response.rs b/src/types/response.rs index 1baf59e..4fb331d 100644 --- a/src/types/response.rs +++ b/src/types/response.rs @@ -1,101 +1,148 @@ -use std::convert::TryInto; use std::borrow::Borrow; -use anyhow::*; -use uriparse::URIReference; -use crate::types::{ResponseHeader, Body, Document}; -use crate::util::Cowy; +use crate::types::{Body, Document}; pub struct Response { - header: ResponseHeader, - body: Option, + pub status: u8, + pub meta: String, + pub body: Option, } impl Response { - pub const fn new(header: ResponseHeader) -> Self { + + /// Create a response with a given status and meta + pub fn new(status: u8, meta: impl ToString) -> Self { Self { - header, + status, + meta: meta.to_string(), body: None, } } - #[deprecated( - since = "0.4.0", - note = "Deprecated in favor of Response::success_gemini() or Document::into()" - )] - pub fn document(document: impl Borrow) -> Self { - Self::success_gemini(document) + /// Create a INPUT (10) response with a given prompt + /// + /// Use [`Response::sensitive_input()`] for collecting any sensitive input, as input + /// collected by this request may be logged. + pub fn input(prompt: impl ToString) -> Self { + Self::new(10, prompt) } - pub fn input(prompt: impl Cowy) -> Result { - let header = ResponseHeader::input(prompt)?; - Ok(Self::new(header)) + /// Create a SENSITIVE INPUT (11) response with a given prompt + /// + /// See also [`Response::input()`] for unsensitive inputs + pub fn sensitive_input(prompt: impl ToString) -> Self { + Self::new(11, prompt) } - pub fn input_lossy(prompt: impl Cowy) -> Self { - let header = ResponseHeader::input_lossy(prompt); - Self::new(header) - } - - pub fn redirect_temporary_lossy<'a>(location: impl TryInto>) -> Self { - let header = ResponseHeader::redirect_temporary_lossy(location); - Self::new(header) - } - - /// Create a successful response with a given body and MIME + /// Create a SUCCESS (20) response with a given body and MIME pub fn success(mime: impl ToString, body: impl Into) -> Self { Self { - header: ResponseHeader::success(mime), + status: 20, + meta: mime.to_string(), body: Some(body.into()), } } - /// Create a successful response with a `text/gemini` MIME + /// Create a SUCCESS (20) response with a `text/gemini` MIME pub fn success_gemini(body: impl Into) -> Self { Self::success("text/gemini", body) } - /// Create a successful response with a `text/plain` MIME + /// Create a SUCCESS (20) response with a `text/plain` MIME pub fn success_plain(body: impl Into) -> Self { Self::success("text/plain", body) } - pub fn server_error(reason: impl Cowy) -> Result { - let header = ResponseHeader::server_error(reason)?; - Ok(Self::new(header)) + /// Create a REDIRECT - TEMPORARY (30) response with a destination + pub fn redirect_temporary(dest: impl ToString) -> Self { + Self::new(30, dest) } + /// Create a REDIRECT - PERMANENT (31) response with a destination + pub fn redirect_permanent(dest: impl ToString) -> Self { + Self::new(31, dest) + } + + /// Create a TEMPORARY FAILURE (40) response with a human readable error + pub fn temporary_failure(reason: impl ToString) -> Self { + Self::new(40, reason) + } + + /// Create a SERVER UNAVAILABLE (41) response with a human readable error + /// + /// Used to denote that the server is temporarily unavailable, for example due to + /// heavy load, or maintenance + pub fn server_unavailable(reason: impl ToString) -> Self { + Self::new(41, reason) + } + + /// Create a CGI ERROR (42) response with a human readable error + pub fn cgi_error(reason: impl ToString) -> Self { + Self::new(42, reason) + } + + /// Create a PROXY ERROR (43) response with a human readable error + pub fn proxy_error(reason: impl ToString) -> Self { + Self::new(43, reason) + } + + /// Create a SLOW DOWN (44) response with a wait time in seconds + /// + /// Used to denote that the user should wait a certain number of seconds before + /// sending another request, often for ratelimiting purposes + pub fn slow_down(wait: u64) -> Self { + Self::new(44, wait) + } + + /// Create a PERMANENT FAILURE (50) response with a human readable error + pub fn permanent_failure(reason: impl ToString) -> Self { + Self::new(50, reason) + } + + /// Create a NOT FOUND (51) response with no further information + /// + /// Essentially a 404 pub fn not_found() -> Self { - let header = ResponseHeader::not_found(); - Self::new(header) + Self::new(51, String::new()) } - pub fn bad_request_lossy(reason: impl Cowy) -> Self { - let header = ResponseHeader::bad_request_lossy(reason); - Self::new(header) + /// Create a GONE (52) response with a human readable error + /// + /// For when a resource used to be here, but never will be again + pub fn gone(reason: impl ToString) -> Self { + Self::new(52, reason) } - pub fn client_certificate_required() -> Self { - let header = ResponseHeader::client_certificate_required(); - Self::new(header) + /// Create a PROXY REQUEST REFUSED (53) response with a human readable error + /// + /// The server does not serve content on this domain + pub fn proxy_request_refused(reason: impl ToString) -> Self { + Self::new(53, reason) } - pub fn certificate_not_authorized() -> Self { - let header = ResponseHeader::certificate_not_authorized(); - Self::new(header) + /// Create a BAD REQUEST (59) response with a human readable error + pub fn bad_request(reason: impl ToString) -> Self { + Self::new(59, reason) } - pub fn with_body(mut self, body: impl Into) -> Self { - self.body = Some(body.into()); - self + /// Create a CLIENT CERTIFICATE REQUIRED (60) response with a human readable error + pub fn client_certificate_required(reason: impl ToString) -> Self { + Self::new(60, reason) } - pub const fn header(&self) -> &ResponseHeader { - &self.header + /// Create a CERTIFICATE NOT AUTHORIZED (61) response with a human readable error + pub fn certificate_not_authorized(reason: impl ToString) -> Self { + Self::new(61, reason) } - pub fn take_body(&mut self) -> Option { - self.body.take() + /// Create a CERTIFICATE NOT VALID (62) response with a human readable error + pub fn certificate_not_valid(reason: impl ToString) -> Self { + Self::new(62, reason) + } + + /// True if the response is a SUCCESS (10) response + pub const fn is_success(&self) -> bool { + self.status == 10 } } diff --git a/src/types/response_header.rs b/src/types/response_header.rs deleted file mode 100644 index 3e214d1..0000000 --- a/src/types/response_header.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::convert::TryInto; - -use anyhow::*; -use uriparse::URIReference; -use crate::util::Cowy; -use crate::types::{Status, Meta}; - -#[derive(Debug,Clone)] -pub struct ResponseHeader { - pub status: Status, - pub meta: Meta, -} - -impl ResponseHeader { - pub fn input(prompt: impl Cowy) -> Result { - Ok(Self { - status: Status::INPUT, - meta: Meta::new(prompt).context("Invalid input prompt")?, - }) - } - - pub fn input_lossy(prompt: impl Cowy) -> Self { - Self { - status: Status::INPUT, - meta: Meta::new_lossy(prompt), - } - } - - pub fn success(mime: impl ToString) -> Self { - Self { - status: Status::SUCCESS, - meta: Meta::new_lossy(mime.to_string()), - } - } - - pub fn redirect_temporary_lossy<'a>(location: impl TryInto>) -> Self { - let location = match location.try_into() { - Ok(location) => location, - Err(_) => return Self::bad_request_lossy("Invalid redirect location"), - }; - - Self { - status: Status::REDIRECT_TEMPORARY, - meta: Meta::new_lossy(location.to_string()), - } - } - - pub fn server_error(reason: impl Cowy) -> Result { - Ok(Self { - status: Status::PERMANENT_FAILURE, - meta: Meta::new(reason).context("Invalid server error reason")?, - }) - } - - pub fn server_error_lossy(reason: impl Cowy) -> Self { - Self { - status: Status::PERMANENT_FAILURE, - meta: Meta::new_lossy(reason), - } - } - - pub fn not_found() -> Self { - Self { - status: Status::NOT_FOUND, - meta: Meta::new_lossy("Not found"), - } - } - - pub fn bad_request_lossy(reason: impl Cowy) -> Self { - Self { - status: Status::BAD_REQUEST, - meta: Meta::new_lossy(reason), - } - } - - pub fn client_certificate_required() -> Self { - Self { - status: Status::CLIENT_CERTIFICATE_REQUIRED, - meta: Meta::new_lossy("No certificate provided"), - } - } - - pub fn certificate_not_authorized() -> Self { - Self { - status: Status::CERTIFICATE_NOT_AUTHORIZED, - meta: Meta::new_lossy("Your certificate is not authorized to view this content"), - } - } - - pub const fn status(&self) -> &Status { - &self.status - } - - pub const fn meta(&self) -> &Meta { - &self.meta - } -} diff --git a/src/types/status.rs b/src/types/status.rs deleted file mode 100644 index ba9ee71..0000000 --- a/src/types/status.rs +++ /dev/null @@ -1,82 +0,0 @@ -#[derive(Debug,Copy,Clone,PartialEq,Eq)] -pub struct Status(u8); - -impl Status { - pub const INPUT: Self = Self(10); - pub const SENSITIVE_INPUT: Self = Self(11); - pub const SUCCESS: Self = Self(20); - pub const REDIRECT_TEMPORARY: Self = Self(30); - pub const REDIRECT_PERMANENT: Self = Self(31); - pub const TEMPORARY_FAILURE: Self = Self(40); - pub const SERVER_UNAVAILABLE: Self = Self(41); - pub const CGI_ERROR: Self = Self(42); - pub const PROXY_ERROR: Self = Self(43); - pub const SLOW_DOWN: Self = Self(44); - pub const PERMANENT_FAILURE: Self = Self(50); - pub const NOT_FOUND: Self = Self(51); - pub const GONE: Self = Self(52); - pub const PROXY_REQUEST_REFUSED: Self = Self(53); - pub const BAD_REQUEST: Self = Self(59); - pub const CLIENT_CERTIFICATE_REQUIRED: Self = Self(60); - pub const CERTIFICATE_NOT_AUTHORIZED: Self = Self(61); - pub const CERTIFICATE_NOT_VALID: Self = Self(62); - - pub const fn code(&self) -> u8 { - self.0 - } - - pub fn is_success(&self) -> bool { - self.category().is_success() - } - - #[allow(clippy::missing_const_for_fn)] - pub fn category(&self) -> StatusCategory { - let class = self.0 / 10; - - match class { - 1 => StatusCategory::Input, - 2 => StatusCategory::Success, - 3 => StatusCategory::Redirect, - 4 => StatusCategory::TemporaryFailure, - 5 => StatusCategory::PermanentFailure, - 6 => StatusCategory::ClientCertificateRequired, - _ => StatusCategory::PermanentFailure, - } - } -} - -#[derive(Copy,Clone,PartialEq,Eq)] -pub enum StatusCategory { - Input, - Success, - Redirect, - TemporaryFailure, - PermanentFailure, - ClientCertificateRequired, -} - -impl StatusCategory { - pub fn is_input(&self) -> bool { - *self == Self::Input - } - - pub fn is_success(&self) -> bool { - *self == Self::Success - } - - pub fn redirect(&self) -> bool { - *self == Self::Redirect - } - - pub fn is_temporary_failure(&self) -> bool { - *self == Self::TemporaryFailure - } - - pub fn is_permanent_failure(&self) -> bool { - *self == Self::PermanentFailure - } - - pub fn is_client_certificate_required(&self) -> bool { - *self == Self::ClientCertificateRequired - } -} diff --git a/src/user_management/routes.rs b/src/user_management/routes.rs index e7f17f3..36ad435 100644 --- a/src/user_management/routes.rs +++ b/src/user_management/routes.rs @@ -165,7 +165,7 @@ impl UserManagementRoutes for crate::Server { if let Some(input) = request.input().map(str::to_owned) { (handler.clone())(request, user, input).await } else { - Response::input(prompt) + Ok(Response::input(prompt)) } } }) @@ -207,7 +207,7 @@ async fn handle_base(request: Request) - async fn handle_ask_cert(request: Request) -> Result { Ok(match request.user::()? { User::Unauthenticated => { - Response::client_certificate_required() + Response::client_certificate_required("Please select a client certificate to proceed.") }, User::NotSignedIn(nsi) => { let segments = request.trailing_segments().iter().map(String::as_str).collect::>(); @@ -270,7 +270,7 @@ async fn handle_register(reque Err(e) => return Err(e.into()) } } else { - Response::input_lossy("Please pick a username") + Response::input("Please pick a username") } }, User::SignedIn(user) => { @@ -305,14 +305,14 @@ async fn handle_login(request: Err(e) => return Err(e.into()), } } else { - Response::input_lossy("Please enter your password") + Response::sensitive_input("Please enter your password") } } else if let Some(username) = request.input() { - Response::redirect_temporary_lossy( + Response::redirect_temporary( format!("/account/login/{}", username).as_str() ) } else { - Response::input_lossy("Please enter your username") + Response::input("Please enter your username") } }, User::SignedIn(user) => { @@ -336,7 +336,7 @@ async fn handle_password(reque user.set_password(password)?; Response::success_gemini(include_str!("pages/password/success.gmi")) } else { - Response::input( + Response::sensitive_input( format!("Please enter a {}password", if user.has_password() { "new " @@ -344,7 +344,7 @@ async fn handle_password(reque "" } ) - )? + ) } }, }) diff --git a/src/util.rs b/src/util.rs index 32d521d..4ac9062 100644 --- a/src/util.rs +++ b/src/util.rs @@ -14,7 +14,7 @@ use std::future::Future; use tokio::time; #[cfg(feature="serve_dir")] -pub async fn serve_file>(path: P, mime: &str) -> Result { +pub async fn serve_file>(path: P, mime: &str) -> Response { let path = path.as_ref(); let file = match File::open(path).await { @@ -22,17 +22,17 @@ pub async fn serve_file>(path: P, mime: &str) -> Result Err(err) => match err.kind() { std::io::ErrorKind::PermissionDenied => { warn!("Asked to serve {}, but permission denied by OS", path.display()); - return Ok(Response::not_found()); + return Response::not_found(); }, _ => return warn_unexpected(err, path, line!()), } }; - Ok(Response::success(mime, file)) + Response::success(mime, file) } #[cfg(feature="serve_dir")] -pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P]) -> Result { +pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P]) -> Response { debug!("Dir: {}", dir.as_ref().display()); let dir = dir.as_ref(); let dir = match dir.canonicalize() { @@ -41,11 +41,11 @@ pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P match e.kind() { std::io::ErrorKind::NotFound => { warn!("Path {} not found. Check your configuration.", dir.display()); - return Response::server_error("Server incorrectly configured") + return Response::temporary_failure("Server incorrectly configured") }, std::io::ErrorKind::PermissionDenied => { warn!("Permission denied for {}. Check that the server has access.", dir.display()); - return Response::server_error("Server incorrectly configured") + return Response::temporary_failure("Server incorrectly configured") }, _ => return warn_unexpected(e, dir, line!()), } @@ -61,12 +61,12 @@ pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P Ok(dir) => dir, Err(e) => { match e.kind() { - std::io::ErrorKind::NotFound => return Ok(Response::not_found()), + std::io::ErrorKind::NotFound => return Response::not_found(), std::io::ErrorKind::PermissionDenied => { // Runs when asked to serve a file in a restricted dir // i.e. not /noaccess, but /noaccess/file warn!("Asked to serve {}, but permission denied by OS", path.display()); - return Ok(Response::not_found()); + return Response::not_found(); }, _ => return warn_unexpected(e, path.as_ref(), line!()), } @@ -74,7 +74,7 @@ pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P }; if !path.starts_with(&dir) { - return Ok(Response::not_found()); + return Response::not_found(); } if !path.is_dir() { @@ -86,14 +86,14 @@ pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P } #[cfg(feature="serve_dir")] -async fn serve_dir_listing, B: AsRef>(path: P, virtual_path: &[B]) -> Result { +async fn serve_dir_listing, B: AsRef>(path: P, virtual_path: &[B]) -> Response { let mut dir = match fs::read_dir(path.as_ref()).await { Ok(dir) => dir, Err(err) => match err.kind() { - io::ErrorKind::NotFound => return Ok(Response::not_found()), + io::ErrorKind::NotFound => return Response::not_found(), std::io::ErrorKind::PermissionDenied => { warn!("Asked to serve {}, but permission denied by OS", path.as_ref().display()); - return Ok(Response::not_found()); + return Response::not_found(); }, _ => return warn_unexpected(err, path.as_ref(), line!()), } @@ -109,12 +109,10 @@ async fn serve_dir_listing, B: AsRef>(path: P, virtual_path document.add_link("..", "📁 ../"); } - while let Some(entry) = dir.next_entry().await.context("Failed to list directory")? { + while let Some(entry) = dir.next_entry().await.expect("Failed to list directory") { let file_name = entry.file_name(); let file_name = file_name.to_string_lossy(); - let is_dir = entry.file_type().await - .with_context(|| format!("Failed to get file type of `{}`", entry.path().display()))? - .is_dir(); + let is_dir = entry.file_type().await.unwrap().is_dir(); let trailing_slash = if is_dir { "/" } else { "" }; let uri = format!("./{}{}", file_name, trailing_slash); @@ -125,7 +123,7 @@ async fn serve_dir_listing, B: AsRef>(path: P, virtual_path )); } - Ok(document.into()) + document.into() } #[cfg(feature="serve_dir")] @@ -146,7 +144,7 @@ pub fn guess_mime_from_path>(path: P) -> &'static str { #[cfg(feature="serve_dir")] /// Print a warning to the log asking to file an issue and respond with "Unexpected Error" -pub (crate) fn warn_unexpected(err: impl std::fmt::Debug, path: &Path, line: u32) -> Result { +pub (crate) fn warn_unexpected(err: impl std::fmt::Debug, path: &Path, line: u32) -> Response { warn!( concat!( "Unexpected error serving path {} at util.rs:{}, please report to ", @@ -157,7 +155,7 @@ pub (crate) fn warn_unexpected(err: impl std::fmt::Debug, path: &Path, line: u32 line, err ); - Response::server_error("Unexpected error") + Response::temporary_failure("Unexpected error") } /// A convenience trait alias for `AsRef + Into`,