diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 6c834c479..b6f206539 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -245,7 +245,6 @@ bind! { zend_class_serialize_deny, zend_class_unserialize_deny, zend_executor_globals, - sapi_globals_struct, sapi_module_struct, zend_objects_store_del, zend_hash_move_forward_ex, @@ -257,13 +256,39 @@ bind! { gc_possible_root, ZEND_ACC_NOT_SERIALIZABLE, executor_globals, + php_core_globals, + core_globals, + sapi_globals_struct, sapi_globals, sapi_module, php_printf, __zend_malloc, tsrm_get_ls_cache, executor_globals_offset, + core_globals_offset, sapi_globals_offset, + php_file_globals, + file_globals, + file_globals_id, + TRACK_VARS_POST, + TRACK_VARS_GET, + TRACK_VARS_COOKIE, + TRACK_VARS_SERVER, + TRACK_VARS_ENV, + TRACK_VARS_FILES, + TRACK_VARS_REQUEST, + sapi_request_info, + sapi_header_struct, + zend_is_auto_global, + zend_llist_get_next_ex, + zend_llist_get_prev_ex, + php_register_url_stream_wrapper, + php_stream_locate_url_wrapper, + php_unregister_url_stream_wrapper, + php_unregister_url_stream_wrapper_volatile, + php_register_url_stream_wrapper_volatile, + php_stream_wrapper, + php_stream_stdio_ops, zend_atomic_bool_store, zend_interrupt_function, zend_eval_string, diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index b25762d9d..d6678ea06 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -182,6 +182,13 @@ pub const ZEND_MODULE_API_NO: u32 = 20230831; pub const USING_ZTS: u32 = 0; pub const MAY_BE_BOOL: u32 = 12; pub const MAY_BE_ANY: u32 = 1022; +pub const TRACK_VARS_POST: u32 = 0; +pub const TRACK_VARS_GET: u32 = 1; +pub const TRACK_VARS_COOKIE: u32 = 2; +pub const TRACK_VARS_SERVER: u32 = 3; +pub const TRACK_VARS_ENV: u32 = 4; +pub const TRACK_VARS_FILES: u32 = 5; +pub const TRACK_VARS_REQUEST: u32 = 6; pub const PHP_INI_USER: u32 = 1; pub const PHP_INI_PERDIR: u32 = 2; pub const PHP_INI_SYSTEM: u32 = 4; @@ -506,6 +513,19 @@ pub struct _zend_llist { pub traverse_ptr: *mut zend_llist_element, } pub type zend_llist = _zend_llist; +pub type zend_llist_position = *mut zend_llist_element; +extern "C" { + pub fn zend_llist_get_next_ex( + l: *mut zend_llist, + pos: *mut zend_llist_position, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn zend_llist_get_prev_ex( + l: *mut zend_llist, + pos: *mut zend_llist_position, + ) -> *mut ::std::os::raw::c_void; +} pub type zend_string_init_interned_func_t = ::std::option::Option< unsafe extern "C" fn( str_: *const ::std::os::raw::c_char, @@ -1469,6 +1489,9 @@ pub struct _zend_executor_globals { pub reserved_stack_size: zend_ulong, pub reserved: [*mut ::std::os::raw::c_void; 6usize], } +extern "C" { + pub fn zend_is_auto_global(name: *mut zend_string) -> bool; +} pub type zend_module_entry = _zend_module_entry; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -2094,6 +2117,127 @@ impl _php_stream { __bindgen_bitfield_unit } } +extern "C" { + pub static mut php_stream_stdio_ops: php_stream_ops; +} +extern "C" { + pub fn php_register_url_stream_wrapper( + protocol: *const ::std::os::raw::c_char, + wrapper: *const php_stream_wrapper, + ) -> zend_result; +} +extern "C" { + pub fn php_unregister_url_stream_wrapper( + protocol: *const ::std::os::raw::c_char, + ) -> zend_result; +} +extern "C" { + pub fn php_register_url_stream_wrapper_volatile( + protocol: *mut zend_string, + wrapper: *mut php_stream_wrapper, + ) -> zend_result; +} +extern "C" { + pub fn php_unregister_url_stream_wrapper_volatile(protocol: *mut zend_string) -> zend_result; +} +extern "C" { + pub fn php_stream_locate_url_wrapper( + path: *const ::std::os::raw::c_char, + path_for_open: *mut *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + ) -> *mut php_stream_wrapper; +} +pub type php_core_globals = _php_core_globals; +#[repr(C)] +pub struct _php_core_globals { + pub output_buffering: zend_long, + pub implicit_flush: bool, + pub enable_dl: bool, + pub display_errors: u8, + pub display_startup_errors: bool, + pub log_errors: bool, + pub ignore_repeated_errors: bool, + pub ignore_repeated_source: bool, + pub report_memleaks: bool, + pub output_handler: *mut ::std::os::raw::c_char, + pub unserialize_callback_func: *mut ::std::os::raw::c_char, + pub serialize_precision: zend_long, + pub memory_limit: zend_long, + pub max_input_time: zend_long, + pub error_log: *mut ::std::os::raw::c_char, + pub doc_root: *mut ::std::os::raw::c_char, + pub user_dir: *mut ::std::os::raw::c_char, + pub include_path: *mut ::std::os::raw::c_char, + pub open_basedir: *mut ::std::os::raw::c_char, + pub open_basedir_modified: bool, + pub extension_dir: *mut ::std::os::raw::c_char, + pub php_binary: *mut ::std::os::raw::c_char, + pub sys_temp_dir: *mut ::std::os::raw::c_char, + pub upload_tmp_dir: *mut ::std::os::raw::c_char, + pub upload_max_filesize: zend_long, + pub error_append_string: *mut ::std::os::raw::c_char, + pub error_prepend_string: *mut ::std::os::raw::c_char, + pub auto_prepend_file: *mut ::std::os::raw::c_char, + pub auto_append_file: *mut ::std::os::raw::c_char, + pub input_encoding: *mut ::std::os::raw::c_char, + pub internal_encoding: *mut ::std::os::raw::c_char, + pub output_encoding: *mut ::std::os::raw::c_char, + pub arg_separator: arg_separators, + pub variables_order: *mut ::std::os::raw::c_char, + pub rfc1867_protected_variables: HashTable, + pub connection_status: ::std::os::raw::c_short, + pub ignore_user_abort: bool, + pub header_is_being_sent: ::std::os::raw::c_uchar, + pub tick_functions: zend_llist, + pub http_globals: [zval; 6usize], + pub expose_php: bool, + pub register_argc_argv: bool, + pub auto_globals_jit: bool, + pub html_errors: bool, + pub xmlrpc_errors: bool, + pub docref_root: *mut ::std::os::raw::c_char, + pub docref_ext: *mut ::std::os::raw::c_char, + pub xmlrpc_error_number: zend_long, + pub activated_auto_globals: [bool; 8usize], + pub modules_activated: bool, + pub file_uploads: bool, + pub during_request_startup: bool, + pub allow_url_fopen: bool, + pub enable_post_data_reading: bool, + pub report_zend_debug: bool, + pub last_error_type: ::std::os::raw::c_int, + pub last_error_lineno: ::std::os::raw::c_int, + pub last_error_message: *mut zend_string, + pub last_error_file: *mut zend_string, + pub php_sys_temp_dir: *mut ::std::os::raw::c_char, + pub disable_classes: *mut ::std::os::raw::c_char, + pub max_input_nesting_level: zend_long, + pub max_input_vars: zend_long, + pub user_ini_filename: *mut ::std::os::raw::c_char, + pub user_ini_cache_ttl: zend_long, + pub request_order: *mut ::std::os::raw::c_char, + pub mail_log: *mut ::std::os::raw::c_char, + pub mail_x_header: bool, + pub mail_mixed_lf_and_crlf: bool, + pub in_error_log: bool, + pub allow_url_include: bool, + pub in_user_include: bool, + pub have_called_openlog: bool, + pub syslog_facility: zend_long, + pub syslog_ident: *mut ::std::os::raw::c_char, + pub syslog_filter: zend_long, + pub error_log_mode: zend_long, +} +extern "C" { + pub static mut core_globals: _php_core_globals; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _arg_separators { + pub output: *mut ::std::os::raw::c_char, + pub input: *mut ::std::os::raw::c_char, +} +pub type arg_separators = _arg_separators; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_ini_entry_def { @@ -2201,6 +2345,37 @@ extern "C" { extern "C" { pub fn php_info_print_table_end(); } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct hostent { + pub h_name: *mut ::std::os::raw::c_char, + pub h_aliases: *mut *mut ::std::os::raw::c_char, + pub h_addrtype: ::std::os::raw::c_int, + pub h_length: ::std::os::raw::c_int, + pub h_addr_list: *mut *mut ::std::os::raw::c_char, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct php_file_globals { + pub pclose_ret: ::std::os::raw::c_int, + pub def_chunk_size: usize, + pub auto_detect_line_endings: bool, + pub default_socket_timeout: zend_long, + pub user_agent: *mut ::std::os::raw::c_char, + pub from_address: *mut ::std::os::raw::c_char, + pub user_stream_current_filename: *const ::std::os::raw::c_char, + pub default_context: *mut php_stream_context, + pub stream_wrappers: *mut HashTable, + pub stream_filters: *mut HashTable, + pub wrapper_errors: *mut HashTable, + pub pclose_wait: ::std::os::raw::c_int, + pub tmp_host_info: hostent, + pub tmp_host_buf: *mut ::std::os::raw::c_char, + pub tmp_host_buf_len: usize, +} +extern "C" { + pub static mut file_globals: php_file_globals; +} extern "C" { pub static mut zend_ce_throwable: *mut zend_class_entry; } diff --git a/src/error.rs b/src/error.rs index 5c4c9ceed..071e99758 100644 --- a/src/error.rs +++ b/src/error.rs @@ -65,6 +65,10 @@ pub enum Error { IntegerOverflow, /// An exception was thrown in a function. Exception(ZBox), + /// A failure occurred while registering the stream wrapper + StreamWrapperRegistrationFailure, + /// A failure occurred while unregistering the stream wrapper + StreamWrapperUnregistrationFailure, } impl Display for Error { @@ -99,6 +103,15 @@ impl Display for Error { write!(f, "Converting integer arguments resulted in an overflow.") } Error::Exception(e) => write!(f, "Exception was thrown: {e:?}"), + Error::StreamWrapperRegistrationFailure => { + write!(f, "A failure occurred while registering the stream wrapper") + } + Error::StreamWrapperUnregistrationFailure => { + write!( + f, + "A failure occurred while unregistering the stream wrapper" + ) + } } } } diff --git a/src/exception.rs b/src/exception.rs index bf06487b7..aee0d9099 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -69,8 +69,8 @@ impl PhpException { /// Set the Zval object for the exception. /// - /// Exceptions can be based of instantiated Zval objects when you are throwing a custom exception with - /// stateful properties. + /// Exceptions can be based of instantiated Zval objects when you are + /// throwing a custom exception with stateful properties. /// /// # Parameters /// diff --git a/src/ffi.rs b/src/ffi.rs index 343b1fada..28ba62594 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -26,7 +26,9 @@ extern "C" { pub fn ext_php_rs_zend_object_alloc(obj_size: usize, ce: *mut zend_class_entry) -> *mut c_void; pub fn ext_php_rs_zend_object_release(obj: *mut zend_object); pub fn ext_php_rs_executor_globals() -> *mut zend_executor_globals; + pub fn ext_php_rs_process_globals() -> *mut php_core_globals; pub fn ext_php_rs_sapi_globals() -> *mut sapi_globals_struct; + pub fn ext_php_rs_file_globals() -> *mut php_file_globals; pub fn ext_php_rs_sapi_module() -> *mut sapi_module_struct; pub fn ext_php_rs_zend_try_catch( func: unsafe extern "C" fn(*const c_void) -> *const c_void, diff --git a/src/types/zval.rs b/src/types/zval.rs index 97def9a09..b0ceec77c 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -222,7 +222,8 @@ impl Zval { } } - /// Returns a mutable reference to the zval if it is an internal indirect reference. + /// Returns a mutable reference to the zval if it is an internal indirect + /// reference. pub fn indirect_mut(&self) -> Option<&mut Zval> { if self.is_indirect() { Some(unsafe { &mut *(self.value.zv as *mut Zval) }) diff --git a/src/wrapper.c b/src/wrapper.c index 109b5aee9..fcb0c2822 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -40,6 +40,18 @@ zend_executor_globals *ext_php_rs_executor_globals() { #endif } +php_core_globals *ext_php_rs_process_globals() { +#ifdef ZTS +#ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE + return TSRMG_FAST_BULK_STATIC(core_globals_offset, php_core_globals); +#else + return TSRMG_FAST_BULK(core_globals_offset, php_core_globals *); +#endif +#else + return &core_globals; +#endif +} + sapi_globals_struct *ext_php_rs_sapi_globals() { #ifdef ZTS #ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE @@ -52,6 +64,14 @@ sapi_globals_struct *ext_php_rs_sapi_globals() { #endif } +php_file_globals *ext_php_rs_file_globals() { +#ifdef ZTS + return TSRMG_FAST_BULK(file_globals_id, php_file_globals *); +#else + return &file_globals; +#endif +} + sapi_module_struct *ext_php_rs_sapi_module() { return &sapi_module; } diff --git a/src/wrapper.h b/src/wrapper.h index 883f1b812..9aef14f15 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -17,9 +17,12 @@ #include "php.h" #include "ext/standard/info.h" +#include "ext/standard/php_var.h" +#include "ext/standard/file.h" #include "zend_exceptions.h" #include "zend_inheritance.h" #include "zend_interfaces.h" +#include "php_variables.h" #include "zend_ini.h" #include "main/SAPI.h" @@ -31,8 +34,10 @@ void ext_php_rs_set_known_valid_utf8(zend_string *zs); const char *ext_php_rs_php_build_id(); void *ext_php_rs_zend_object_alloc(size_t obj_size, zend_class_entry *ce); void ext_php_rs_zend_object_release(zend_object *obj); -zend_executor_globals *ext_php_rs_executor_globals();; +zend_executor_globals *ext_php_rs_executor_globals(); +php_core_globals *ext_php_rs_process_globals(); sapi_globals_struct *ext_php_rs_sapi_globals(); +php_file_globals *ext_php_rs_file_globals(); sapi_module_struct *ext_php_rs_sapi_module(); bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result); void ext_php_rs_zend_bailout(); diff --git a/src/zend/globals.rs b/src/zend/globals.rs index e455a224d..046195fc5 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,7 +1,10 @@ -//! Types related to the PHP executor globals. +//! Types related to the PHP executor, sapi and process globals. use std::collections::HashMap; +use std::ffi::CStr; use std::ops::{Deref, DerefMut}; +use std::slice; +use std::str; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -9,17 +12,21 @@ use crate::boxed::ZBox; #[cfg(php82)] use crate::ffi::zend_atomic_bool_store; use crate::ffi::{ - _sapi_globals_struct, _sapi_module_struct, _zend_executor_globals, ext_php_rs_executor_globals, - ext_php_rs_sapi_globals, ext_php_rs_sapi_module, zend_ini_entry, + _sapi_module_struct, _zend_executor_globals, ext_php_rs_executor_globals, + ext_php_rs_file_globals, ext_php_rs_process_globals, ext_php_rs_sapi_globals, + ext_php_rs_sapi_module, php_core_globals, php_file_globals, sapi_globals_struct, + sapi_header_struct, sapi_headers_struct, sapi_request_info, zend_ini_entry, + zend_is_auto_global, TRACK_VARS_COOKIE, TRACK_VARS_ENV, TRACK_VARS_FILES, TRACK_VARS_GET, + TRACK_VARS_POST, TRACK_VARS_REQUEST, TRACK_VARS_SERVER, }; -use crate::types::{ZendHashTable, ZendObject}; + +use crate::types::{ZendHashTable, ZendObject, ZendStr}; + +use super::linked_list::ZendLinkedListIterator; /// Stores global variables used in the PHP executor. pub type ExecutorGlobals = _zend_executor_globals; -/// Stores global SAPI variables used in the PHP executor. -pub type SapiGlobals = _sapi_globals_struct; - /// Stores the SAPI module used in the PHP executor. pub type SapiModule = _sapi_module_struct; @@ -146,8 +153,8 @@ impl ExecutorGlobals { } } -impl SapiGlobals { - /// Returns a reference to the PHP SAPI globals. +impl SapiModule { + /// Returns a reference to the PHP SAPI module. /// /// The executor globals are guarded by a RwLock. There can be multiple /// immutable references at one time but only ever one mutable reference. @@ -157,9 +164,9 @@ impl SapiGlobals { pub fn get() -> GlobalReadGuard { // SAFETY: PHP executor globals are statically declared therefore should never // return an invalid pointer. - let globals = unsafe { ext_php_rs_sapi_globals().as_ref() } + let globals = unsafe { ext_php_rs_sapi_module().as_ref() } .expect("Static executor globals were invalid"); - let guard = SAPI_LOCK.read(); + let guard = SAPI_MODULE_LOCK.read(); GlobalReadGuard { globals, guard } } @@ -173,27 +180,121 @@ impl SapiGlobals { pub fn get_mut() -> GlobalWriteGuard { // SAFETY: PHP executor globals are statically declared therefore should never // return an invalid pointer. - let globals = unsafe { ext_php_rs_sapi_globals().as_mut() } + let globals = unsafe { ext_php_rs_sapi_module().as_mut() } .expect("Static executor globals were invalid"); - let guard = SAPI_LOCK.write(); + let guard = SAPI_MODULE_LOCK.write(); GlobalWriteGuard { globals, guard } } } -impl SapiModule { - /// Returns a reference to the PHP SAPI module. +/// Stores global variables used in the PHP executor. +pub type ProcessGlobals = php_core_globals; + +impl ProcessGlobals { + /// Returns a reference to the PHP process globals. + /// + /// The process globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &*ext_php_rs_process_globals() }; + let guard = PROCESS_GLOBALS_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. /// /// The executor globals are guarded by a RwLock. There can be multiple /// immutable references at one time but only ever one mutable reference. /// Attempting to retrieve the globals while already holding the global /// guard will lead to a deadlock. Dropping the globals guard will release /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &mut *ext_php_rs_process_globals() }; + let guard = PROCESS_GLOBALS_LOCK.write(); + GlobalWriteGuard { globals, guard } + } + + /// Get the HTTP Server variables. Equivalent of $_SERVER. + pub fn http_server_vars(&self) -> Option<&ZendHashTable> { + // $_SERVER is lazy-initted, we need to call zend_is_auto_global + // if it's not already populated. + if !self.http_globals[TRACK_VARS_SERVER as usize].is_array() { + let name = ZendStr::new("_SERVER", false).as_mut_ptr(); + unsafe { zend_is_auto_global(name) }; + } + if self.http_globals[TRACK_VARS_SERVER as usize].is_array() { + self.http_globals[TRACK_VARS_SERVER as usize].array() + } else { + None + } + } + + /// Get the HTTP POST variables. Equivalent of $_POST. + pub fn http_post_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_POST as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP GET variables. Equivalent of $_GET. + pub fn http_get_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_GET as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Cookie variables. Equivalent of $_COOKIE. + pub fn http_cookie_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_COOKIE as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Request variables. Equivalent of $_REQUEST. + pub fn http_request_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_REQUEST as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Environment variables. Equivalent of $_ENV. + pub fn http_env_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_ENV as usize] + .array() + .expect("Type is not a ZendArray") + } + + /// Get the HTTP Files variables. Equivalent of $_FILES. + pub fn http_files_vars(&self) -> &ZendHashTable { + self.http_globals[TRACK_VARS_FILES as usize] + .array() + .expect("Type is not a ZendArray") + } +} + +/// Stores global variables used in the SAPI. +pub type SapiGlobals = sapi_globals_struct; + +impl SapiGlobals { + /// Returns a reference to the PHP process globals. + /// + /// The process globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. pub fn get() -> GlobalReadGuard { // SAFETY: PHP executor globals are statically declared therefore should never // return an invalid pointer. - let globals = unsafe { ext_php_rs_sapi_module().as_ref() } - .expect("Static executor globals were invalid"); - let guard = SAPI_MODULE_LOCK.read(); + let globals = unsafe { &*ext_php_rs_sapi_globals() }; + let guard = SAPI_GLOBALS_LOCK.read(); GlobalReadGuard { globals, guard } } @@ -207,11 +308,205 @@ impl SapiModule { pub fn get_mut() -> GlobalWriteGuard { // SAFETY: PHP executor globals are statically declared therefore should never // return an invalid pointer. - let globals = unsafe { ext_php_rs_sapi_module().as_mut() } - .expect("Static executor globals were invalid"); - let guard = SAPI_MODULE_LOCK.write(); + let globals = unsafe { &mut *ext_php_rs_sapi_globals() }; + let guard = SAPI_GLOBALS_LOCK.write(); GlobalWriteGuard { globals, guard } } + // Get the request info for the Sapi. + pub fn request_info(&self) -> &SapiRequestInfo { + &self.request_info + } + + pub fn sapi_headers(&self) -> &SapiHeaders { + &self.sapi_headers + } +} + +pub type SapiHeaders = sapi_headers_struct; + +impl<'a> SapiHeaders { + pub fn headers(&'a mut self) -> ZendLinkedListIterator<'a, SapiHeader> { + self.headers.iter() + } +} + +pub type SapiHeader = sapi_header_struct; + +impl<'a> SapiHeader { + pub fn as_str(&'a self) -> &'a str { + unsafe { + let slice = slice::from_raw_parts(self.header as *const u8, self.header_len); + str::from_utf8(slice).expect("Invalid header string") + } + } + + pub fn name(&'a self) -> &'a str { + self.as_str().split(':').next().unwrap_or("").trim() + } + + pub fn value(&'a self) -> Option<&'a str> { + self.as_str().split(':').nth(1).map(|s| s.trim()) + } +} + +pub type SapiRequestInfo = sapi_request_info; + +impl SapiRequestInfo { + pub fn request_method(&self) -> Option<&str> { + if self.request_method.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.request_method).to_str().ok() } + } + + pub fn query_string(&self) -> Option<&str> { + if self.query_string.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.query_string).to_str().ok() } + } + + pub fn cookie_data(&self) -> Option<&str> { + if self.cookie_data.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.cookie_data).to_str().ok() } + } + + pub fn content_length(&self) -> i64 { + self.content_length + } + + pub fn path_translated(&self) -> Option<&str> { + if self.path_translated.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.path_translated).to_str().ok() } + } + + pub fn request_uri(&self) -> Option<&str> { + if self.request_uri.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.request_uri).to_str().ok() } + } + + // Todo: request_body _php_stream + + pub fn content_type(&self) -> Option<&str> { + if self.content_type.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.content_type).to_str().ok() } + } + + pub fn headers_only(&self) -> bool { + self.headers_only + } + + pub fn no_headers(&self) -> bool { + self.no_headers + } + + pub fn headers_read(&self) -> bool { + self.headers_read + } + + // Todo: post_entry sapi_post_entry + + pub fn auth_user(&self) -> Option<&str> { + if self.auth_user.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.auth_user).to_str().ok() } + } + + pub fn auth_password(&self) -> Option<&str> { + if self.auth_password.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.auth_password).to_str().ok() } + } + + pub fn auth_digest(&self) -> Option<&str> { + if self.auth_digest.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.auth_digest).to_str().ok() } + } + + pub fn argv0(&self) -> Option<&str> { + if self.argv0.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.argv0).to_str().ok() } + } + + pub fn current_user(&self) -> Option<&str> { + if self.current_user.is_null() { + return None; + } + unsafe { CStr::from_ptr(self.current_user).to_str().ok() } + } + + pub fn current_user_length(&self) -> i32 { + self.current_user_length + } + + pub fn argvc(&self) -> i32 { + self.argc + } + + pub fn argv(&self) -> Option<&str> { + if self.argv.is_null() { + return None; + } + unsafe { CStr::from_ptr(*self.argv).to_str().ok() } + } + + pub fn proto_num(&self) -> i32 { + self.proto_num + } +} + +/// Stores global variables used in the SAPI. +pub type FileGlobals = php_file_globals; + +impl FileGlobals { + /// Returns a reference to the PHP process globals. + /// + /// The process globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_file_globals().as_ref() } + .expect("Static file globals were invalid"); + let guard = FILE_GLOBALS_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { &mut *ext_php_rs_file_globals() }; + let guard = SAPI_GLOBALS_LOCK.write(); + GlobalWriteGuard { globals, guard } + } + + pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> { + unsafe { self.stream_wrappers.as_ref() } + } } /// Executor globals rwlock. @@ -219,12 +514,9 @@ impl SapiModule { /// PHP provides no indication if the executor globals are being accessed so /// this is only effective on the Rust side. static GLOBALS_LOCK: RwLock<()> = const_rwlock(()); - -/// SAPI globals rwlock. -/// -/// PHP provides no indication if the executor globals are being accessed so -/// this is only effective on the Rust side. -static SAPI_LOCK: RwLock<()> = const_rwlock(()); +static PROCESS_GLOBALS_LOCK: RwLock<()> = const_rwlock(()); +static SAPI_GLOBALS_LOCK: RwLock<()> = const_rwlock(()); +static FILE_GLOBALS_LOCK: RwLock<()> = const_rwlock(()); /// SAPI globals rwlock. /// diff --git a/src/zend/linked_list.rs b/src/zend/linked_list.rs new file mode 100644 index 000000000..481b22979 --- /dev/null +++ b/src/zend/linked_list.rs @@ -0,0 +1,46 @@ +use std::marker::PhantomData; + +use crate::ffi::{zend_llist, zend_llist_element, zend_llist_get_next_ex}; + +pub type ZendLinkedList = zend_llist; + +impl ZendLinkedList { + pub fn iter(&self) -> ZendLinkedListIterator { + ZendLinkedListIterator::new(self) + } +} + +pub struct ZendLinkedListIterator<'a, T> { + list: &'a zend_llist, + position: *mut zend_llist_element, + _marker: PhantomData, +} + +impl<'a, T> ZendLinkedListIterator<'a, T> { + fn new(list: &'a ZendLinkedList) -> Self { + ZendLinkedListIterator { + list, + position: list.head, + _marker: PhantomData, + } + } +} + +impl<'a, T: 'a> Iterator for ZendLinkedListIterator<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.position.is_null() { + return None; + } + let ptr = unsafe { (*self.position).data.as_mut_ptr() }; + let value = unsafe { &*(ptr as *const T as *mut T) }; + unsafe { + zend_llist_get_next_ex( + self.list as *const ZendLinkedList as *mut ZendLinkedList, + &mut self.position, + ) + }; + Some(value) + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index a46215311..357f2934a 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -8,7 +8,9 @@ mod function; mod globals; mod handlers; mod ini_entry_def; +mod linked_list; mod module; +mod streams; mod try_catch; use crate::{ @@ -23,11 +25,15 @@ pub use ex::ExecuteData; pub use function::Function; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; +pub use globals::FileGlobals; +pub use globals::ProcessGlobals; pub use globals::SapiGlobals; pub use globals::SapiModule; pub use handlers::ZendObjectHandlers; pub use ini_entry_def::IniEntryDef; +pub use linked_list::ZendLinkedList; pub use module::ModuleEntry; +pub use streams::*; #[cfg(feature = "embed")] pub(crate) use try_catch::panic_wrapper; pub use try_catch::{bailout, try_catch}; diff --git a/src/zend/streams.rs b/src/zend/streams.rs new file mode 100644 index 000000000..8ea5da228 --- /dev/null +++ b/src/zend/streams.rs @@ -0,0 +1,101 @@ +use std::ptr::{self, NonNull}; + +use crate::{ + error::Error, + ffi::{ + php_register_url_stream_wrapper, php_register_url_stream_wrapper_volatile, php_stream, + php_stream_context, php_stream_locate_url_wrapper, php_stream_wrapper, + php_stream_wrapper_ops, php_unregister_url_stream_wrapper, + php_unregister_url_stream_wrapper_volatile, zend_string, + }, + types::ZendStr, +}; + +pub type StreamWrapper = php_stream_wrapper; + +pub type StreamOpener = unsafe extern "C" fn( + *mut StreamWrapper, + *const std::ffi::c_char, + *const std::ffi::c_char, + i32, + *mut *mut zend_string, + *mut php_stream_context, + i32, + *const std::ffi::c_char, + u32, + *const std::ffi::c_char, + u32, +) -> *mut Stream; + +impl StreamWrapper { + pub fn get(name: &str) -> Option<&Self> { + unsafe { + let result = php_stream_locate_url_wrapper(name.as_ptr().cast(), ptr::null_mut(), 0); + Some(NonNull::new(result)?.as_ref()) + } + } + + pub fn get_mut(name: &str) -> Option<&mut Self> { + unsafe { + let result = php_stream_locate_url_wrapper(name.as_ptr().cast(), ptr::null_mut(), 0); + Some(NonNull::new(result)?.as_mut()) + } + } + + pub fn register(self, name: &str) -> Result { + // We have to convert it to a static so owned streamwrapper doesn't get dropped. + let copy = Box::new(self); + let copy = Box::leak(copy); + let name = std::ffi::CString::new(name).expect("Could not create C string for name!"); + let result = unsafe { php_register_url_stream_wrapper(name.as_ptr(), copy) }; + if result == 0 { + Ok(*copy) + } else { + Err(Error::StreamWrapperRegistrationFailure) + } + } + + pub fn register_volatile(self, name: &str) -> Result { + // We have to convert it to a static so owned streamwrapper doesn't get dropped. + let copy = Box::new(self); + let copy = Box::leak(copy); + let name = ZendStr::new(name, false); + let result = + unsafe { php_register_url_stream_wrapper_volatile((*name).as_ptr() as _, copy) }; + if result == 0 { + Ok(*copy) + } else { + Err(Error::StreamWrapperRegistrationFailure) + } + } + + pub fn unregister(name: &str) -> Result<(), Error> { + let name = std::ffi::CString::new(name).expect("Could not create C string for name!"); + match unsafe { php_unregister_url_stream_wrapper(name.as_ptr()) } { + 0 => Ok(()), + _ => Err(Error::StreamWrapperUnregistrationFailure), + } + } + + pub fn unregister_volatile(name: &str) -> Result<(), Error> { + let name = ZendStr::new(name, false); + match unsafe { php_unregister_url_stream_wrapper_volatile((*name).as_ptr() as _) } { + 0 => Ok(()), + _ => Err(Error::StreamWrapperUnregistrationFailure), + } + } + + pub fn wops(&self) -> &php_stream_wrapper_ops { + unsafe { &*self.wops } + } + + pub fn wops_mut(&mut self) -> &mut php_stream_wrapper_ops { + unsafe { &mut *(self.wops as *mut php_stream_wrapper_ops) } + } +} + +pub type Stream = php_stream; + +pub type StreamWrapperOps = php_stream_wrapper_ops; + +impl StreamWrapperOps {}