diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-26 03:53:31 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-26 03:53:31 +0000 |
| commit | 963b2971ec2f43b1c2f669a969c294fc1d291d3b (patch) | |
| tree | 9495d5bd350ebf5123a9c72b9da3367b17e5534f /src/http | |
| parent | 75f3da90edb66b81dbb6ed9806155f6bd7925fe1 (diff) | |
add cors support
Diffstat (limited to 'src/http')
| -rw-r--r-- | src/http/mod.rs | 46 |
1 files changed, 41 insertions, 5 deletions
diff --git a/src/http/mod.rs b/src/http/mod.rs index c676bda..85b72f4 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs | |||
| @@ -24,6 +24,19 @@ use base64::Engine; | |||
| 24 | use crate::config::Config; | 24 | use crate::config::Config; |
| 25 | use crate::git; | 25 | use crate::git; |
| 26 | 26 | ||
| 27 | /// CORS headers required by GRASP-01 specification (lines 40-47) | ||
| 28 | const CORS_ALLOW_ORIGIN: &str = "*"; | ||
| 29 | const CORS_ALLOW_METHODS: &str = "GET, POST"; | ||
| 30 | const CORS_ALLOW_HEADERS: &str = "Content-Type"; | ||
| 31 | |||
| 32 | /// Add CORS headers to a response builder | ||
| 33 | fn add_cors_headers(builder: hyper::http::response::Builder) -> hyper::http::response::Builder { | ||
| 34 | builder | ||
| 35 | .header("Access-Control-Allow-Origin", CORS_ALLOW_ORIGIN) | ||
| 36 | .header("Access-Control-Allow-Methods", CORS_ALLOW_METHODS) | ||
| 37 | .header("Access-Control-Allow-Headers", CORS_ALLOW_HEADERS) | ||
| 38 | } | ||
| 39 | |||
| 27 | /// HTTP Service that serves both WebSocket (relay) and HTML landing page | 40 | /// HTTP Service that serves both WebSocket (relay) and HTML landing page |
| 28 | struct HttpService { | 41 | struct HttpService { |
| 29 | relay: LocalRelay, | 42 | relay: LocalRelay, |
| @@ -47,12 +60,23 @@ impl Service<Request<Incoming>> for HttpService { | |||
| 47 | type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; | 60 | type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; |
| 48 | 61 | ||
| 49 | fn call(&self, req: Request<Incoming>) -> Self::Future { | 62 | fn call(&self, req: Request<Incoming>) -> Self::Future { |
| 50 | let base = Response::builder().header("server", "ngit-grasp"); | 63 | let base = add_cors_headers(Response::builder().header("server", "ngit-grasp")); |
| 51 | let path = req.uri().path().to_string(); | 64 | let path = req.uri().path().to_string(); |
| 52 | let query = req.uri().query().map(|s| s.to_string()); | 65 | let query = req.uri().query().map(|s| s.to_string()); |
| 53 | let method = req.method().clone(); | 66 | let method = req.method().clone(); |
| 54 | let git_data_path = self.config.git_data_path.clone(); | 67 | let git_data_path = self.config.git_data_path.clone(); |
| 55 | 68 | ||
| 69 | // Handle OPTIONS preflight requests (CORS) | ||
| 70 | // GRASP-01 spec line 47: Respond to OPTIONS with 204 No Content | ||
| 71 | if method == Method::OPTIONS { | ||
| 72 | return Box::pin(async move { | ||
| 73 | Ok(add_cors_headers(Response::builder().header("server", "ngit-grasp")) | ||
| 74 | .status(204) | ||
| 75 | .body(Full::new(Bytes::new())) | ||
| 76 | .unwrap()) | ||
| 77 | }); | ||
| 78 | } | ||
| 79 | |||
| 56 | // Check for Git HTTP requests first | 80 | // Check for Git HTTP requests first |
| 57 | if let Some((npub, identifier, subpath)) = git::parse_git_url(&path) { | 81 | if let Some((npub, identifier, subpath)) = git::parse_git_url(&path) { |
| 58 | let npub = npub.to_string(); | 82 | let npub = npub.to_string(); |
| @@ -104,11 +128,24 @@ impl Service<Request<Incoming>> for HttpService { | |||
| 104 | }; | 128 | }; |
| 105 | 129 | ||
| 106 | match result { | 130 | match result { |
| 107 | Ok(response) => Ok(response), | 131 | Ok(response) => { |
| 132 | // Add CORS headers to successful Git responses | ||
| 133 | let (parts, body) = response.into_parts(); | ||
| 134 | Ok(add_cors_headers(Response::builder() | ||
| 135 | .status(parts.status)) | ||
| 136 | .header("content-type", parts.headers.get("content-type") | ||
| 137 | .and_then(|v| v.to_str().ok()) | ||
| 138 | .unwrap_or("application/octet-stream")) | ||
| 139 | .header("cache-control", parts.headers.get("cache-control") | ||
| 140 | .and_then(|v| v.to_str().ok()) | ||
| 141 | .unwrap_or("no-cache")) | ||
| 142 | .body(body) | ||
| 143 | .unwrap()) | ||
| 144 | } | ||
| 108 | Err(e) => { | 145 | Err(e) => { |
| 109 | tracing::error!("Git handler error: {}", e); | 146 | tracing::error!("Git handler error: {}", e); |
| 110 | let error_msg = format!("Git error: {}", e); | 147 | let error_msg = format!("Git error: {}", e); |
| 111 | Ok(Response::builder() | 148 | Ok(add_cors_headers(Response::builder()) |
| 112 | .status(e.status_code()) | 149 | .status(e.status_code()) |
| 113 | .body(Full::new(Bytes::from(error_msg))) | 150 | .body(Full::new(Bytes::from(error_msg))) |
| 114 | .unwrap()) | 151 | .unwrap()) |
| @@ -133,10 +170,9 @@ impl Service<Request<Incoming>> for HttpService { | |||
| 133 | tracing::debug!("Serving NIP-11 relay information document to {}", self.remote); | 170 | tracing::debug!("Serving NIP-11 relay information document to {}", self.remote); |
| 134 | 171 | ||
| 135 | return Box::pin(async move { | 172 | return Box::pin(async move { |
| 136 | Ok(base | 173 | Ok(add_cors_headers(Response::builder().header("server", "ngit-grasp")) |
| 137 | .status(200) | 174 | .status(200) |
| 138 | .header("content-type", "application/nostr+json") | 175 | .header("content-type", "application/nostr+json") |
| 139 | .header("access-control-allow-origin", "*") | ||
| 140 | .body(Full::new(Bytes::from(json))) | 176 | .body(Full::new(Bytes::from(json))) |
| 141 | .unwrap()) | 177 | .unwrap()) |
| 142 | }); | 178 | }); |