Merged Meta, Status, and ResponseHeader into Response

This commit is contained in:
Emii Tatsuo 2020-12-01 16:36:29 -05:00
parent a92b3788e2
commit f922f8c70d
Signed by: Emi
GPG Key ID: 68FAB2E2E6DFC98B
9 changed files with 162 additions and 444 deletions

View File

@ -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("")))
}
}
}

View File

@ -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<Response> {
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<Body>, 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<Body>, 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(())
}

View File

@ -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;

View File

@ -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<str>) -> Result<Self> {
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<str>) -> 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);
}
}

View File

@ -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<Body>,
pub status: u8,
pub meta: String,
pub body: Option<Body>,
}
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<Document>) -> 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<str>) -> Result<Self> {
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<str>) -> Self {
let header = ResponseHeader::input_lossy(prompt);
Self::new(header)
}
pub fn redirect_temporary_lossy<'a>(location: impl TryInto<URIReference<'a>>) -> 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<Body>) -> 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<Body>) -> 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<Body>) -> Self {
Self::success("text/plain", body)
}
pub fn server_error(reason: impl Cowy<str>) -> Result<Self> {
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<str>) -> 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<Body>) -> 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<Body> {
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
}
}

View File

@ -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<str>) -> Result<Self> {
Ok(Self {
status: Status::INPUT,
meta: Meta::new(prompt).context("Invalid input prompt")?,
})
}
pub fn input_lossy(prompt: impl Cowy<str>) -> 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<URIReference<'a>>) -> 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<str>) -> Result<Self> {
Ok(Self {
status: Status::PERMANENT_FAILURE,
meta: Meta::new(reason).context("Invalid server error reason")?,
})
}
pub fn server_error_lossy(reason: impl Cowy<str>) -> 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<str>) -> 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
}
}

View File

@ -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
}
}

View File

@ -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<UserData: Serialize + DeserializeOwned>(request: Request) -
async fn handle_ask_cert<UserData: Serialize + DeserializeOwned>(request: Request) -> Result<Response> {
Ok(match request.user::<UserData>()? {
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::<Vec<&str>>();
@ -270,7 +270,7 @@ async fn handle_register<UserData: Serialize + DeserializeOwned + Default>(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<UserData: Serialize + DeserializeOwned + Default>(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<UserData: Serialize + DeserializeOwned + Default>(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<UserData: Serialize + DeserializeOwned + Default>(reque
""
}
)
)?
)
}
},
})

View File

@ -14,7 +14,7 @@ use std::future::Future;
use tokio::time;
#[cfg(feature="serve_dir")]
pub async fn serve_file<P: AsRef<Path>>(path: P, mime: &str) -> Result<Response> {
pub async fn serve_file<P: AsRef<Path>>(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<P: AsRef<Path>>(path: P, mime: &str) -> Result<Response>
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<D: AsRef<Path>, P: AsRef<Path>>(dir: D, virtual_path: &[P]) -> Result<Response> {
pub async fn serve_dir<D: AsRef<Path>, P: AsRef<Path>>(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<D: AsRef<Path>, P: AsRef<Path>>(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<D: AsRef<Path>, P: AsRef<Path>>(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<D: AsRef<Path>, P: AsRef<Path>>(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<D: AsRef<Path>, P: AsRef<Path>>(dir: D, virtual_path: &[P
}
#[cfg(feature="serve_dir")]
async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path: &[B]) -> Result<Response> {
async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(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<P: AsRef<Path>, B: AsRef<Path>>(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<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path
));
}
Ok(document.into())
document.into()
}
#[cfg(feature="serve_dir")]
@ -146,7 +144,7 @@ pub fn guess_mime_from_path<P: AsRef<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<Response> {
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<T> + Into<T::Owned>`,