use crate::builders::SapiBuilder;
use crate::embed::SapiModule;
use crate::embed::context::ServerContext;
use crate::embed::server_vars::ServerVarRegistrar;
use crate::error::Result;
use crate::ffi::{ext_php_rs_sapi_globals, sapi_header_struct, sapi_headers_struct};
use crate::types::Zval;
use std::ffi::{c_char, c_int, c_void};
pub struct SapiHeaders {
raw: *mut sapi_headers_struct,
}
impl std::fmt::Debug for SapiHeaders {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SapiHeaders")
.field("http_response_code", &self.http_response_code())
.finish()
}
}
impl SapiHeaders {
#[must_use]
pub fn from_raw(raw: *mut sapi_headers_struct) -> Self {
Self { raw }
}
#[must_use]
pub fn http_response_code(&self) -> i32 {
unsafe { (*self.raw).http_response_code }
}
}
pub struct SapiHeader {
raw: *mut sapi_header_struct,
}
impl std::fmt::Debug for SapiHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SapiHeader")
.field("header", &self.as_str())
.finish()
}
}
impl SapiHeader {
#[must_use]
pub fn from_raw(raw: *mut sapi_header_struct) -> Self {
Self { raw }
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
let raw = unsafe { &*self.raw };
if raw.header.is_null() || raw.header_len == 0 {
return None;
}
let bytes = unsafe { std::slice::from_raw_parts(raw.header.cast::<u8>(), raw.header_len) };
std::str::from_utf8(bytes).ok()
}
#[must_use]
pub fn as_name_value(&self) -> Option<(&str, &str)> {
let s = self.as_str()?;
let (name, value) = s.split_once(':')?;
Some((name.trim(), value.trim()))
}
#[must_use]
pub fn len(&self) -> usize {
unsafe { (*self.raw).header_len }
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[non_exhaustive]
pub enum SendHeadersResult {
SentSuccessfully,
DoSend,
Failed,
}
impl SendHeadersResult {
fn into_c_int(self) -> c_int {
match self {
Self::SentSuccessfully => 1,
Self::DoSend => 2,
Self::Failed => 3,
}
}
}
pub trait Sapi: Send + Sync + 'static {
type Context: ServerContext;
fn name() -> &'static str;
fn pretty_name() -> &'static str;
fn ub_write(ctx: &mut Self::Context, buf: &[u8]) -> usize;
fn log_message(message: &str, syslog_type: i32);
fn flush(_ctx: &mut Self::Context) {}
fn send_headers(_ctx: &mut Self::Context, _headers: &SapiHeaders) -> SendHeadersResult {
SendHeadersResult::DoSend
}
fn send_header(_ctx: &mut Self::Context, _header: &SapiHeader) {}
fn read_post(ctx: &mut Self::Context, buf: &mut [u8]) -> usize {
ctx.read_post(buf)
}
fn read_cookies(ctx: &mut Self::Context) -> Option<String> {
ctx.read_cookies().map(String::from)
}
fn register_server_variables(_ctx: &mut Self::Context, _registrar: &mut ServerVarRegistrar) {}
fn build_module() -> Result<SapiModule>
where
Self: Sized,
{
SapiBuilder::new(Self::name(), Self::pretty_name())
.ub_write_function(trampoline_ub_write::<Self>)
.log_message_function(trampoline_log_message::<Self>)
.flush_function(trampoline_flush::<Self>)
.send_headers_function(trampoline_send_headers::<Self>)
.send_header_function(trampoline_send_header::<Self>)
.read_post_function(trampoline_read_post::<Self>)
.read_cookies_function(trampoline_read_cookies::<Self>)
.register_server_variables_function(trampoline_register_server_variables::<Self>)
.build()
}
}
fn get_server_context<S: Sapi>() -> Option<&'static mut S::Context> {
let globals = unsafe { &*ext_php_rs_sapi_globals() };
let ctx_ptr = globals.server_context;
if ctx_ptr.is_null() {
return None;
}
Some(unsafe { &mut *ctx_ptr.cast::<S::Context>() })
}
extern "C" fn trampoline_ub_write<S: Sapi>(str: *const c_char, str_length: usize) -> usize {
if str.is_null() || str_length == 0 {
return 0;
}
let Some(ctx) = get_server_context::<S>() else {
return 0;
};
let buf = unsafe { std::slice::from_raw_parts(str.cast::<u8>(), str_length) };
S::ub_write(ctx, buf)
}
extern "C" fn trampoline_log_message<S: Sapi>(message: *const c_char, syslog_type: c_int) {
if message.is_null() {
return;
}
let msg = unsafe { std::ffi::CStr::from_ptr(message) };
let msg_str = msg.to_string_lossy();
S::log_message(&msg_str, syslog_type);
}
extern "C" fn trampoline_flush<S: Sapi>(server_context: *mut c_void) {
let _ = server_context;
if let Some(ctx) = get_server_context::<S>() {
S::flush(ctx);
}
}
extern "C" fn trampoline_send_headers<S: Sapi>(sapi_headers: *mut sapi_headers_struct) -> c_int {
if sapi_headers.is_null() {
return SendHeadersResult::Failed.into_c_int();
}
let Some(ctx) = get_server_context::<S>() else {
return SendHeadersResult::Failed.into_c_int();
};
let headers = SapiHeaders::from_raw(sapi_headers);
S::send_headers(ctx, &headers).into_c_int()
}
extern "C" fn trampoline_send_header<S: Sapi>(
header: *mut sapi_header_struct,
_server_context: *mut c_void,
) {
if header.is_null() {
return;
}
if let Some(ctx) = get_server_context::<S>() {
let header = SapiHeader::from_raw(header);
S::send_header(ctx, &header);
}
}
extern "C" fn trampoline_read_post<S: Sapi>(buffer: *mut c_char, length: usize) -> usize {
if buffer.is_null() || length == 0 {
return 0;
}
let Some(ctx) = get_server_context::<S>() else {
return 0;
};
let buf = unsafe { std::slice::from_raw_parts_mut(buffer.cast::<u8>(), length) };
S::read_post(ctx, buf)
}
extern "C" fn trampoline_read_cookies<S: Sapi>() -> *mut c_char {
let Some(ctx) = get_server_context::<S>() else {
return std::ptr::null_mut();
};
match S::read_cookies(ctx) {
Some(cookies) => match std::ffi::CString::new(cookies) {
Ok(c) => c.into_raw(),
Err(_) => std::ptr::null_mut(),
},
None => std::ptr::null_mut(),
}
}
extern "C" fn trampoline_register_server_variables<S: Sapi>(vars: *mut Zval) {
if vars.is_null() {
return;
}
let Some(ctx) = get_server_context::<S>() else {
return;
};
let mut registrar = unsafe { ServerVarRegistrar::from_raw(vars) };
S::register_server_variables(ctx, &mut registrar);
}