extern crate php_sys;
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uchar, c_void};
use std::ptr;
use std::slice;
pub struct Runtime<T> {
callbacks: Callbacks<T>,
}
impl<T> Runtime<T> {
pub fn new(name: &str, long_name: &str, threads: usize) -> RuntimeBuilder<T> {
let threads = if threads > 0 { threads } else { 1 };
unsafe {
php_sys::tsrm_startup(threads as i32, 1, 0, ptr::null_mut());
php_sys::ts_resource_ex(0, ptr::null_mut());
php_sys::zend_tsrmls_cache_update();
php_sys::zend_signal_startup();
let mut module = Box::new(php_sys::sapi_module_struct::default());
let name = CString::new(name).unwrap();
let pretty_name = CString::new(long_name).unwrap();
module.name = name.into_raw();
module.pretty_name = pretty_name.into_raw();
module.startup = Some(sapi_server_startup::<T>);
module.shutdown = Some(sapi_server_shutdown::<T>);
module.ub_write = Some(sapi_server_ub_write::<T>);
module.flush = Some(sapi_server_flush);
module.sapi_error = Some(php_sys::zend_error);
module.send_headers = Some(sapi_server_send_headers::<T>);
module.read_post = Some(sapi_server_read_post::<T>);
module.read_cookies = Some(sapi_server_read_cookies::<T>);
module.register_server_variables = Some(sapi_server_register_variables::<T>);
module.log_message = Some(sapi_server_log_message::<T>);
RuntimeBuilder {
callbacks: Callbacks::<T> {
startup: None,
shutdown: None,
write: None,
read: None,
},
module,
}
}
}
pub fn execute(&mut self, handle_filename: &str, context: &mut T) -> Result<(), ()>
where
T: std::fmt::Debug,
{
let mode = CString::new("rb").unwrap();
unsafe {
php_sys::ts_resource_ex(0, ptr::null_mut());
let handle_filename = CString::new(handle_filename).unwrap();
let fp = php_sys::phprpm_fopen(handle_filename.as_ptr() as *const i8, mode.as_ptr());
let mut handle = php_sys::_zend_file_handle__bindgen_ty_1::default();
handle.fp = fp;
let script = Box::new(php_sys::zend_file_handle {
handle: handle,
filename: handle_filename.as_ptr() as *const i8,
opened_path: ptr::null_mut(),
type_: php_sys::zend_stream_type_ZEND_HANDLE_FP,
free_filename: 0,
});
let script_ptr = Box::into_raw(script);
(*php_sys::sg_sapi_headers()).http_response_code = 200;
let ctx = Box::new(PHPContext {
callbacks: &mut self.callbacks,
context: context,
});
let context_ptr = Box::into_raw(ctx);
php_sys::sg_set_server_context(context_ptr as *mut c_void);
php_sys::php_request_startup();
php_sys::php_execute_script(script_ptr);
drop(Box::from_raw(script_ptr));
if !(*php_sys::sg_request_info()).cookie_data.is_null() {
drop(CString::from_raw((*php_sys::sg_request_info()).cookie_data));
}
(*php_sys::sg_request_info()).cookie_data = ptr::null_mut();
drop(Box::from_raw(
php_sys::sg_server_context() as *mut PHPContext<T>
));
php_sys::sg_set_server_context(ptr::null_mut());
php_sys::php_request_shutdown(ptr::null_mut());
Ok(())
}
}
}
struct PHPContext<'ctx, T: 'ctx> {
callbacks: &'ctx mut Callbacks<T>,
context: &'ctx mut T,
}
pub type StartupCallback<T> = FnMut(&mut T) -> Result<(), ()>;
pub type ShutdownCallback<T> = FnMut(&mut T) -> Result<(), ()>;
pub type WriteCallback<T> = FnMut(&mut T, &[u8]) -> Result<usize, ()>;
pub type ReadCallback<T> = FnMut(&mut T, *mut i8, usize) -> Result<usize, ()>;
struct Callbacks<T> {
startup: Option<Box<StartupCallback<T>>>,
shutdown: Option<Box<ShutdownCallback<T>>>,
write: Option<Box<WriteCallback<T>>>,
read: Option<Box<ReadCallback<T>>>,
}
#[derive(Debug)]
pub struct IOContext {
pub buffer: Vec<u8>,
pub body: Box<[u8]>,
}
impl IOContext {
fn write(ctx: &mut IOContext, buf: &[u8]) -> Result<usize, ()> {
ctx.buffer.extend_from_slice(&buf);
Ok(buf.len())
}
fn read(ctx: &mut IOContext, buf: *mut i8, bytes: usize) -> Result<usize, ()> {
unsafe {
let ctx = ctx as *mut IOContext;
let body = &(*ctx).body;
let copied = ::std::cmp::min(bytes, body.len());
if copied > 0 {
let (to_send, to_retain) = body.split_at(copied);
let ptr = to_send.as_ptr() as *const i8;
::std::ptr::copy(ptr, buf, copied);
(*ctx).body = to_retain.to_owned().into_boxed_slice();
}
Ok(copied)
}
}
pub fn add_to_builder(builder: RuntimeBuilder<IOContext>) -> RuntimeBuilder<IOContext> {
builder
.read(Box::new(IOContext::read))
.write(Box::new(IOContext::write))
}
}
pub struct RuntimeBuilder<T> {
callbacks: Callbacks<T>,
module: Box<php_sys::_sapi_module_struct>,
}
impl<T> RuntimeBuilder<T> {
pub fn startup(mut self, callback: Box<StartupCallback<T>>) -> Self {
self.callbacks.startup = Some(callback);
self
}
pub fn shutdown(mut self, callback: Box<ShutdownCallback<T>>) -> Self {
self.callbacks.shutdown = Some(callback);
self
}
pub fn write(mut self, callback: Box<WriteCallback<T>>) -> Self {
self.callbacks.write = Some(callback);
self
}
pub fn read(mut self, callback: Box<ReadCallback<T>>) -> Self {
self.callbacks.read = Some(callback);
self
}
pub fn start(self) -> Runtime<T> {
unsafe {
let module_ptr = Box::into_raw(self.module);
php_sys::sapi_startup(module_ptr);
let request_method = CString::new("POST").unwrap();
let path_translated = CString::new("/some/file").unwrap();
let content_type = CString::new("text/html").unwrap();
(*php_sys::sg_request_info()).request_method = request_method.as_ptr();
(*php_sys::sg_request_info()).content_length = 0;
(*php_sys::sg_request_info()).path_translated = path_translated.into_raw();
(*php_sys::sg_request_info()).content_type = content_type.as_ptr();
php_sys::php_module_startup(module_ptr, ptr::null_mut(), 0);
}
Runtime {
callbacks: self.callbacks,
}
}
}
unsafe extern "C" fn sapi_server_startup<T>(_module: *mut php_sys::sapi_module_struct) -> c_int {
let context = php_sys::sg_server_context() as *mut PHPContext<T>;
if let Some(ref mut cb) = (*context).callbacks.startup {
if cb(&mut (*context).context).is_ok() {
php_sys::ZEND_RESULT_CODE_SUCCESS as c_int
} else {
php_sys::ZEND_RESULT_CODE_FAILURE as c_int
}
} else {
php_sys::ZEND_RESULT_CODE_SUCCESS as c_int
}
}
unsafe extern "C" fn sapi_server_shutdown<T>(_module: *mut php_sys::sapi_module_struct) -> c_int {
let context = php_sys::sg_server_context() as *mut PHPContext<T>;
if let Some(ref mut cb) = (*context).callbacks.shutdown {
if cb(&mut (*context).context).is_ok() {
php_sys::ZEND_RESULT_CODE_SUCCESS as c_int
} else {
php_sys::ZEND_RESULT_CODE_FAILURE as c_int
}
} else {
php_sys::ZEND_RESULT_CODE_SUCCESS as c_int
}
}
unsafe extern "C" fn sapi_server_ub_write<T>(s: *const c_char, s_len: usize) -> usize {
let context = php_sys::sg_server_context() as *mut PHPContext<T>;
if let Some(ref mut cb) = (*context).callbacks.write {
let s_: *const c_uchar = s as *const c_uchar;
let rs = slice::from_raw_parts(s_, s_len);
if let Ok(size) = cb(&mut (*context).context, rs) {
size
} else {
0
}
} else {
0
}
}
unsafe extern "C" fn sapi_server_flush(_server_context: *mut c_void) {
if php_sys::sg_headers_sent() == 1 {
return;
}
php_sys::sapi_send_headers();
}
unsafe extern "C" fn sapi_server_send_headers<T>(
_sapi_headers: *mut php_sys::sapi_headers_struct,
) -> c_int {
php_sys::sg_set_headers_sent(1);
php_sys::SAPI_HEADER_SENT_SUCCESSFULLY as c_int
}
unsafe extern "C" fn sapi_server_read_post<T>(buf: *mut c_char, bytes: usize) -> usize {
let context = php_sys::sg_server_context() as *mut PHPContext<T>;
if let Some(ref mut cb) = (*context).callbacks.read {
if let Ok(copied) = cb(&mut (*context).context, buf as *mut i8, bytes) {
copied
} else {
0
}
} else {
0
}
}
unsafe extern "C" fn sapi_server_read_cookies<T>() -> *mut c_char {
ptr::null_mut()
}
unsafe extern "C" fn sapi_server_register_variables<T>(_track_vars_array: *mut php_sys::zval) {
}
unsafe extern "C" fn sapi_server_log_message<T>(_ebmessage: *mut c_char, _syslog_type_int: c_int) {
}
#[cfg(test)]
mod tests {
use super::*;
fn run(runtime: &mut Runtime<IOContext>, s: String) -> String {
let mut ctx = IOContext {
body: s.into_bytes().into_boxed_slice(),
buffer: Vec::with_capacity(1028),
};
let d = ::std::env::current_dir().unwrap();
let d = d.join("tests/test.php");
if let Ok(result) = runtime.execute(d.to_str().unwrap(), &mut ctx) {
String::from_utf8(ctx.buffer.clone()).unwrap()
} else {
"error".into()
}
}
#[test]
fn test_execution() {
let mut runtime =
IOContext::add_to_builder(Runtime::new("php-test", "PHP Test Runtime", 1)).start();
assert_eq!(
run(&mut runtime, "hello".into()),
"php got: hello".to_string()
);
assert_eq!(
run(&mut runtime, "world".into()),
"php got: world".to_string()
);
}
}