Skip to content

Commit

Permalink
Fixed FCGI implementation in PHP plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
w-henderson committed Oct 2, 2021
1 parent 9bd7964 commit e017bf3
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 26 deletions.
4 changes: 4 additions & 0 deletions plugins/php/src/fcgi/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ impl Params for HashMap<String, String> {
result.push(name.len() as u8);
} else {
result.extend((name.len() as u32).to_be_bytes());
let len = result.len();
result[len - 4] |= 0x80;
}

if value.len() < 128 {
result.push(value.len() as u8);
} else {
result.extend((value.len() as u32).to_be_bytes());
let len = result.len();
result[len - 4] |= 0x80;
}

result.extend(name.as_bytes());
Expand Down
22 changes: 11 additions & 11 deletions plugins/php/src/fcgi/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,19 @@ impl FcgiRecord {
}
}

impl Into<Vec<u8>> for FcgiRecord {
fn into(self) -> Vec<u8> {
let length = FCGI_HEADER_SIZE as u16 + self.content_length + self.padding_length as u16;
impl From<FcgiRecord> for Vec<u8> {
fn from(val: FcgiRecord) -> Self {
let length = FCGI_HEADER_SIZE as u16 + val.content_length + val.padding_length as u16;
let mut result: Vec<u8> = Vec::with_capacity(length as usize);

result.push(self.version);
result.push(self.fcgi_type as u8);
result.extend(self.request_id.to_be_bytes());
result.extend(self.content_length.to_be_bytes());
result.push(self.padding_length);
result.push(self.reserved);
result.extend(self.content_data);
result.extend(self.padding_data);
result.push(val.version);
result.push(val.fcgi_type as u8);
result.extend(val.request_id.to_be_bytes());
result.extend(val.content_length.to_be_bytes());
result.push(val.padding_length);
result.push(val.reserved);
result.extend(val.content_data);
result.extend(val.padding_data);

result
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/php/src/fcgi/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl<'a> FcgiRequest<'a> {

// Add the params record if it exists, then add an empty one
let params: Vec<u8> = self.params.encode();
if params.len() > 0 {
if !params.is_empty() {
let params_record: Vec<u8> =
FcgiRecord::new(FcgiType::Params, &params, request_id).into();
request.extend(params_record);
Expand All @@ -42,7 +42,7 @@ impl<'a> FcgiRequest<'a> {
request.extend(blank_params_record);

// Add the content record if it exists, then add an empty one
if self.content.len() > 0 {
if !self.content.is_empty() {
let content_record: Vec<u8> =
FcgiRecord::new(FcgiType::Stdin, self.content, request_id).into();
request.extend(content_record);
Expand Down
63 changes: 50 additions & 13 deletions plugins/php/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use humphrey::http::headers::ResponseHeaderMap;
use humphrey::http::headers::{RequestHeader, ResponseHeaderMap};
use humphrey::http::{Request, Response, StatusCode};

use humphrey_server::config::extended_hashmap::ExtendedMap;
use humphrey_server::config::Config;
use humphrey_server::declare_plugin;
use humphrey_server::plugins::plugin::{Plugin, PluginLoadResult};
use humphrey_server::route::try_find_path;
use humphrey_server::server::server::AppState;

use std::collections::{BTreeMap, HashMap};
use std::convert::TryFrom;
use std::io::Write;
use std::net::{Shutdown, TcpStream};
use std::sync::atomic::{AtomicUsize, Ordering};
Expand Down Expand Up @@ -91,14 +91,17 @@ impl Plugin for PhpPlugin {
let mut params: HashMap<String, String> = HashMap::new();
params.insert("GATEWAY_INTERFACE".into(), "FastCGI/1.0".into());
params.insert("REQUEST_METHOD".into(), request.method.to_string());
params.insert("SCRIPT_NAME".into(), request.uri.clone());
params.insert("REQUEST_URI".into(), format!("/{}", request.uri));
params.insert("SCRIPT_NAME".into(), format!("/{}", request.uri));
params.insert("SCRIPT_FILENAME".into(), file_name);
params.insert("QUERY_STRING".into(), request.query.clone());
params.insert("SERVER_SOFTWARE".into(), "Humphrey".into());
params.insert("REMOTE_ADDR".into(), "127.0.0.1".into());
params.insert("SERVER_NAME".into(), "127.0.0.1".into());
params.insert("SERVER_NAME".into(), "localhost".into());
params.insert("SERVER_PORT".into(), "80".into());
params.insert("SERVER_PROTOCOL".into(), "HTTP/1.1".into());
params.insert("PHP_SELF".into(), format!("/{}", request.uri));
params.insert("DOCUMENT_ROOT".into(), directory.into());
params.insert(
"CONTENT_LENGTH".into(),
request
Expand All @@ -109,6 +112,19 @@ impl Plugin for PhpPlugin {
.to_string(),
);

// Forward supported headers
for (forwarded_header, param_name) in [
(RequestHeader::Host, "HTTP_HOST"),
(RequestHeader::ContentType, "CONTENT_TYPE"),
(RequestHeader::Cookie, "HTTP_COOKIE"),
(RequestHeader::UserAgent, "HTTP_USER_AGENT"),
(RequestHeader::Authorization, "HTTP_AUTHORIZATION"),
] {
if let Some(value) = request.headers.get(&forwarded_header) {
params.insert(param_name.into(), value.clone());
}
}

// Generate the FCGI request
let empty_vec = Vec::new();
let fcgi_request =
Expand All @@ -133,7 +149,9 @@ impl Plugin for PhpPlugin {
loop {
match FcgiRecord::read_from(&*stream) {
Ok(record) => {
if record.fcgi_type != FcgiType::Stdout {
if record.fcgi_type != FcgiType::Stdout
&& record.fcgi_type != FcgiType::Stderr
{
break;
}
records.push(record);
Expand All @@ -148,37 +166,56 @@ impl Plugin for PhpPlugin {
}
}

// Assume the content to be UTF-8 and parse the headers
let content = std::str::from_utf8(&records[0].content_data).unwrap();
// Assume the content to be UTF-8 and load it all
let mut content_bytes: Vec<u8> = Vec::new();
records.iter().fold(&mut content_bytes, |acc, val| {
acc.extend(&val.content_data);
acc
});

// Parse the headers
let content = std::str::from_utf8(&content_bytes).unwrap();
let mut headers: ResponseHeaderMap = BTreeMap::new();
let mut content_split = content.splitn(2, "\r\n\r\n");
let mut status = StatusCode::OK;

for line in content_split.next().unwrap().lines() {
let mut line_split = line.splitn(2, ":");
let mut line_split = line.splitn(2, ':');
let name = line_split.next().unwrap().trim();
let value = line_split.next().map(|s| s.trim());

if let Some(value) = value {
headers.insert(name.into(), value.into());
if name == "Status" {
if let Ok(new_status) = StatusCode::try_from(
value.split(' ').next().unwrap().parse::<u16>().unwrap(),
) {
status = new_status;
}
} else {
headers.insert(name.into(), value.into());
}
}
}

// Create a response
let mut response = Response::new(StatusCode::OK)
let mut response = Response::new(status.clone())
.with_bytes(content_split.next().unwrap_or("").as_bytes().to_vec());

// Add the headers
response.headers = headers;

let status_code_number: u16 = status.clone().into();
let status_code_string: &str = status.into();

state.logger.info(&format!(
"{}: 200 OK (PHP Plugin) {}",
request.address, request.uri
"{}: {} {} (PHP Plugin) {}",
request.address, status_code_number, status_code_string, request.uri
));

// Add final headers and return the response
Some(
response
.with_request_compatibility(&request)
.with_request_compatibility(request)
.with_generated_headers(),
)
} else {
Expand Down

0 comments on commit e017bf3

Please sign in to comment.