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", 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/lib.rs b/cgi-rs/src/lib.rs index e4d9fa6..6ad77e6 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"); @@ -106,6 +108,7 @@ pub enum MetaVariableKind { HttpHost, HttpUserAgent, HttpAccept, + HttpCookie, ServerSignature, DocumentRoot, RequestScheme, @@ -148,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 63c8128..0f33293 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 { @@ -44,11 +55,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()? )) }) @@ -65,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 @@ -84,6 +107,7 @@ impl TryFrom for Request { ["Accept", MetaVariableKind::HttpAccept], ["Host", MetaVariableKind::HttpHost], ["User-Agent", MetaVariableKind::HttpUserAgent], + ["Cookie", MetaVariableKind::HttpCookie], ); request_builder 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..a3ecd8c 100644 --- a/sample-cgi-script/Cargo.toml +++ b/sample-cgi-script/Cargo.toml @@ -6,6 +6,9 @@ license = "Apache-2.0" publish = false [dependencies] -axum = "0.6" +axum = "0.8.4" 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 4495f83..af7b57b 100644 --- a/sample-cgi-script/src/main.rs +++ b/sample-cgi-script/src/main.rs @@ -1,12 +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); diff --git a/tower-cgi/Cargo.toml b/tower-cgi/Cargo.toml index 3f7d6c8..5dda0f1 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.4" [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