From 427827ef3777c13ffe2a33ecbc54c316e58693d5 Mon Sep 17 00:00:00 2001 From: Marcel Koch Date: Tue, 25 Feb 2025 22:28:05 +0100 Subject: [PATCH 1/6] support variable `PATH_INFO` in routes --- cgi-rs/src/request.rs | 13 ++++++++++++- sample-cgi-script/src/main.rs | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cgi-rs/src/request.rs b/cgi-rs/src/request.rs index 63c8128..2667140 100644 --- a/cgi-rs/src/request.rs +++ b/cgi-rs/src/request.rs @@ -44,11 +44,22 @@ impl CGIRequest { self.var(MetaVariableKind::RequestUri) .map(|uri| Ok(uri.as_str()?.to_string())) .unwrap_or_else(|| { + + let path_info_str = match MetaVariableKind::PathInfo.try_from_env() { + Ok(meta_variable) => { + String::from(meta_variable.as_str().unwrap_or("")) + } + Err(_) => { + String::from("") + } + }; + let script_name = MetaVariableKind::ScriptName.try_from_env()?; let query_string = MetaVariableKind::QueryString.try_from_env()?; Ok(format!( - "{}?{}", + "{}{}?{}", script_name.as_str()?, + path_info_str, query_string.as_str()? )) }) diff --git a/sample-cgi-script/src/main.rs b/sample-cgi-script/src/main.rs index 4495f83..3d2641c 100644 --- a/sample-cgi-script/src/main.rs +++ b/sample-cgi-script/src/main.rs @@ -6,6 +6,9 @@ async fn main() { let app = Router::new().route( "/cgi-bin/sample-cgi-server", get(|| async { "Hello, World!" }), + ).route( + "/cgi-bin/sample-cgi-server/with/path-info", + get(|| async { "Hello, PATH_INFO" }), ); if let Err(e) = serve_cgi(app).await { From 03d1f1ccd5ecc69494f666972bba9716ee68a983 Mon Sep 17 00:00:00 2001 From: Marcel Koch Date: Wed, 16 Apr 2025 22:12:50 +0200 Subject: [PATCH 2/6] support axum 0.8.1 --- cgi-rs/Cargo.toml | 4 +++- cgi-rs/src/request.rs | 30 ++++++++++++++++++++--------- cgi-rs/src/response.rs | 37 +++++++++++------------------------- sample-cgi-script/Cargo.toml | 2 +- tower-cgi/Cargo.toml | 5 +++-- tower-cgi/src/lib.rs | 31 +++++++++++++++++++++++------- 6 files changed, 63 insertions(+), 46 deletions(-) diff --git a/cgi-rs/Cargo.toml b/cgi-rs/Cargo.toml index 102c53f..ee4b499 100644 --- a/cgi-rs/Cargo.toml +++ b/cgi-rs/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -hyper = "0.14" +http-body-util = "0.1.2" +hyper = "1.6.0" snafu = "0.8" tokio = "1" +bytes = "1.10.0" diff --git a/cgi-rs/src/request.rs b/cgi-rs/src/request.rs index 2667140..2f5a5e0 100644 --- a/cgi-rs/src/request.rs +++ b/cgi-rs/src/request.rs @@ -1,14 +1,18 @@ +use std::convert::Infallible; use crate::{error, CGIError, MetaVariable, MetaVariableKind, Result}; -use hyper::{Body as HttpBody, Request}; +use hyper::Request; +use hyper::body::{Body, Bytes}; use snafu::ResultExt; use std::io::{stdin, Read}; +use http_body_util::combinators::BoxBody; +use http_body_util::Full; -pub struct CGIRequest { - pub request_body: HttpBody, +pub struct CGIRequest { + pub request_body: B } -impl CGIRequest { - pub fn from_env() -> Result { +impl CGIRequest where B: Body { + pub fn from_env() -> Result>> { let content_length = MetaVariableKind::ContentLength .from_env() .map(|content_length| { @@ -19,8 +23,15 @@ impl CGIRequest { .transpose()? .unwrap_or_default(); - let request_body = HttpBody::from(Self::request_body_from_env(content_length)?); - Ok(Self { request_body }) + let read_content = Self::request_body_from_env(content_length)?; + + let request_body = Bytes::from(read_content); + + let full = Full::from(request_body); + + let result = CGIRequest { request_body: full }; + + Ok(result) } pub fn var(&self, kind: MetaVariableKind) -> Option { @@ -76,10 +87,11 @@ macro_rules! try_set_headers { }; } -impl TryFrom for Request { +impl TryFrom> for Request where B: Body { type Error = CGIError; - fn try_from(cgi_request: CGIRequest) -> Result { + fn try_from(cgi_request: CGIRequest) -> Result { + let mut request_builder = Request::builder() .method( cgi_request diff --git a/cgi-rs/src/response.rs b/cgi-rs/src/response.rs index 616fff0..ebdaf09 100644 --- a/cgi-rs/src/response.rs +++ b/cgi-rs/src/response.rs @@ -2,16 +2,19 @@ use crate::{error, CGIError, Result}; use hyper::{http::HeaderValue, HeaderMap, Response}; use snafu::ResultExt; use std::io::Write; +use bytes::Bytes; +use http_body_util::{Full}; +use hyper::body::{Body}; #[derive(Debug)] -pub struct CGIResponse { - headers: HeaderMap, - status: String, - reason: Option, - body: B, +pub struct CGIResponse { + pub headers: HeaderMap, + pub status: String, + pub reason: Option, + pub body: Bytes, } -impl CGIResponse { +impl CGIResponse { pub async fn write_response_to_output(self, mut output: impl Write) -> Result<()> { self.write_status(&mut output).await?; self.write_headers(&mut output).await?; @@ -50,29 +53,11 @@ impl CGIResponse { } async fn write_body(self, output: &mut impl Write) -> Result<()> { - let body = hyper::body::to_bytes(self.body) - .await - .or_else(|_| error::BuildResponseSnafu.fail())?; + let body = self.body; - output.write(&body).context(error::WriteResponseSnafu)?; + output.write(body.as_ref()).context(error::WriteResponseSnafu)?; Ok(()) } } -impl TryFrom> for CGIResponse { - type Error = CGIError; - - fn try_from(response: Response) -> Result { - let headers = response.headers().clone(); - let status = response.status().to_string(); - let reason = response.status().canonical_reason().map(|s| s.to_string()); - let body = response.into_body(); - Ok(CGIResponse { - headers, - status, - reason, - body, - }) - } -} diff --git a/sample-cgi-script/Cargo.toml b/sample-cgi-script/Cargo.toml index 12561c6..f2fa7d1 100644 --- a/sample-cgi-script/Cargo.toml +++ b/sample-cgi-script/Cargo.toml @@ -6,6 +6,6 @@ license = "Apache-2.0" publish = false [dependencies] -axum = "0.6" +axum = "0.8.1" tower-cgi = { path = "../tower-cgi" } tokio = { version = "1", features = ["full"] } diff --git a/tower-cgi/Cargo.toml b/tower-cgi/Cargo.toml index 3f7d6c8..e4dd881 100644 --- a/tower-cgi/Cargo.toml +++ b/tower-cgi/Cargo.toml @@ -6,10 +6,11 @@ license = "Apache-2.0" [dependencies] cgi-rs = { path = "../cgi-rs" } -hyper = { version = "0.14", default-features = false } +hyper = { version = "1.6.0", default-features = false } snafu = "0.8" tower = { version = "0.5", default-features = false, features = ["util"] } +http-body-util = "0.1.2" +axum = "0.8.1" [dev-dependencies] -axum = "0.6" tokio = { version = "1", features = ["full"] } diff --git a/tower-cgi/src/lib.rs b/tower-cgi/src/lib.rs index 38287b6..45dc744 100644 --- a/tower-cgi/src/lib.rs +++ b/tower-cgi/src/lib.rs @@ -17,10 +17,13 @@ //! ``` use cgi_rs::{CGIError, CGIRequest, CGIResponse}; -use hyper::{Body as HttpBody, Request, Response}; use snafu::ResultExt; use std::convert::Infallible; +use std::fmt::Debug; use std::io::Write; +use http_body_util::{Full, BodyExt}; +use hyper::body::{Body, Bytes}; +use hyper::{Request, Response}; use tower::{Service, ServiceExt}; /// Serve a CGI application. @@ -28,11 +31,11 @@ use tower::{Service, ServiceExt}; /// Responses are emitted to stdout per the CGI RFC3875 pub async fn serve_cgi(app: S) -> Result<()> where - S: Service, Response = Response, Error = Infallible> + S: Service>, Response = Response, Error = Infallible> + Clone + Send + 'static, - B: hyper::body::HttpBody, + B: Body, ::Error: Debug { serve_cgi_with_output(std::io::stdout(), app).await } @@ -42,13 +45,13 @@ where /// Responses are emitted to the provided output stream. pub async fn serve_cgi_with_output(output: impl Write, app: S) -> Result<()> where - S: Service, Response = Response, Error = Infallible> + S: Service>, Response = Response, Error = Infallible> + Clone + Send + 'static, - B: hyper::body::HttpBody, + B: Body, ::Error: Debug { - let request = CGIRequest::from_env() + let request = CGIRequest::>::from_env() .and_then(Request::try_from) .context(error::CGIRequestParseSnafu)?; @@ -57,7 +60,21 @@ where .await .expect("The Error type is Infallible, this should never fail."); - let cgi_response: CGIResponse = response.try_into().context(error::CGIResponseParseSnafu)?; + let headers = response.headers().clone(); + let status = response.status().to_string(); + let reason = response.status().canonical_reason().map(|s| s.to_string()); + + let collected = response.into_body().collect().await; + + let body_bytes = collected.unwrap().to_bytes(); + + let cgi_response = CGIResponse { + headers, + status, + reason, + body: body_bytes, + }; + cgi_response .write_response_to_output(output) .await From ec8a99912dd6b9909bd9414a8b1c86bfee88a19d Mon Sep 17 00:00:00 2001 From: Marcel Koch Date: Sun, 13 Jul 2025 17:28:04 +0200 Subject: [PATCH 3/6] fix doc tests --- cgi-rs/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cgi-rs/src/lib.rs b/cgi-rs/src/lib.rs index e4d9fa6..bc29071 100644 --- a/cgi-rs/src/lib.rs +++ b/cgi-rs/src/lib.rs @@ -11,7 +11,9 @@ //! ## Examples //! ### Parsing an HTTP Request //! ```rust -//! use hyper::{Request, Body}; +//! use hyper::Request; +//! use hyper::body::Bytes; +//! use http_body_util::Full; //! use cgi_rs::CGIRequest; //! //! // In a CGI environment, the CGI server would set these variables, as well as others. @@ -19,7 +21,7 @@ //! std::env::set_var("CONTENT_LENGTH", "0"); //! std::env::set_var("REQUEST_URI", "/"); //! -//! let cgi_request: Request = CGIRequest::from_env() +//! let cgi_request: Request> = CGIRequest::>::from_env() //! .and_then(Request::try_from).unwrap(); //! //! assert_eq!(cgi_request.method(), "GET"); From b7a59986775c032594a3106c17761e8f3010dbba Mon Sep 17 00:00:00 2001 From: Marcel Koch Date: Mon, 14 Jul 2025 01:38:13 +0200 Subject: [PATCH 4/6] support cookies --- cgi-rs/src/lib.rs | 2 ++ cgi-rs/src/request.rs | 1 + sample-cgi-script/Cargo.toml | 3 +++ sample-cgi-script/src/main.rs | 29 ++++++++++++++++++++++++++--- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cgi-rs/src/lib.rs b/cgi-rs/src/lib.rs index bc29071..6ad77e6 100644 --- a/cgi-rs/src/lib.rs +++ b/cgi-rs/src/lib.rs @@ -108,6 +108,7 @@ pub enum MetaVariableKind { HttpHost, HttpUserAgent, HttpAccept, + HttpCookie, ServerSignature, DocumentRoot, RequestScheme, @@ -150,6 +151,7 @@ impl MetaVariableKind { MetaVariableKind::ScriptFilename => "SCRIPT_FILENAME", MetaVariableKind::RemotePort => "REMOTE_PORT", MetaVariableKind::RequestUri => "REQUEST_URI", + MetaVariableKind::HttpCookie => "HTTP_COOKIE" } } diff --git a/cgi-rs/src/request.rs b/cgi-rs/src/request.rs index 2f5a5e0..0f33293 100644 --- a/cgi-rs/src/request.rs +++ b/cgi-rs/src/request.rs @@ -107,6 +107,7 @@ impl TryFrom> for Request where B: Body { ["Accept", MetaVariableKind::HttpAccept], ["Host", MetaVariableKind::HttpHost], ["User-Agent", MetaVariableKind::HttpUserAgent], + ["Cookie", MetaVariableKind::HttpCookie], ); request_builder diff --git a/sample-cgi-script/Cargo.toml b/sample-cgi-script/Cargo.toml index f2fa7d1..f89f8a3 100644 --- a/sample-cgi-script/Cargo.toml +++ b/sample-cgi-script/Cargo.toml @@ -9,3 +9,6 @@ publish = false axum = "0.8.1" tower-cgi = { path = "../tower-cgi" } tokio = { version = "1", features = ["full"] } +tower-sessions = "0.14.0" +tower-cookies = "0.11.0" +tower-sessions-file-based-store = "*" diff --git a/sample-cgi-script/src/main.rs b/sample-cgi-script/src/main.rs index 3d2641c..af7b57b 100644 --- a/sample-cgi-script/src/main.rs +++ b/sample-cgi-script/src/main.rs @@ -1,15 +1,38 @@ use axum::{routing::get, Router}; +use axum::http::StatusCode; +use axum::response::Response; +use tower_cookies::{Cookie, Cookies}; +use tower_sessions::cookie::time::Duration; +use tower_sessions::{MemoryStore, Session, SessionStore}; +use tower_sessions::session::Record; use tower_cgi::serve_cgi; +use tower_sessions_file_based_store::FileStore; + #[tokio::main] async fn main() { + let session_store = FileStore::new("./", "prefix-", ".json"); + // let session_store = MemoryStore::default(); + let session_layer = tower_sessions::SessionManagerLayer::new(session_store) + .with_secure(false) + //.with_always_save(true) + .with_expiry(tower_sessions::Expiry::OnInactivity(Duration::seconds(15))); + let app = Router::new().route( - "/cgi-bin/sample-cgi-server", - get(|| async { "Hello, World!" }), + "/cgi-bin/sample-cgi-server/", + get(|cookies: Cookies, session: Session| async move { + cookies.add(Cookie::new("hello_world", "hello_world")); + session.clear().await; + session.insert("foo", "bar").await.unwrap(); + let value: String = session.get("foo").await.unwrap().unwrap_or("no value".to_string()); + + value + + }), ).route( "/cgi-bin/sample-cgi-server/with/path-info", get(|| async { "Hello, PATH_INFO" }), - ); + ).layer(session_layer); if let Err(e) = serve_cgi(app).await { eprintln!("Error while serving CGI request: {}", e); From 711e0f39b247733b7a610c3704a2ba1b967b6f5d Mon Sep 17 00:00:00 2001 From: Marcel Koch Date: Mon, 14 Jul 2025 01:53:52 +0200 Subject: [PATCH 5/6] upgrade resolver version --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 94a8658..e2e7b8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "tower-cgi", "cgi-rs", From a3e752062ec3f2912448b5086ab726de72219cc0 Mon Sep 17 00:00:00 2001 From: Marcel Koch Date: Mon, 14 Jul 2025 01:59:54 +0200 Subject: [PATCH 6/6] bump axum version to 0.8.4 --- sample-cgi-script/Cargo.toml | 2 +- tower-cgi/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sample-cgi-script/Cargo.toml b/sample-cgi-script/Cargo.toml index f89f8a3..a3ecd8c 100644 --- a/sample-cgi-script/Cargo.toml +++ b/sample-cgi-script/Cargo.toml @@ -6,7 +6,7 @@ license = "Apache-2.0" publish = false [dependencies] -axum = "0.8.1" +axum = "0.8.4" tower-cgi = { path = "../tower-cgi" } tokio = { version = "1", features = ["full"] } tower-sessions = "0.14.0" diff --git a/tower-cgi/Cargo.toml b/tower-cgi/Cargo.toml index e4dd881..5dda0f1 100644 --- a/tower-cgi/Cargo.toml +++ b/tower-cgi/Cargo.toml @@ -10,7 +10,7 @@ hyper = { version = "1.6.0", default-features = false } snafu = "0.8" tower = { version = "0.5", default-features = false, features = ["util"] } http-body-util = "0.1.2" -axum = "0.8.1" +axum = "0.8.4" [dev-dependencies] tokio = { version = "1", features = ["full"] }