Skip to content
Open
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[workspace]
resolver = "2"
members = [
"tower-cgi",
"cgi-rs",
Expand Down
6 changes: 4 additions & 2 deletions cgi-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021"

[dependencies]
hyper = "0.14"
snafu = "0.8"
http-body-util = "0.1"
hyper = "1"
snafu = "0.9"
tokio = "1"
bytes = "1"
10 changes: 8 additions & 2 deletions cgi-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@
//! ## 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.
//! std::env::set_var("REQUEST_METHOD", "GET");
//! std::env::set_var("CONTENT_LENGTH", "0");
//! std::env::set_var("REQUEST_URI", "/");
//!
//! let cgi_request: Request<Body> = CGIRequest::from_env()
//! let cgi_request: Request<Full<Bytes>> = CGIRequest::<Full<Bytes>>::from_env()
//! .and_then(Request::try_from).unwrap();
//!
//! assert_eq!(cgi_request.method(), "GET");
Expand Down Expand Up @@ -103,9 +105,11 @@ pub enum MetaVariableKind {

// Not in the RFC, but emitted from httpd's mod_cgi
UniqueID,
HttpAuthorization,
HttpHost,
HttpUserAgent,
HttpAccept,
HttpCookie,
ServerSignature,
DocumentRoot,
RequestScheme,
Expand All @@ -119,6 +123,7 @@ pub enum MetaVariableKind {
impl MetaVariableKind {
fn as_str(&self) -> &'static str {
match self {
MetaVariableKind::HttpAuthorization => "HTTP_AUTHORIZATION",
MetaVariableKind::AuthType => "AUTH_TYPE",
MetaVariableKind::ContentLength => "CONTENT_LENGTH",
MetaVariableKind::ContentType => "CONTENT_TYPE",
Expand Down Expand Up @@ -148,6 +153,7 @@ impl MetaVariableKind {
MetaVariableKind::ScriptFilename => "SCRIPT_FILENAME",
MetaVariableKind::RemotePort => "REMOTE_PORT",
MetaVariableKind::RequestUri => "REQUEST_URI",
MetaVariableKind::HttpCookie => "HTTP_COOKIE",
}
}

Expand Down
47 changes: 35 additions & 12 deletions cgi-rs/src/request.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use crate::{error, CGIError, MetaVariable, MetaVariableKind, Result};
use hyper::{Body as HttpBody, Request};
use crate::{error, MetaVariable, MetaVariableKind, Result};
use http_body_util::Full;
use hyper::body::{Body, Bytes};
use hyper::Request;
use snafu::ResultExt;
use std::io::{stdin, Read};

pub struct CGIRequest {
pub request_body: HttpBody,
pub struct CGIRequest<B> {
pub request_body: B,
}

impl CGIRequest {
pub fn from_env() -> Result<Self> {
impl<B> CGIRequest<B>
where
B: Body,
{
pub fn from_env() -> Result<CGIRequest<Full<Bytes>>> {
let content_length = MetaVariableKind::ContentLength
.from_env()
.map(|content_length| {
Expand All @@ -19,8 +24,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<MetaVariable> {
Expand All @@ -44,11 +56,17 @@ 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()?
))
})
Expand All @@ -65,10 +83,13 @@ macro_rules! try_set_headers {
};
}

impl TryFrom<CGIRequest> for Request<HttpBody> {
type Error = CGIError;
impl<B> TryFrom<CGIRequest<B>> for Request<B>
where
B: Body,
{
type Error = crate::CGIError;

fn try_from(cgi_request: CGIRequest) -> Result<Self> {
fn try_from(cgi_request: CGIRequest<B>) -> Result<Self> {
let mut request_builder = Request::builder()
.method(
cgi_request
Expand All @@ -81,9 +102,11 @@ impl TryFrom<CGIRequest> for Request<HttpBody> {
request_builder,
cgi_request,
["Content-Length", MetaVariableKind::ContentLength],
["Authorization", MetaVariableKind::HttpAuthorization],
["Accept", MetaVariableKind::HttpAccept],
["Host", MetaVariableKind::HttpHost],
["User-Agent", MetaVariableKind::HttpUserAgent],
["Cookie", MetaVariableKind::HttpCookie],
);

request_builder
Expand Down
42 changes: 13 additions & 29 deletions cgi-rs/src/response.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use crate::{error, CGIError, Result};
use hyper::{http::HeaderValue, HeaderMap, Response};
use crate::{error, Result};
use bytes::Bytes;
use hyper::{http::HeaderValue, HeaderMap};
use snafu::ResultExt;
use std::io::Write;

#[derive(Debug)]
pub struct CGIResponse<B: hyper::body::HttpBody> {
headers: HeaderMap<HeaderValue>,
status: String,
reason: Option<String>,
body: B,
pub struct CGIResponse {
pub headers: HeaderMap<HeaderValue>,
pub status: String,
pub reason: Option<String>,
pub body: Bytes,
}

impl<B: hyper::body::HttpBody> CGIResponse<B> {
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?;
Expand Down Expand Up @@ -50,29 +51,12 @@ impl<B: hyper::body::HttpBody> CGIResponse<B> {
}

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<B: hyper::body::HttpBody> TryFrom<Response<B>> for CGIResponse<B> {
type Error = CGIError;

fn try_from(response: Response<B>) -> Result<Self> {
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,
})
}
}
8 changes: 8 additions & 0 deletions http-cgi-server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "http-cgi-server"
version = "0.1.0"
edition = "2021"

[dependencies]

[workspace]
Loading