From 53edaf9d111c7014ea79bee26f177dc5f7976f6e Mon Sep 17 00:00:00 2001 From: Tpt Date: Mon, 6 Mar 2023 21:46:22 +0100 Subject: [PATCH] Server: Add a nicer error when writes are not allowed --- server/src/main.rs | 59 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index 957acd55..0d069822 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -173,10 +173,10 @@ pub fn main() -> anyhow::Result<()> { Store::new() }?, bind, - true, + false, ), Command::ServeReadOnly { location, bind } => { - serve(Store::open_read_only(location)?, bind, false) + serve(Store::open_read_only(location)?, bind, true) } Command::ServeSecondary { primary_location, @@ -189,7 +189,7 @@ pub fn main() -> anyhow::Result<()> { Store::open_secondary(primary_location) }?, bind, - false, + true, ), Command::Backup { location, @@ -486,9 +486,9 @@ impl FromStr for GraphOrDatasetFormat { } } -fn serve(store: Store, bind: String, allow_writes: bool) -> anyhow::Result<()> { +fn serve(store: Store, bind: String, read_only: bool) -> anyhow::Result<()> { let mut server = Server::new(move |request| { - handle_request(request, store.clone(), allow_writes) + handle_request(request, store.clone(), read_only) .unwrap_or_else(|(status, message)| error(status, message)) }); server.set_global_timeout(HTTP_TIMEOUT); @@ -503,7 +503,7 @@ type HttpError = (Status, String); fn handle_request( request: &mut Request, store: Store, - allow_writes: bool, + read_only: bool, ) -> Result { match (request.url().path(), request.method().as_ref()) { ("/", "HEAD") => Ok(Response::builder(Status::OK) @@ -558,7 +558,10 @@ fn handle_request( Err(unsupported_media_type(&content_type)) } } - ("/update", "POST") if allow_writes => { + ("/update", "POST") => { + if read_only { + return Err(the_server_is_read_only()); + } let content_type = content_type(request).ok_or_else(|| bad_request("No Content-Type given"))?; if content_type == "application/sparql-update" { @@ -641,7 +644,10 @@ fn handle_request( ) } } - (path, "PUT") if path.starts_with("/store") && allow_writes => { + (path, "PUT") if path.starts_with("/store") => { + if read_only { + return Err(the_server_is_read_only()); + } let content_type = content_type(request).ok_or_else(|| bad_request("No Content-Type given"))?; if let Some(target) = store_target(request)? { @@ -684,7 +690,10 @@ fn handle_request( Ok(Response::builder(Status::NO_CONTENT).build()) } } - (path, "DELETE") if path.starts_with("/store") && allow_writes => { + (path, "DELETE") if path.starts_with("/store") => { + if read_only { + return Err(the_server_is_read_only()); + } if let Some(target) = store_target(request)? { match target { NamedGraphName::DefaultGraph => store @@ -711,7 +720,10 @@ fn handle_request( } Ok(Response::builder(Status::NO_CONTENT).build()) } - (path, "POST") if path.starts_with("/store") && allow_writes => { + (path, "POST") if path.starts_with("/store") => { + if read_only { + return Err(the_server_is_read_only()); + } let content_type = content_type(request).ok_or_else(|| bad_request("No Content-Type given"))?; if let Some(target) = store_target(request)? { @@ -1258,6 +1270,10 @@ fn bad_request(message: impl fmt::Display) -> HttpError { (Status::BAD_REQUEST, message.to_string()) } +fn the_server_is_read_only() -> HttpError { + (Status::FORBIDDEN, "The server is read-only".into()) +} + fn unsupported_media_type(content_type: &str) -> HttpError { ( Status::UNSUPPORTED_MEDIA_TYPE, @@ -1813,6 +1829,19 @@ mod tests { ServerTest::new()?.test_status(request, Status::BAD_REQUEST) } + #[test] + fn post_update_read_only() -> Result<()> { + let request = Request::builder(Method::POST, "http://localhost/update".parse()?) + .with_header(HeaderName::CONTENT_TYPE, "application/sparql-update")? + .with_body( + "INSERT DATA { }", + ); + ServerTest::check_status( + ServerTest::new()?.exec_read_only(request), + Status::FORBIDDEN, + ) + } + #[test] fn graph_store_url_normalization() -> Result<()> { let server = ServerTest::new()?; @@ -2148,12 +2177,20 @@ mod tests { } fn exec(&self, mut request: Request) -> Response { + handle_request(&mut request, self.store.clone(), false) + .unwrap_or_else(|(status, message)| error(status, message)) + } + + fn exec_read_only(&self, mut request: Request) -> Response { handle_request(&mut request, self.store.clone(), true) .unwrap_or_else(|(status, message)| error(status, message)) } fn test_status(&self, request: Request, expected_status: Status) -> Result<()> { - let mut response = self.exec(request); + Self::check_status(self.exec(request), expected_status) + } + + fn check_status(mut response: Response, expected_status: Status) -> Result<()> { let mut buf = String::new(); response.body_mut().read_to_string(&mut buf)?; assert_eq!(response.status(), expected_status, "Error message: {buf}");