use parking_lot::{ArcRwLockReadGuard, ArcRwLockWriteGuard, RawRwLock, RwLock};
use std::collections::HashMap;
use std::ffi::CStr;
use std::ops::{Deref, DerefMut};
use std::slice;
use std::str;
use std::sync::{Arc, LazyLock};
use crate::boxed::ZBox;
use crate::exception::PhpResult;
#[cfg(php82)]
use crate::ffi::zend_atomic_bool_store;
use crate::ffi::{
_sapi_module_struct, _zend_compiler_globals, _zend_executor_globals, TRACK_VARS_COOKIE,
TRACK_VARS_ENV, TRACK_VARS_FILES, TRACK_VARS_GET, TRACK_VARS_POST, TRACK_VARS_SERVER,
ext_php_rs_compiler_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,
};
#[cfg(not(php81))]
use crate::ffi::{_zend_hash_find_known_hash, _zend_string};
#[cfg(php81)]
use crate::ffi::{
_zend_known_string_id_ZEND_STR_AUTOGLOBAL_REQUEST, zend_hash_find_known_hash,
zend_known_strings,
};
use crate::types::{ZendHashTable, ZendObject, ZendStr};
use super::linked_list::ZendLinkedListIterator;
pub type ExecutorGlobals = _zend_executor_globals;
impl ExecutorGlobals {
pub fn get() -> GlobalReadGuard<Self> {
let globals = unsafe { ext_php_rs_executor_globals().as_ref() }
.expect("Static executor globals were invalid");
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::GLOBALS_LOCK.with(RwLock::read_arc);
} else {
let guard = lock::GLOBALS_LOCK.read_arc();
}
}
GlobalReadGuard { globals, guard }
}
pub fn get_mut() -> GlobalWriteGuard<Self> {
let globals = unsafe { ext_php_rs_executor_globals().as_mut() }
.expect("Static executor globals were invalid");
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::GLOBALS_LOCK.with(RwLock::write_arc);
} else {
let guard = lock::GLOBALS_LOCK.write_arc();
}
}
GlobalWriteGuard { globals, guard }
}
#[must_use]
pub fn class_table(&self) -> Option<&ZendHashTable> {
unsafe { self.class_table.as_ref() }
}
#[must_use]
pub fn function_table(&self) -> Option<&ZendHashTable> {
unsafe { self.function_table.as_ref() }
}
#[allow(clippy::mut_from_ref)]
#[must_use]
pub fn function_table_mut(&self) -> Option<&mut ZendHashTable> {
unsafe { self.function_table.as_mut() }
}
#[must_use]
pub fn ini_values(&self) -> HashMap<String, Option<String>> {
let hash_table = unsafe { &*self.ini_directives };
let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
for (key, value) in hash_table {
ini_hash_map.insert(key.to_string(), unsafe {
let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
if ini_entry.value.is_null() {
None
} else {
Some(
(*ini_entry.value)
.as_str()
.expect("Ini value is not a string")
.to_owned(),
)
}
});
}
ini_hash_map
}
#[must_use]
pub fn constants(&self) -> Option<&ZendHashTable> {
unsafe { self.zend_constants.as_ref() }
}
#[must_use]
pub fn take_exception() -> Option<ZBox<ZendObject>> {
{
if Self::get().exception.is_null() {
return None;
}
}
let mut globals = Self::get_mut();
let mut exception_ptr = std::ptr::null_mut();
std::mem::swap(&mut exception_ptr, &mut globals.exception);
Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) })
}
#[must_use]
pub fn has_exception() -> bool {
!Self::get().exception.is_null()
}
pub fn throw_if_exception() -> PhpResult<()> {
if let Some(e) = Self::take_exception() {
Err(crate::error::Error::Exception(e).into())
} else {
Ok(())
}
}
pub fn request_interrupt(&mut self) {
cfg_if::cfg_if! {
if #[cfg(php82)] {
unsafe {
zend_atomic_bool_store(&raw mut self.vm_interrupt, true);
}
} else {
self.vm_interrupt = true;
}
}
}
pub fn cancel_interrupt(&mut self) {
cfg_if::cfg_if! {
if #[cfg(php82)] {
unsafe {
zend_atomic_bool_store(&raw mut self.vm_interrupt, false);
}
} else {
self.vm_interrupt = true;
}
}
}
}
pub type CompilerGlobals = _zend_compiler_globals;
impl CompilerGlobals {
pub fn get() -> GlobalReadGuard<Self> {
let globals = unsafe { ext_php_rs_compiler_globals().as_ref() }
.expect("Static compiler globals were invalid");
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::GLOBALS_LOCK.with(RwLock::read_arc);
} else {
let guard = lock::GLOBALS_LOCK.read_arc();
}
}
GlobalReadGuard { globals, guard }
}
pub fn get_mut() -> GlobalWriteGuard<Self> {
let globals = unsafe { ext_php_rs_compiler_globals().as_mut() }
.expect("Static compiler globals were invalid");
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::GLOBALS_LOCK.with(RwLock::write_arc);
} else {
let guard = lock::GLOBALS_LOCK.write_arc();
}
}
GlobalWriteGuard { globals, guard }
}
}
pub type SapiModule = _sapi_module_struct;
impl SapiModule {
pub fn get() -> GlobalReadGuard<Self> {
let globals = unsafe { ext_php_rs_sapi_module().as_ref() }
.expect("Static executor globals were invalid");
let guard = SAPI_MODULE_LOCK.read_arc();
GlobalReadGuard { globals, guard }
}
pub fn get_mut() -> GlobalWriteGuard<Self> {
let globals = unsafe { ext_php_rs_sapi_module().as_mut() }
.expect("Static executor globals were invalid");
let guard = SAPI_MODULE_LOCK.write_arc();
GlobalWriteGuard { globals, guard }
}
}
pub type ProcessGlobals = php_core_globals;
impl ProcessGlobals {
pub fn get() -> GlobalReadGuard<Self> {
let globals = unsafe { &*ext_php_rs_process_globals() };
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::PROCESS_GLOBALS_LOCK.with(RwLock::read_arc);
} else {
let guard = lock::PROCESS_GLOBALS_LOCK.read_arc();
}
}
GlobalReadGuard { globals, guard }
}
pub fn get_mut() -> GlobalWriteGuard<Self> {
let globals = unsafe { &mut *ext_php_rs_process_globals() };
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::PROCESS_GLOBALS_LOCK.with(RwLock::write_arc);
} else {
let guard = lock::PROCESS_GLOBALS_LOCK.write_arc();
}
}
GlobalWriteGuard { globals, guard }
}
#[must_use]
pub fn http_server_vars(&self) -> Option<&ZendHashTable> {
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
}
}
#[must_use]
pub fn http_post_vars(&self) -> &ZendHashTable {
self.http_globals[TRACK_VARS_POST as usize]
.array()
.expect("Type is not a ZendArray")
}
#[must_use]
pub fn http_get_vars(&self) -> &ZendHashTable {
self.http_globals[TRACK_VARS_GET as usize]
.array()
.expect("Type is not a ZendArray")
}
#[must_use]
pub fn http_cookie_vars(&self) -> &ZendHashTable {
self.http_globals[TRACK_VARS_COOKIE as usize]
.array()
.expect("Type is not a ZendArray")
}
pub fn http_request_vars(&self) -> Option<&ZendHashTable> {
cfg_if::cfg_if! {
if #[cfg(php81)] {
let key = unsafe {
*zend_known_strings.add(_zend_known_string_id_ZEND_STR_AUTOGLOBAL_REQUEST as usize)
};
} else {
let key = _zend_string::new("_REQUEST", false).as_mut_ptr();
}
};
assert!(
unsafe { zend_is_auto_global(key) },
"Failed to get request global"
);
let symbol_table = &ExecutorGlobals::get().symbol_table;
cfg_if::cfg_if! {
if #[cfg(php81)] {
let request = unsafe { zend_hash_find_known_hash(symbol_table, key) };
} else {
let request = unsafe { _zend_hash_find_known_hash(symbol_table, key) };
}
};
if request.is_null() {
return None;
}
Some(unsafe { (*request).array() }.expect("Type is not a ZendArray"))
}
#[must_use]
pub fn http_env_vars(&self) -> &ZendHashTable {
self.http_globals[TRACK_VARS_ENV as usize]
.array()
.expect("Type is not a ZendArray")
}
#[must_use]
pub fn http_files_vars(&self) -> &ZendHashTable {
self.http_globals[TRACK_VARS_FILES as usize]
.array()
.expect("Type is not a ZendArray")
}
}
pub type SapiGlobals = sapi_globals_struct;
impl SapiGlobals {
#[must_use]
pub fn get() -> GlobalReadGuard<Self> {
let globals = unsafe { &*ext_php_rs_sapi_globals() };
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::SAPI_GLOBALS_LOCK.with(RwLock::read_arc);
} else {
let guard = lock::SAPI_GLOBALS_LOCK.read_arc();
}
}
GlobalReadGuard { globals, guard }
}
pub fn get_mut() -> GlobalWriteGuard<Self> {
let globals = unsafe { &mut *ext_php_rs_sapi_globals() };
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::SAPI_GLOBALS_LOCK.with(RwLock::write_arc);
} else {
let guard = lock::SAPI_GLOBALS_LOCK.write_arc();
}
}
GlobalWriteGuard { globals, guard }
}
#[must_use]
pub fn request_info(&self) -> &SapiRequestInfo {
&self.request_info
}
#[must_use]
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 {
#[must_use]
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")
}
}
#[must_use]
pub fn name(&'a self) -> &'a str {
self.as_str().split(':').next().unwrap_or("").trim()
}
#[must_use]
pub fn value(&'a self) -> Option<&'a str> {
self.as_str().split_once(':').map(|(_, value)| value.trim())
}
}
pub type SapiRequestInfo = sapi_request_info;
impl SapiRequestInfo {
#[must_use]
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() }
}
#[must_use]
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() }
}
#[must_use]
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() }
}
#[must_use]
pub fn content_length(&self) -> i64 {
self.content_length
}
#[must_use]
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() }
}
#[must_use]
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() }
}
#[must_use]
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() }
}
#[must_use]
pub fn headers_only(&self) -> bool {
self.headers_only
}
#[must_use]
pub fn no_headers(&self) -> bool {
self.no_headers
}
#[must_use]
pub fn headers_read(&self) -> bool {
self.headers_read
}
#[must_use]
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() }
}
#[must_use]
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() }
}
#[must_use]
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() }
}
#[must_use]
pub fn argv0(&self) -> Option<&str> {
if self.argv0.is_null() {
return None;
}
unsafe { CStr::from_ptr(self.argv0).to_str().ok() }
}
#[must_use]
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() }
}
#[must_use]
pub fn current_user_length(&self) -> i32 {
self.current_user_length
}
#[must_use]
pub fn argvc(&self) -> i32 {
self.argc
}
#[must_use]
pub fn argv(&self) -> Option<&str> {
if self.argv.is_null() {
return None;
}
unsafe { CStr::from_ptr(*self.argv).to_str().ok() }
}
#[must_use]
pub fn proto_num(&self) -> i32 {
self.proto_num
}
}
pub type FileGlobals = php_file_globals;
impl FileGlobals {
pub fn get() -> GlobalReadGuard<Self> {
let globals = unsafe { ext_php_rs_file_globals().as_ref() }
.expect("Static file globals were invalid");
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::FILE_GLOBALS_LOCK.with(RwLock::read_arc);
} else {
let guard = lock::FILE_GLOBALS_LOCK.read_arc();
}
}
GlobalReadGuard { globals, guard }
}
#[must_use]
pub fn get_mut() -> GlobalWriteGuard<Self> {
let globals = unsafe { &mut *ext_php_rs_file_globals() };
cfg_if::cfg_if! {
if #[cfg(php_zts)] {
let guard = lock::FILE_GLOBALS_LOCK.with(RwLock::write_arc);
} else {
let guard = lock::FILE_GLOBALS_LOCK.write_arc();
}
}
GlobalWriteGuard { globals, guard }
}
#[must_use]
pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> {
unsafe { self.stream_wrappers.as_ref() }
}
}
#[cfg(not(php_zts))]
pub(crate) mod lock {
use parking_lot::RwLock;
use std::sync::{Arc, LazyLock};
pub(crate) static GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
LazyLock::new(|| Arc::new(RwLock::new(())));
pub(crate) static PROCESS_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
LazyLock::new(|| Arc::new(RwLock::new(())));
pub(crate) static SAPI_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
LazyLock::new(|| Arc::new(RwLock::new(())));
pub(crate) static FILE_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
LazyLock::new(|| Arc::new(RwLock::new(())));
}
#[cfg(php_zts)]
pub(crate) mod lock {
use parking_lot::{RwLock, const_rwlock};
use std::sync::Arc;
thread_local! {
pub(crate) static GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new(const_rwlock(()));
pub(crate) static PROCESS_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
pub(crate) static SAPI_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
pub(crate) static FILE_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
}
}
static SAPI_MODULE_LOCK: LazyLock<Arc<RwLock<()>>> = LazyLock::new(|| Arc::new(RwLock::new(())));
pub struct GlobalReadGuard<T: 'static> {
globals: &'static T,
#[allow(dead_code)]
guard: ArcRwLockReadGuard<RawRwLock, ()>,
}
impl<T> Deref for GlobalReadGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.globals
}
}
pub struct GlobalWriteGuard<T: 'static> {
globals: &'static mut T,
#[allow(dead_code)]
guard: ArcRwLockWriteGuard<RawRwLock, ()>,
}
impl<T> Deref for GlobalWriteGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.globals
}
}
impl<T> DerefMut for GlobalWriteGuard<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.globals
}
}
#[cfg(feature = "embed")]
#[cfg(test)]
mod embed_tests {
use super::*;
use crate::embed::Embed;
use std::os::raw::c_char;
#[test]
fn test_sapi_header() {
Embed::run(|| {
let headers = [
("Content-Type: text/html", "Content-Type", "text/html"),
("X: Custom:Header", "X", "Custom:Header"),
];
for (header_text, name, value) in headers {
let header = SapiHeader {
header: header_text.as_bytes().as_ptr() as *mut c_char,
header_len: header_text.len(),
};
assert_eq!(header.name(), name, "Header name mismatch");
assert_eq!(header.value(), Some(value), "Header value mismatch");
assert_eq!(
header.as_str(),
format!("{name}: {value}"),
"Header string mismatch"
);
}
});
}
#[test]
fn test_executor_globals() {
Embed::run(|| {
let state = ExecutorGlobals::get().active;
ExecutorGlobals::get_mut().active = !state;
let changed = ExecutorGlobals::get().active;
ExecutorGlobals::get_mut().active = state;
assert_eq!(changed, !state);
});
}
#[test]
fn test_compiler_globals() {
Embed::run(|| {
let state = CompilerGlobals::get().in_compilation;
CompilerGlobals::get_mut().in_compilation = !state;
let changed = CompilerGlobals::get().in_compilation;
CompilerGlobals::get_mut().in_compilation = state;
assert_eq!(changed, !state);
});
}
}