From 6dd53f96d82d5c472be48bff1a7cce1daddeb092 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 16 Jan 2024 14:31:32 +0100 Subject: [PATCH] Handle requested path being a directory correctly --- src/origin.rs | 3 +++ src/origin/git.rs | 18 ++++++++++++++---- src/service/gemini.rs | 7 +++++++ src/service/http.rs | 34 ++++++++++++++++++++++++++++------ 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/origin.rs b/src/origin.rs index 527ef01..83b3dc8 100644 --- a/src/origin.rs +++ b/src/origin.rs @@ -39,6 +39,9 @@ pub enum GetFileError { #[error("file not found")] FileNotFound, + #[error("path is directory")] + PathIsDirectory, + #[error(transparent)] Unexpected(#[from] unexpected::Error), } diff --git a/src/origin/git.rs b/src/origin/git.rs index bd06f37..397b5d9 100644 --- a/src/origin/git.rs +++ b/src/origin/git.rs @@ -330,10 +330,20 @@ impl super::Store for FSStore { .object() .or_unexpected()?; - // TODO this is very not ideal, the whole file is first read totally into memory, and then - // that is cloned. - let data = bytes::Bytes::copy_from_slice(file_object.data.as_slice()); - Ok(Box::pin(stream::once(async move { Ok(data) }))) + use gix::object::Kind; + match file_object.kind { + Kind::Tree => Err(origin::GetFileError::PathIsDirectory), + Kind::Blob => { + // TODO this is very not ideal, the whole file is first read totally into memory, and then + // that is cloned. + let data = bytes::Bytes::copy_from_slice(file_object.data.as_slice()); + Ok(Box::pin(stream::once(async move { Ok(data) }))) + } + Kind::Commit | Kind::Tag => Err(unexpected::Error::from( + format!("found object of kind {} in tree", file_object.kind).as_str(), + ) + .into()), + } } } diff --git a/src/service/gemini.rs b/src/service/gemini.rs index ebf0b5b..981c94c 100644 --- a/src/service/gemini.rs +++ b/src/service/gemini.rs @@ -113,6 +113,13 @@ impl Service { ) .into()) } + Err(GetFileError::PathIsDirectory) => { + // redirect so that the path has '/' appended to it, which will cause the server to + // check index.gmi within the path on the new page load. + let mut path = path.into_owned(); + path.push_str("/"); + return Ok(self.respond_conn(w, "30", path.as_str(), None).await?); + } Err(GetFileError::Unexpected(e)) => return Err(e.into()), }; diff --git a/src/service/http.rs b/src/service/http.rs index 38d340d..a111b68 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -153,6 +153,19 @@ impl Service { ) } + fn render_redirect(&self, status_code: u16, target_uri: &str) -> Response { + Response::builder() + .status(status_code) + .header("Location", target_uri.to_string()) + .body(Body::empty()) + .unwrap_or_else(|err| { + self.internal_error( + format!("failed to render {status_code} redirect to {target_uri}: {err}",) + .as_str(), + ) + }) + } + fn https_redirect(&self, domain: domain::Name, req: Request) -> Response { let https_addr = self.config.http.https_addr.unwrap(); @@ -173,13 +186,12 @@ impl Service { .try_into() .or_unexpected_while("constructing new URI")?; - Response::builder() - .status(http::status::StatusCode::PERMANENT_REDIRECT) - .header("Location", uri.to_string()) - .body(Body::empty()) - .or_unexpected_while("building redirect") + Ok(self.render_redirect( + http::status::StatusCode::PERMANENT_REDIRECT.into(), + uri.to_string().as_str(), + )) })() - .unwrap_or_else(|err| { + .unwrap_or_else(|err: unexpected::Error| { self.internal_error( format!("failed to redirect from {} to https: {}", req.uri(), err).as_str(), ) @@ -216,6 +228,16 @@ impl Service { ) .as_str(), ), + Err(GetFileError::PathIsDirectory) => { + // redirect so that the path has '/' appended to it, which will cause the server to + // check index.html within the path on the new page load. + let mut path = path.into_owned(); + path.push_str("/"); + self.render_redirect( + http::status::StatusCode::TEMPORARY_REDIRECT.into(), + path.as_str(), + ) + } Err(GetFileError::Unexpected(e)) => { self.internal_error(format!("failed to fetch file {path}: {e}").as_str()) }