mirror of
https://github.com/lxsang/luafcgi.git
synced 2024-12-26 05:08:23 +01:00
The server now can handles multipart data
All checks were successful
gitea-sync/luafcgi/pipeline/head This commit looks good
All checks were successful
gitea-sync/luafcgi/pipeline/head This commit looks good
This commit is contained in:
parent
5e8b44546d
commit
2cda1b84c0
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -117,6 +117,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"toml",
|
"toml",
|
||||||
|
"twoway",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -321,6 +322,22 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "twoway"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"unchecked-index",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unchecked-index"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -14,6 +14,7 @@ serde_derive = "1.0"
|
|||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
twoway = "0.2.2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 's'
|
opt-level = 's'
|
||||||
|
504
src/lib.rs
504
src/lib.rs
@ -7,6 +7,9 @@ use std::fmt::Arguments;
|
|||||||
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write};
|
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write};
|
||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use twoway;
|
||||||
|
|
||||||
/// app author
|
/// app author
|
||||||
pub const APP_AUTHOR: &str = "Dany LE <mrsang@iohub.dev>";
|
pub const APP_AUTHOR: &str = "Dany LE <mrsang@iohub.dev>";
|
||||||
|
|
||||||
@ -15,15 +18,30 @@ pub const APP_VERSION: &str = "0.1.0";
|
|||||||
|
|
||||||
/// Application name
|
/// Application name
|
||||||
pub const DAEMON_NAME: &str = "luad";
|
pub const DAEMON_NAME: &str = "luad";
|
||||||
|
/// Magic number for lua slice structure
|
||||||
const LUA_SLICE_MAGIC: usize = 0x8AD73B9F;
|
const LUA_SLICE_MAGIC: usize = 0x8AD73B9F;
|
||||||
|
/// FastCGI frame header len
|
||||||
|
const FCGI_HEADER_LEN: usize = 8;
|
||||||
|
/// FastCGI protocol version
|
||||||
|
const FCGI_VERSION: u8 = 1;
|
||||||
|
/// Temporal location for file upload
|
||||||
|
const TMP_DIR: &str = "/tmp";
|
||||||
|
|
||||||
fn is_debug_enable() -> bool {
|
/// LOG_MASK is used to create the priority mask in setlogmask
|
||||||
match std::env::var("debug") {
|
/// For a mask UPTO specified
|
||||||
Ok(value) => return value == "1" || value == "true",
|
/// used with [Priority]
|
||||||
Err(_) => {}
|
///
|
||||||
}
|
/// # Examples
|
||||||
false
|
///
|
||||||
|
/// ```
|
||||||
|
/// LOG_UPTO!(Priority::LOG_ALERT)
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! LOG_UPTO
|
||||||
|
{
|
||||||
|
($($arg:tt)*) => (
|
||||||
|
((1 << (($($arg)*) + 1)) - 1)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop user privileges
|
/// Drop user privileges
|
||||||
@ -187,9 +205,17 @@ impl LOG {
|
|||||||
libc::LOG_CONS | libc::LOG_PID | libc::LOG_NDELAY,
|
libc::LOG_CONS | libc::LOG_PID | libc::LOG_NDELAY,
|
||||||
libc::LOG_DAEMON,
|
libc::LOG_DAEMON,
|
||||||
);
|
);
|
||||||
|
libc::setlogmask(LOG_UPTO!(libc::LOG_NOTICE));
|
||||||
}
|
}
|
||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
|
/// Enable the Log debug
|
||||||
|
///
|
||||||
|
pub fn enable_debug() {
|
||||||
|
unsafe {
|
||||||
|
libc::setlogmask(LOG_UPTO!(libc::LOG_INFO));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrapper function that log error or info message to the
|
/// Wrapper function that log error or info message to the
|
||||||
/// connected syslog server
|
/// connected syslog server
|
||||||
@ -209,12 +235,7 @@ impl LOG {
|
|||||||
LogLevel::ERROR => libc::LOG_ERR,
|
LogLevel::ERROR => libc::LOG_ERR,
|
||||||
LogLevel::WARN => libc::LOG_WARNING,
|
LogLevel::WARN => libc::LOG_WARNING,
|
||||||
LogLevel::INFO => libc::LOG_NOTICE,
|
LogLevel::INFO => libc::LOG_NOTICE,
|
||||||
_ => {
|
_ => libc::LOG_INFO,
|
||||||
if !is_debug_enable() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
libc::LOG_INFO
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
if output.write_fmt(args).is_err() {
|
if output.write_fmt(args).is_err() {
|
||||||
@ -342,9 +363,6 @@ impl EndRequestStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const FCGI_HEADER_LEN: usize = 8;
|
|
||||||
const FCGI_VERSION: u8 = 1;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum FCGIRole {
|
enum FCGIRole {
|
||||||
Responder,
|
Responder,
|
||||||
@ -449,7 +467,7 @@ impl std::fmt::Display for FcgiHeader {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum FCGIRequestState {
|
enum FCGIRequestState {
|
||||||
WaitForParams,
|
WaitForParams,
|
||||||
WaitForStdin,
|
WaitForStdin(FCGIRequestBodyState),
|
||||||
WaitForStdout,
|
WaitForStdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,7 +475,7 @@ impl std::fmt::Display for FCGIRequestState {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
let s = match self {
|
let s = match self {
|
||||||
FCGIRequestState::WaitForParams => "WaitForParams",
|
FCGIRequestState::WaitForParams => "WaitForParams",
|
||||||
FCGIRequestState::WaitForStdin => "WaitForStdin",
|
FCGIRequestState::WaitForStdin(_) => "WaitForStdin",
|
||||||
FCGIRequestState::WaitForStdout => "WaitForStdout",
|
FCGIRequestState::WaitForStdout => "WaitForStdout",
|
||||||
};
|
};
|
||||||
write!(f, "{}", s)
|
write!(f, "{}", s)
|
||||||
@ -465,11 +483,18 @@ impl std::fmt::Display for FCGIRequestState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct FGCIRequest {
|
struct FGCIRequest {
|
||||||
|
/// FastCGI params
|
||||||
params: HashMap<String, String>,
|
params: HashMap<String, String>,
|
||||||
|
/// current request ID
|
||||||
id: u16,
|
id: u16,
|
||||||
|
/// current request socket
|
||||||
fd: RawFd,
|
fd: RawFd,
|
||||||
|
/// Request data buffer
|
||||||
data: Option<Vec<u8>>,
|
data: Option<Vec<u8>>,
|
||||||
|
/// current request state
|
||||||
state: FCGIRequestState,
|
state: FCGIRequestState,
|
||||||
|
/// pending data length
|
||||||
|
pending_dlen: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -541,7 +566,7 @@ impl mlua::UserData for WSHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl WSHeader {
|
impl WSHeader {
|
||||||
fn read_from(stream: &mut FCGIOStream) -> Result<WSHeader, Box<dyn std::error::Error>> {
|
fn read_from(stream: &mut FCGIOStream) -> Result<WSHeader, std::io::Error> {
|
||||||
let mut header = WSHeader {
|
let mut header = WSHeader {
|
||||||
fin: 0,
|
fin: 0,
|
||||||
opcode: WSHeaderOpcode::Close,
|
opcode: WSHeaderOpcode::Close,
|
||||||
@ -551,7 +576,7 @@ impl WSHeader {
|
|||||||
};
|
};
|
||||||
let mut bytes = stream.stdin_read_exact(2)?;
|
let mut bytes = stream.stdin_read_exact(2)?;
|
||||||
if BITV!(bytes[0], 6) == 1 || BITV!(bytes[0], 5) == 1 || BITV!(bytes[0], 4) == 1 {
|
if BITV!(bytes[0], 6) == 1 || BITV!(bytes[0], 5) == 1 || BITV!(bytes[0], 4) == 1 {
|
||||||
return Err(Box::new(ERR!("Reserved bits 4,5,6 must be 0")));
|
return Err(ERR!("Reserved bits 4,5,6 must be 0"));
|
||||||
}
|
}
|
||||||
header.fin = BITV!(bytes[0], 7);
|
header.fin = BITV!(bytes[0], 7);
|
||||||
header.opcode = WSHeaderOpcode::from_u8(bytes[0] & 0x0F);
|
header.opcode = WSHeaderOpcode::from_u8(bytes[0] & 0x0F);
|
||||||
@ -591,10 +616,7 @@ impl WSHeader {
|
|||||||
Ok(header)
|
Ok(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_data_from(
|
fn read_data_from(&mut self, stream: &mut FCGIOStream) -> Result<Vec<u8>, std::io::Error> {
|
||||||
&mut self,
|
|
||||||
stream: &mut FCGIOStream,
|
|
||||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
|
||||||
let mut vec = stream.stdin_read_exact(self.len)?;
|
let mut vec = stream.stdin_read_exact(self.len)?;
|
||||||
if self.mask == 1 {
|
if self.mask == 1 {
|
||||||
for i in 0..vec.len() {
|
for i in 0..vec.len() {
|
||||||
@ -604,11 +626,7 @@ impl WSHeader {
|
|||||||
Ok(vec)
|
Ok(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_to(
|
fn send_to(&mut self, stream: &mut FCGIOStream, data: &[u8]) -> Result<(), std::io::Error> {
|
||||||
&mut self,
|
|
||||||
stream: &mut FCGIOStream,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let mut frame: Vec<u8>;
|
let mut frame: Vec<u8>;
|
||||||
if self.mask == 1 {
|
if self.mask == 1 {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
@ -676,7 +694,7 @@ struct FCGIOStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FCGIOStream {
|
impl FCGIOStream {
|
||||||
fn read_stdin_record(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
fn read_stdin_record(&mut self) -> Result<(), std::io::Error> {
|
||||||
if !self.ws {
|
if !self.ws {
|
||||||
WARN!("read_stdin_record is only active when the current connection is websocket");
|
WARN!("read_stdin_record is only active when the current connection is websocket");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -697,7 +715,7 @@ impl FCGIOStream {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stdin_read_exact(&mut self, len: usize) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
fn stdin_read_exact(&mut self, len: usize) -> Result<Vec<u8>, std::io::Error> {
|
||||||
while self.stdin_buffer.len() < len {
|
while self.stdin_buffer.len() < len {
|
||||||
self.read_stdin_record()?;
|
self.read_stdin_record()?;
|
||||||
}
|
}
|
||||||
@ -705,7 +723,7 @@ impl FCGIOStream {
|
|||||||
Ok(self.stdin_buffer.drain(0..len).collect::<Vec<u8>>())
|
Ok(self.stdin_buffer.drain(0..len).collect::<Vec<u8>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_record(&mut self, buf: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
|
fn write_record(&mut self, buf: Vec<u8>) -> Result<(), std::io::Error> {
|
||||||
fcgi_send_stdout(self, self.id, Some(buf))?;
|
fcgi_send_stdout(self, self.id, Some(buf))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -714,9 +732,9 @@ impl FCGIOStream {
|
|||||||
fn vec_from_variadic(
|
fn vec_from_variadic(
|
||||||
values: mlua::Variadic<LuaValue>,
|
values: mlua::Variadic<LuaValue>,
|
||||||
bin_only: bool,
|
bin_only: bool,
|
||||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
) -> Result<Vec<u8>, std::io::Error> {
|
||||||
let mut output: Vec<u8> = Vec::new();
|
let mut output: Vec<u8> = Vec::new();
|
||||||
let error = Box::new(ERR!("Unsupported data type"));
|
let error = ERR!("Unsupported data type");
|
||||||
for value in values {
|
for value in values {
|
||||||
match &value {
|
match &value {
|
||||||
LuaNil => {}
|
LuaNil => {}
|
||||||
@ -749,7 +767,9 @@ fn vec_from_variadic(
|
|||||||
}
|
}
|
||||||
LuaValue::UserData(v) => {
|
LuaValue::UserData(v) => {
|
||||||
if v.is::<LuabyteArray>() {
|
if v.is::<LuabyteArray>() {
|
||||||
let arr = v.borrow::<LuabyteArray>()?;
|
let arr = v
|
||||||
|
.borrow::<LuabyteArray>()
|
||||||
|
.map_err(|e| ERR!(e.to_string()))?;
|
||||||
output.extend(&arr.0);
|
output.extend(&arr.0);
|
||||||
} else {
|
} else {
|
||||||
let st = value.to_pointer() as *const LuaSlice;
|
let st = value.to_pointer() as *const LuaSlice;
|
||||||
@ -761,7 +781,7 @@ fn vec_from_variadic(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
LuaValue::Error(e) => {
|
LuaValue::Error(e) => {
|
||||||
return Err(Box::new(ERR!(e.to_string())));
|
return Err(ERR!(e.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1018,7 +1038,7 @@ fn fcgi_send_stderr<T: Write>(
|
|||||||
stream: &mut T,
|
stream: &mut T,
|
||||||
id: u16,
|
id: u16,
|
||||||
eopt: Option<Box<dyn std::error::Error>>,
|
eopt: Option<Box<dyn std::error::Error>>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), std::io::Error> {
|
||||||
let mut header = FcgiHeader {
|
let mut header = FcgiHeader {
|
||||||
version: FCGI_VERSION,
|
version: FCGI_VERSION,
|
||||||
kind: FCGIHeaderType::Stderr,
|
kind: FCGIHeaderType::Stderr,
|
||||||
@ -1050,7 +1070,7 @@ fn fcgi_send_stdout<T: Write>(
|
|||||||
stream: &mut T,
|
stream: &mut T,
|
||||||
id: u16,
|
id: u16,
|
||||||
dopt: Option<Vec<u8>>,
|
dopt: Option<Vec<u8>>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), std::io::Error> {
|
||||||
let mut header = FcgiHeader {
|
let mut header = FcgiHeader {
|
||||||
version: FCGI_VERSION,
|
version: FCGI_VERSION,
|
||||||
kind: FCGIHeaderType::Stdout,
|
kind: FCGIHeaderType::Stdout,
|
||||||
@ -1075,11 +1095,11 @@ fn fcgi_send_stdout<T: Write>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fcgi_send_end_request<T: Read + Write + AsRawFd>(
|
fn fcgi_send_end_request<T: Read + Write>(
|
||||||
stream: &mut T,
|
stream: &mut T,
|
||||||
id: u16,
|
id: u16,
|
||||||
status: EndRequestStatus,
|
status: EndRequestStatus,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), std::io::Error> {
|
||||||
let header = FcgiHeader {
|
let header = FcgiHeader {
|
||||||
version: FCGI_VERSION,
|
version: FCGI_VERSION,
|
||||||
kind: FCGIHeaderType::EndRequest,
|
kind: FCGIHeaderType::EndRequest,
|
||||||
@ -1093,9 +1113,157 @@ fn fcgi_send_end_request<T: Read + Write + AsRawFd>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_request<T: Read + Write + AsRawFd>(
|
#[derive(Debug)]
|
||||||
|
struct FCGIFilePart {
|
||||||
|
name: String,
|
||||||
|
tmp_path: String,
|
||||||
|
handle: std::fs::File,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum FCGIRequestBodyState {
|
||||||
|
FindBoundary(String),
|
||||||
|
HeaderDecoding(String),
|
||||||
|
DataDecoding {
|
||||||
|
boundary: String,
|
||||||
|
name: String,
|
||||||
|
file: Option<FCGIFilePart>,
|
||||||
|
vec: Vec<u8>,
|
||||||
|
},
|
||||||
|
BinaryData,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fcgi_read_stdin<T: Read + Write>(
|
||||||
stream: &mut T,
|
stream: &mut T,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
rq: &mut FGCIRequest,
|
||||||
|
header: &FcgiHeader,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
match &mut rq.state {
|
||||||
|
FCGIRequestState::WaitForStdin(body_state) => {
|
||||||
|
if header.length == 0 {
|
||||||
|
match body_state {
|
||||||
|
FCGIRequestBodyState::BinaryData => {}
|
||||||
|
__ => {
|
||||||
|
return Err(ERR!("Invalid body data"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUG!(
|
||||||
|
"All stdin records read, now wait for stdout data on request: {}",
|
||||||
|
header.id
|
||||||
|
);
|
||||||
|
rq.state = FCGIRequestState::WaitForStdout;
|
||||||
|
if let Err(error) = fcgi_execute_request_handle(rq) {
|
||||||
|
// send stderror
|
||||||
|
fcgi_send_stderr(stream, header.id, Some(error))?;
|
||||||
|
}
|
||||||
|
fcgi_send_stderr(stream, header.id, None)?;
|
||||||
|
fcgi_send_stdout(stream, header.id, None)?;
|
||||||
|
// send end connection
|
||||||
|
fcgi_send_end_request(stream, header.id, EndRequestStatus::Complete)?;
|
||||||
|
return Ok(true);
|
||||||
|
} else {
|
||||||
|
let body = fcgi_read_body(stream, &header)?;
|
||||||
|
rq.pending_dlen -= body.len() as isize;
|
||||||
|
if rq.pending_dlen < 0 {
|
||||||
|
return Err(ERR!(
|
||||||
|
"Request body is bigger than request content-length header"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let None = rq.data {
|
||||||
|
rq.data = Some(Vec::new());
|
||||||
|
}
|
||||||
|
match rq.data.take() {
|
||||||
|
Some(mut data) => {
|
||||||
|
let mut stateopt = None;
|
||||||
|
data.extend(body);
|
||||||
|
loop {
|
||||||
|
let curr_state = match &mut stateopt {
|
||||||
|
Some(st) => st,
|
||||||
|
None => body_state,
|
||||||
|
};
|
||||||
|
// decode stdin data
|
||||||
|
match fcgi_decode_stdin_data(curr_state, &mut data, &mut rq.params)? {
|
||||||
|
Some(st) => {
|
||||||
|
stateopt = Some(st);
|
||||||
|
if data.len() == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(state) = stateopt {
|
||||||
|
rq.state = FCGIRequestState::WaitForStdin(state);
|
||||||
|
}
|
||||||
|
rq.data = if data.len() == 0 { None } else { Some(data) };
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
WARN!(
|
||||||
|
"Should not receive a stdin record as the request is in {} state",
|
||||||
|
rq.state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fcgi_read_params<T: Read + Write>(
|
||||||
|
stream: &mut T,
|
||||||
|
rq: &mut FGCIRequest,
|
||||||
|
header: &FcgiHeader,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
match &mut rq.state {
|
||||||
|
FCGIRequestState::WaitForParams => {
|
||||||
|
if header.length == 0 {
|
||||||
|
DEBUG!(
|
||||||
|
"All param records read, now wait for stdin data on request: {}",
|
||||||
|
header.id
|
||||||
|
);
|
||||||
|
// get the content length
|
||||||
|
if let Some(text) = rq.params.get("CONTENT_LENGTH") {
|
||||||
|
if text.len() > 0 {
|
||||||
|
rq.pending_dlen = text.parse::<isize>().map_err(|e| ERR!(e.to_string()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(text) = rq.params.get("CONTENT_TYPE") {
|
||||||
|
// check if this is a multipart/form-data request
|
||||||
|
if let Some(_) = text.find("multipart/form-data") {
|
||||||
|
let split: Vec<&str> = text.split("boundary=").collect();
|
||||||
|
if split.len() != 2 {
|
||||||
|
return Err(ERR!("No boundary found in multipart/form-data body"));
|
||||||
|
}
|
||||||
|
DEBUG!("multipart/form-data boundary: {}", split[1].trim());
|
||||||
|
let mut boundary = "--".to_owned();
|
||||||
|
boundary.push_str(split[1].trim());
|
||||||
|
rq.state = FCGIRequestState::WaitForStdin(
|
||||||
|
FCGIRequestBodyState::FindBoundary(boundary),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
rq.state = FCGIRequestState::WaitForStdin(FCGIRequestBodyState::BinaryData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ERR!("No content type header found in the request"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fcgi_decode_params(rq, &fcgi_read_body(stream, &header)?)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__ => {
|
||||||
|
WARN!(
|
||||||
|
"Should not receive a param record as the request is in {} state",
|
||||||
|
rq.state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_request<T: Read + Write + AsRawFd>(stream: &mut T) -> Result<(), std::io::Error> {
|
||||||
let mut requests: HashMap<u16, FGCIRequest> = HashMap::new();
|
let mut requests: HashMap<u16, FGCIRequest> = HashMap::new();
|
||||||
loop {
|
loop {
|
||||||
let header = fcgi_read_header(stream)?;
|
let header = fcgi_read_header(stream)?;
|
||||||
@ -1105,7 +1273,7 @@ pub fn process_request<T: Read + Write + AsRawFd>(
|
|||||||
DEBUG!("Begin Request: {:?}, with body {:?}", header, body);
|
DEBUG!("Begin Request: {:?}, with body {:?}", header, body);
|
||||||
if body.role != FCGIRole::Responder {
|
if body.role != FCGIRole::Responder {
|
||||||
fcgi_send_end_request(stream, header.id, EndRequestStatus::UnknownRole)?;
|
fcgi_send_end_request(stream, header.id, EndRequestStatus::UnknownRole)?;
|
||||||
return Err(Box::new(ERR!("Only Responder role is supported")));
|
return Err(ERR!("Only Responder role is supported"));
|
||||||
}
|
}
|
||||||
// check if we have already request of this kind
|
// check if we have already request of this kind
|
||||||
if let Some(_) = requests.get(&header.id) {
|
if let Some(_) = requests.get(&header.id) {
|
||||||
@ -1117,78 +1285,30 @@ pub fn process_request<T: Read + Write + AsRawFd>(
|
|||||||
data: None,
|
data: None,
|
||||||
state: FCGIRequestState::WaitForParams,
|
state: FCGIRequestState::WaitForParams,
|
||||||
fd: stream.as_raw_fd(),
|
fd: stream.as_raw_fd(),
|
||||||
|
pending_dlen: 0,
|
||||||
};
|
};
|
||||||
requests.insert(header.id, rq);
|
requests.insert(header.id, rq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FCGIHeaderType::Params => {
|
FCGIHeaderType::Params => {
|
||||||
if let Some(rq) = requests.get_mut(&header.id) {
|
if let Some(rq) = requests.get_mut(&header.id) {
|
||||||
match &rq.state {
|
fcgi_read_params(stream, rq, &header).map_err(|e| {
|
||||||
FCGIRequestState::WaitForParams => {
|
let _ =
|
||||||
if header.length == 0 {
|
fcgi_send_end_request(stream, header.id, EndRequestStatus::Complete);
|
||||||
DEBUG!(
|
e
|
||||||
"All param records read, now wait for stdin data on request: {}",
|
})?;
|
||||||
header.id
|
|
||||||
);
|
|
||||||
rq.state = FCGIRequestState::WaitForStdin;
|
|
||||||
} else {
|
|
||||||
fcgi_decode_params(rq, &fcgi_read_body(stream, &header)?)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__ => {
|
|
||||||
WARN!(
|
|
||||||
"Should not receive a param record as the request is in {} state",
|
|
||||||
rq.state
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
WARN!("Uknown request {}, ignore param record", header.id);
|
WARN!("Uknown request {}, ignore param record", header.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FCGIHeaderType::Stdin => {
|
FCGIHeaderType::Stdin => {
|
||||||
if let Some(rq) = requests.get_mut(&header.id) {
|
if let Some(rq) = requests.get_mut(&header.id) {
|
||||||
match &rq.state {
|
if fcgi_read_stdin(stream, rq, &header).map_err(|e| {
|
||||||
FCGIRequestState::WaitForStdin => {
|
let _ =
|
||||||
if header.length == 0 {
|
fcgi_send_end_request(stream, header.id, EndRequestStatus::Complete);
|
||||||
DEBUG!(
|
e
|
||||||
"All stdin records read, now wait for stdout data on request: {}",
|
})? {
|
||||||
header.id
|
break;
|
||||||
);
|
|
||||||
rq.state = FCGIRequestState::WaitForStdout;
|
|
||||||
if let Err(error) = fcgi_execute_request_handle(rq) {
|
|
||||||
// send stderror
|
|
||||||
fcgi_send_stderr(stream, header.id, Some(error))?;
|
|
||||||
}
|
|
||||||
fcgi_send_stderr(stream, header.id, None)?;
|
|
||||||
fcgi_send_stdout(stream, header.id, None)?;
|
|
||||||
// send end connection
|
|
||||||
fcgi_send_end_request(
|
|
||||||
stream,
|
|
||||||
header.id,
|
|
||||||
EndRequestStatus::Complete,
|
|
||||||
)?;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
let body = fcgi_read_body(stream, &header)?;
|
|
||||||
if let None = rq.data {
|
|
||||||
rq.data = Some(Vec::new())
|
|
||||||
}
|
|
||||||
match rq.data.take() {
|
|
||||||
Some(mut data) => {
|
|
||||||
data.extend(body);
|
|
||||||
rq.data = Some(data);
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
WARN!(
|
|
||||||
"Should not receive a stdin record as the request is in {} state",
|
|
||||||
rq.state
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
WARN!("Uknow request {}, ignore stdin record", header.id);
|
WARN!("Uknow request {}, ignore stdin record", header.id);
|
||||||
@ -1206,6 +1326,173 @@ pub fn process_request<T: Read + Write + AsRawFd>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fcgi_decode_stdin_data(
|
||||||
|
state: &mut FCGIRequestBodyState,
|
||||||
|
buffer: &mut Vec<u8>,
|
||||||
|
params: &mut HashMap<String, String>,
|
||||||
|
) -> Result<Option<FCGIRequestBodyState>, Error> {
|
||||||
|
match state {
|
||||||
|
FCGIRequestBodyState::FindBoundary(boundary) => {
|
||||||
|
let mut pattern = boundary.to_string();
|
||||||
|
pattern.push_str("\r\n");
|
||||||
|
if let Some(index) = twoway::find_bytes(buffer, pattern.as_bytes()) {
|
||||||
|
let _ = buffer.drain(0..index + pattern.len());
|
||||||
|
DEBUG!("Boundary found, decoding header");
|
||||||
|
return Ok(Some(FCGIRequestBodyState::HeaderDecoding(
|
||||||
|
boundary.to_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
FCGIRequestBodyState::HeaderDecoding(boundary) => {
|
||||||
|
if let Some(end_index) = twoway::find_bytes(buffer, "\r\n\r\n".as_bytes()) {
|
||||||
|
let pattern = "Content-Disposition:";
|
||||||
|
if let Some(index) = twoway::find_bytes(&buffer[0..end_index], pattern.as_bytes()) {
|
||||||
|
// got content-disposition, get the line
|
||||||
|
let start_lime_index = index + pattern.len();
|
||||||
|
let offset = twoway::find_bytes(
|
||||||
|
&buffer[start_lime_index..end_index + 2],
|
||||||
|
"\r\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.ok_or(ERR!("Unknown ending of Content-Disposition line"))?;
|
||||||
|
let line = String::from_utf8(
|
||||||
|
buffer[start_lime_index..start_lime_index + offset].to_vec(),
|
||||||
|
)
|
||||||
|
.map_err(|e| ERR!(e.to_string()))?;
|
||||||
|
DEBUG!("Content-Disposition: {}", line);
|
||||||
|
// parsing content disposition for `name` and `file`
|
||||||
|
// name is obliged, so find it first
|
||||||
|
let mut name: Option<&str> = None;
|
||||||
|
let mut fileopt: Option<&str> = None;
|
||||||
|
|
||||||
|
for text in line.trim().split(";") {
|
||||||
|
let trimmed = text.trim();
|
||||||
|
if let Some(index) = trimmed.find("filename=") {
|
||||||
|
fileopt = Some(&text[index + 11..trimmed.len()]);
|
||||||
|
DEBUG!("Part filename = [{}]", fileopt.unwrap());
|
||||||
|
} else if let Some(index) = trimmed.find("name=") {
|
||||||
|
name = Some(&text[index + 7..trimmed.len()]);
|
||||||
|
DEBUG!("Part name = [{}]", name.unwrap());
|
||||||
|
} else {
|
||||||
|
DEBUG!("Ignore part: {}", text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let None = name {
|
||||||
|
return Err(ERR!("No name attribut found in multi-part part"));
|
||||||
|
}
|
||||||
|
let mut file = None;
|
||||||
|
if let Some(filename) = fileopt {
|
||||||
|
let tmp_path = format!(
|
||||||
|
"{}/{}.{}",
|
||||||
|
TMP_DIR,
|
||||||
|
filename,
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.map_err(|e| ERR!(e.to_string()))?
|
||||||
|
.as_millis()
|
||||||
|
);
|
||||||
|
DEBUG!("Part will be saved to: {}", &tmp_path);
|
||||||
|
file = Some(FCGIFilePart {
|
||||||
|
name: filename.to_owned(),
|
||||||
|
handle: std::fs::File::create(&tmp_path)?,
|
||||||
|
tmp_path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let part_state = FCGIRequestBodyState::DataDecoding {
|
||||||
|
boundary: boundary.to_string(),
|
||||||
|
name: name.unwrap().to_owned(),
|
||||||
|
file: file,
|
||||||
|
vec: Vec::new(),
|
||||||
|
};
|
||||||
|
DEBUG!("Header decoding finished, go to data-decoding");
|
||||||
|
let _ = buffer.drain(0..end_index + 4);
|
||||||
|
return Ok(Some(part_state));
|
||||||
|
}
|
||||||
|
let _ = buffer.drain(0..end_index + 4);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
FCGIRequestBodyState::DataDecoding {
|
||||||
|
boundary,
|
||||||
|
name,
|
||||||
|
file,
|
||||||
|
vec,
|
||||||
|
} => {
|
||||||
|
loop {
|
||||||
|
match twoway::find_bytes(buffer, "\r\n".as_bytes()) {
|
||||||
|
Some(index) => {
|
||||||
|
let mut pattern_len = boundary.len();
|
||||||
|
let mut content_data = &buffer[0..index + 2];
|
||||||
|
let mut remaining = &buffer[index + 2..];
|
||||||
|
if remaining.len() > pattern_len {
|
||||||
|
//DEBUG!("content : {:?}", content_data);
|
||||||
|
//DEBUG!("boundary: {:?}", boundary.as_bytes());
|
||||||
|
//DEBUG!("remaining : {:?}", remaining);
|
||||||
|
if remaining.starts_with(boundary.as_bytes()) {
|
||||||
|
remaining = &remaining[pattern_len..];
|
||||||
|
let state;
|
||||||
|
if remaining.starts_with("\r\n".as_bytes()) {
|
||||||
|
state = Some(FCGIRequestBodyState::HeaderDecoding(
|
||||||
|
boundary.to_owned(),
|
||||||
|
));
|
||||||
|
DEBUG!("Part Boundary end found, decoding next header");
|
||||||
|
} else if remaining.starts_with("--".as_bytes()) {
|
||||||
|
pattern_len += 2;
|
||||||
|
state = Some(FCGIRequestBodyState::BinaryData);
|
||||||
|
DEBUG!("Request Boundary end found, finish stdin read");
|
||||||
|
} else {
|
||||||
|
return Err(ERR!("Invalid boundary ending"));
|
||||||
|
}
|
||||||
|
// ignore or write to file
|
||||||
|
content_data = &buffer[0..index];
|
||||||
|
let value;
|
||||||
|
if let Some(part) = file {
|
||||||
|
value = format!(
|
||||||
|
"{{\"file\":\"{}\",\"tmp\":\"{}\"}}",
|
||||||
|
part.name, part.tmp_path
|
||||||
|
);
|
||||||
|
part.handle.write_all(content_data)?;
|
||||||
|
} else {
|
||||||
|
vec.extend(content_data);
|
||||||
|
// collect the data
|
||||||
|
value = String::from_utf8(vec.to_vec())
|
||||||
|
.map_err(|e| ERR!(e.to_string()))?;
|
||||||
|
DEBUG!("part data: {}", &value);
|
||||||
|
}
|
||||||
|
let _ = params.insert(format!("MULTIPART[{}]", name), value);
|
||||||
|
let _ = buffer.drain(0..content_data.len() + pattern_len + 4);
|
||||||
|
return Ok(state);
|
||||||
|
} else {
|
||||||
|
// ignore or write to file
|
||||||
|
if let Some(part) = file {
|
||||||
|
part.handle.write_all(content_data)?;
|
||||||
|
} else {
|
||||||
|
vec.extend(content_data);
|
||||||
|
}
|
||||||
|
let _ = buffer.drain(0..content_data.len());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// ignore or write to file
|
||||||
|
if let Some(part) = file {
|
||||||
|
part.handle.write_all(buffer)?;
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
FCGIRequestBodyState::BinaryData => Ok(None),
|
||||||
|
}
|
||||||
|
//Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn fcgi_read_header<T: Read + Write>(stream: &mut T) -> Result<FcgiHeader, Error> {
|
fn fcgi_read_header<T: Read + Write>(stream: &mut T) -> Result<FcgiHeader, Error> {
|
||||||
let mut buf = vec![0; FCGI_HEADER_LEN];
|
let mut buf = vec![0; FCGI_HEADER_LEN];
|
||||||
stream.read_exact(&mut buf)?;
|
stream.read_exact(&mut buf)?;
|
||||||
@ -1234,10 +1521,7 @@ fn fcgi_decode_strlen(data: &[u8]) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fcgi_decode_params(
|
fn fcgi_decode_params(rq: &mut FGCIRequest, data: &Vec<u8>) -> Result<(), std::io::Error> {
|
||||||
rq: &mut FGCIRequest,
|
|
||||||
data: &Vec<u8>,
|
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let mut index: usize = 1;
|
let mut index: usize = 1;
|
||||||
let key_len = fcgi_decode_strlen(data);
|
let key_len = fcgi_decode_strlen(data);
|
||||||
if key_len > 127 {
|
if key_len > 127 {
|
||||||
@ -1253,9 +1537,11 @@ fn fcgi_decode_params(
|
|||||||
//INFO!("data: {:?}", data);
|
//INFO!("data: {:?}", data);
|
||||||
//INFO!("key: {:?}", data[index..index + key_len].to_vec());
|
//INFO!("key: {:?}", data[index..index + key_len].to_vec());
|
||||||
//INFO!("Value: {:?}", data[index+key_len..index+key_len+value_len].to_vec());
|
//INFO!("Value: {:?}", data[index+key_len..index+key_len+value_len].to_vec());
|
||||||
let key = String::from_utf8(data[index..index + key_len].to_vec())?;
|
let key = String::from_utf8(data[index..index + key_len].to_vec())
|
||||||
|
.map_err(|e| ERR!(e.to_string()))?;
|
||||||
let value: String =
|
let value: String =
|
||||||
String::from_utf8(data[index + key_len..index + key_len + value_len].to_vec())?;
|
String::from_utf8(data[index + key_len..index + key_len + value_len].to_vec())
|
||||||
|
.map_err(|e| ERR!(e.to_string()))?;
|
||||||
DEBUG!("PARAM: [{}] -> [{}]", key, value);
|
DEBUG!("PARAM: [{}] -> [{}]", key, value);
|
||||||
let _ = rq.params.insert(key, value);
|
let _ = rq.params.insert(key, value);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
12
src/main.rs
12
src/main.rs
@ -157,14 +157,16 @@ fn main() {
|
|||||||
group: None,
|
group: None,
|
||||||
debug: false,
|
debug: false,
|
||||||
};
|
};
|
||||||
|
if let Ok(val) = std::env::var("debug") {
|
||||||
|
if val == "1" || val == "true" {
|
||||||
|
config.debug = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
match matches.value_of("file") {
|
match matches.value_of("file") {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
INFO!("Configuration file: {}", path);
|
INFO!("Configuration file: {}", path);
|
||||||
let contents = std::fs::read_to_string(path).unwrap();
|
let contents = std::fs::read_to_string(path).unwrap();
|
||||||
config = toml::from_str(&contents).unwrap();
|
config = toml::from_str(&contents).unwrap();
|
||||||
if config.debug {
|
|
||||||
std::env::set_var("luad_debug", "true");
|
|
||||||
}
|
|
||||||
// drop user privilege if only user and group available in
|
// drop user privilege if only user and group available in
|
||||||
// the configuration file, otherwise ignore
|
// the configuration file, otherwise ignore
|
||||||
privdrop(config.user.as_deref(), config.group.as_deref()).unwrap();
|
privdrop(config.user.as_deref(), config.group.as_deref()).unwrap();
|
||||||
@ -181,5 +183,9 @@ fn main() {
|
|||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
if config.debug {
|
||||||
|
INFO!("Debug enabled");
|
||||||
|
LOG::enable_debug();
|
||||||
|
}
|
||||||
serve(&config);
|
serve(&config);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user