use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_void};
#[repr(C)]
pub struct BextRequestCtx {
_opaque: [u8; 0],
}
extern "C" {
pub fn bext_php_module_init(ini_entries: *const c_char) -> c_int;
pub fn bext_php_module_shutdown();
pub fn bext_php_execute_script(
ctx: *mut BextRequestCtx,
script_path: *const c_char,
method: *const c_char,
uri: *const c_char,
query_string: *const c_char,
content_type: *const c_char,
content_length: i64,
) -> c_int;
pub fn bext_php_execute_worker(
initial_ctx: *mut BextRequestCtx,
worker_script_path: *const c_char,
) -> c_int;
pub fn bext_php_register_variable(
key: *const c_char,
val: *const c_char,
track_vars_array: *mut c_void,
);
}
use super::context::RequestCtx;
const MAX_OUTPUT_BYTES: usize = 256 * 1024 * 1024;
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_ub_write(
ctx: *mut BextRequestCtx,
str_ptr: *const c_char,
len: usize,
) -> usize {
if ctx.is_null() || str_ptr.is_null() {
return 0;
}
let req_ctx = &mut *(ctx as *mut RequestCtx);
if req_ctx.output_buf.len().saturating_add(len) > MAX_OUTPUT_BYTES {
return 0;
}
let bytes = std::slice::from_raw_parts(str_ptr as *const u8, len);
req_ctx.output_buf.extend_from_slice(bytes);
len
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_read_post(
ctx: *mut BextRequestCtx,
buf: *mut c_char,
count: usize,
) -> usize {
if ctx.is_null() || buf.is_null() {
return 0;
}
let req_ctx = &mut *(ctx as *mut RequestCtx);
let remaining = &req_ctx.request_body[req_ctx.body_read_pos..];
let to_read = remaining.len().min(count);
if to_read > 0 {
std::ptr::copy_nonoverlapping(remaining.as_ptr(), buf as *mut u8, to_read);
req_ctx.body_read_pos += to_read;
}
to_read
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_read_cookies(ctx: *mut BextRequestCtx) -> *mut c_char {
if ctx.is_null() {
return std::ptr::null_mut();
}
let req_ctx = &mut *(ctx as *mut RequestCtx);
match &req_ctx.cookie_header {
Some(c) => c.as_ptr() as *mut c_char,
None => std::ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_on_header(
ctx: *mut BextRequestCtx,
header: *const c_char,
header_len: usize,
) {
if ctx.is_null() || header.is_null() || header_len == 0 {
return;
}
let req_ctx = &mut *(ctx as *mut RequestCtx);
let bytes = std::slice::from_raw_parts(header as *const u8, header_len);
let header_str = String::from_utf8_lossy(bytes);
if let Some(colon_pos) = header_str.find(": ") {
let name = header_str[..colon_pos].to_string();
let value = header_str[colon_pos + 2..].to_string();
if name.contains('\r') || name.contains('\n') || name.contains('\0')
|| value.contains('\r') || value.contains('\n') || value.contains('\0')
{
return;
}
req_ctx.response_headers.push((name, value));
}
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_register_server_variables(
ctx: *mut BextRequestCtx,
track_vars_array: *mut c_void,
) {
if ctx.is_null() || track_vars_array.is_null() {
return;
}
let req_ctx = &*(ctx as *const RequestCtx);
let reg = |key: &str, val: &str| {
if let (Ok(k), Ok(v)) = (CString::new(key), CString::new(val)) {
bext_php_register_variable(k.as_ptr(), v.as_ptr(), track_vars_array);
}
};
reg("SERVER_SOFTWARE", "bext-php/0.1");
reg("GATEWAY_INTERFACE", "CGI/1.1");
reg("SERVER_PROTOCOL", "HTTP/1.1");
if let Some(ref name) = req_ctx.server_name {
let s = name.to_string_lossy();
reg("SERVER_NAME", &s);
reg("HTTP_HOST", &s);
}
reg("SERVER_PORT", &req_ctx.server_port.to_string());
if let Some(ref addr) = req_ctx.remote_addr {
let s = addr.to_string_lossy();
reg("REMOTE_ADDR", &s);
reg("REMOTE_HOST", &s);
}
if req_ctx.https {
reg("HTTPS", "on");
}
for (name, value) in &req_ctx.request_headers {
let var_name = format!("HTTP_{}", name.to_uppercase().replace('-', "_"));
reg(&var_name, value);
}
if let Some(ref doc_root) = req_ctx.document_root {
reg("DOCUMENT_ROOT", doc_root);
}
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_log_message(message: *const c_char, syslog_type: c_int) {
if message.is_null() {
return;
}
let msg = std::ffi::CStr::from_ptr(message);
let msg_str = msg.to_string_lossy();
match syslog_type {
0..=3 => tracing::error!(target: "php", "{}", msg_str),
4 => tracing::warn!(target: "php", "{}", msg_str),
5..=6 => tracing::info!(target: "php", "{}", msg_str),
_ => tracing::debug!(target: "php", "{}", msg_str),
}
}
use super::pool::WORKER_THREAD_STATE;
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_worker_wait_request(ctx_out: *mut *mut BextRequestCtx) -> c_int {
WORKER_THREAD_STATE.with(|state| {
let state = state.borrow();
if let Some(ref wts) = *state {
match wts.request_rx.recv() {
Ok(send_ptr) => {
*ctx_out = send_ptr.0 as *mut BextRequestCtx;
1
}
Err(_) => 0,
}
} else {
0
}
})
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_worker_finish_request(ctx: *mut BextRequestCtx, status: c_int) {
if ctx.is_null() {
return;
}
let req_ctx = &mut *(ctx as *mut RequestCtx);
req_ctx.status_code = status as u16;
WORKER_THREAD_STATE.with(|state| {
let state = state.borrow();
if let Some(ref wts) = *state {
let _ = wts.done_tx.send(());
}
});
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_jsc_render(
component: *const c_char,
props_json: *const c_char,
) -> *mut c_char {
if component.is_null() || props_json.is_null() {
let err = CString::new("<div>bext_render: null argument</div>").unwrap();
return err.into_raw();
}
let comp_str = std::ffi::CStr::from_ptr(component)
.to_string_lossy()
.into_owned();
let props_str = std::ffi::CStr::from_ptr(props_json)
.to_string_lossy()
.into_owned();
if !comp_str
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
{
let err = CString::new("<div style=\"color:red\">Invalid component name</div>").unwrap();
return err.into_raw();
}
let full_props = match serde_json::from_str::<serde_json::Value>(&props_str) {
Ok(mut obj) => {
if let Some(map) = obj.as_object_mut() {
map.insert(
"_component".to_string(),
serde_json::Value::String(comp_str.clone()),
);
}
serde_json::to_string(&obj)
.unwrap_or_else(|_| format!("{{\"_component\":\"{}\"}}", comp_str))
}
Err(_) => {
format!("{{\"_component\":\"{}\"}}", comp_str)
}
};
let html = super::bridge::jsc_render(&full_props);
match CString::new(html) {
Ok(cs) => cs.into_raw(),
Err(_) => {
let err = CString::new("<div>bext_render: null byte in HTML</div>").unwrap();
err.into_raw()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
let _ = CString::from_raw(ptr);
}
}
static FALLBACK_GET: &[u8] = b"GET\0";
static FALLBACK_SLASH: &[u8] = b"/\0";
static FALLBACK_EMPTY: &[u8] = b"\0";
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_get_method(ctx: *mut BextRequestCtx) -> *const c_char {
if ctx.is_null() {
return FALLBACK_GET.as_ptr() as *const c_char;
}
let req_ctx = &*(ctx as *const RequestCtx);
req_ctx
.c_method
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(FALLBACK_GET.as_ptr() as *const c_char)
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_get_uri(ctx: *mut BextRequestCtx) -> *const c_char {
if ctx.is_null() {
return FALLBACK_SLASH.as_ptr() as *const c_char;
}
let req_ctx = &*(ctx as *const RequestCtx);
req_ctx
.c_uri
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(FALLBACK_SLASH.as_ptr() as *const c_char)
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_get_query_string(ctx: *mut BextRequestCtx) -> *const c_char {
if ctx.is_null() {
return FALLBACK_EMPTY.as_ptr() as *const c_char;
}
let req_ctx = &*(ctx as *const RequestCtx);
req_ctx
.c_query_string
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(FALLBACK_EMPTY.as_ptr() as *const c_char)
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_get_content_type(ctx: *mut BextRequestCtx) -> *const c_char {
if ctx.is_null() {
return std::ptr::null();
}
let req_ctx = &*(ctx as *const RequestCtx);
req_ctx
.c_content_type
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(std::ptr::null())
}
#[no_mangle]
pub unsafe extern "C" fn bext_sapi_get_content_length(ctx: *mut BextRequestCtx) -> i64 {
if ctx.is_null() {
return 0;
}
let req_ctx = &*(ctx as *const RequestCtx);
req_ctx.request_body.len() as i64
}