ext_php_rs/zend/
globals.rs

1//! Types related to the PHP executor, sapi and process globals.
2
3use parking_lot::{ArcRwLockReadGuard, ArcRwLockWriteGuard, RawRwLock, RwLock};
4use std::collections::HashMap;
5use std::ffi::CStr;
6use std::ops::{Deref, DerefMut};
7use std::slice;
8use std::str;
9use std::sync::{Arc, LazyLock};
10
11use crate::boxed::ZBox;
12use crate::exception::PhpResult;
13#[cfg(php82)]
14use crate::ffi::zend_atomic_bool_store;
15use crate::ffi::{
16    _sapi_module_struct, _zend_compiler_globals, _zend_executor_globals,
17    ext_php_rs_compiler_globals, ext_php_rs_executor_globals, ext_php_rs_file_globals,
18    ext_php_rs_process_globals, ext_php_rs_sapi_globals, ext_php_rs_sapi_module, php_core_globals,
19    php_file_globals, sapi_globals_struct, sapi_header_struct, sapi_headers_struct,
20    sapi_request_info, zend_ini_entry, zend_is_auto_global, TRACK_VARS_COOKIE, TRACK_VARS_ENV,
21    TRACK_VARS_FILES, TRACK_VARS_GET, TRACK_VARS_POST, TRACK_VARS_SERVER,
22};
23#[cfg(not(php81))]
24use crate::ffi::{_zend_hash_find_known_hash, _zend_string};
25#[cfg(php81)]
26use crate::ffi::{
27    _zend_known_string_id_ZEND_STR_AUTOGLOBAL_REQUEST, zend_hash_find_known_hash,
28    zend_known_strings,
29};
30
31use crate::types::{ZendHashTable, ZendObject, ZendStr};
32
33use super::linked_list::ZendLinkedListIterator;
34
35/// Stores global variables used in the PHP executor.
36pub type ExecutorGlobals = _zend_executor_globals;
37
38impl ExecutorGlobals {
39    /// Returns a reference to the PHP executor globals.
40    ///
41    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
42    /// immutable references at one time but only ever one mutable reference.
43    /// Attempting to retrieve the globals while already holding the global
44    /// guard will lead to a deadlock. Dropping the globals guard will release
45    /// the lock.
46    ///
47    /// # Panics
48    ///
49    /// * If static executor globals are not set
50    pub fn get() -> GlobalReadGuard<Self> {
51        // SAFETY: PHP executor globals are statically declared therefore should never
52        // return an invalid pointer.
53        let globals = unsafe { ext_php_rs_executor_globals().as_ref() }
54            .expect("Static executor globals were invalid");
55
56        cfg_if::cfg_if! {
57            if #[cfg(php_zts)] {
58                let guard = lock::GLOBALS_LOCK.with(RwLock::read_arc);
59            } else {
60                let guard = lock::GLOBALS_LOCK.read_arc();
61            }
62        }
63
64        GlobalReadGuard { globals, guard }
65    }
66
67    /// Returns a mutable reference to the PHP executor globals.
68    ///
69    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
70    /// immutable references at one time but only ever one mutable reference.
71    /// Attempting to retrieve the globals while already holding the global
72    /// guard will lead to a deadlock. Dropping the globals guard will release
73    /// the lock.
74    ///
75    /// # Panics
76    ///
77    /// * If static executor globals are not set
78    pub fn get_mut() -> GlobalWriteGuard<Self> {
79        // SAFETY: PHP executor globals are statically declared therefore should never
80        // return an invalid pointer.
81        let globals = unsafe { ext_php_rs_executor_globals().as_mut() }
82            .expect("Static executor globals were invalid");
83
84        cfg_if::cfg_if! {
85            if #[cfg(php_zts)] {
86                let guard = lock::GLOBALS_LOCK.with(RwLock::write_arc);
87            } else {
88                let guard = lock::GLOBALS_LOCK.write_arc();
89            }
90        }
91
92        GlobalWriteGuard { globals, guard }
93    }
94
95    /// Attempts to retrieve the global class hash table.
96    #[must_use]
97    pub fn class_table(&self) -> Option<&ZendHashTable> {
98        unsafe { self.class_table.as_ref() }
99    }
100
101    /// Attempts to retrieve the global functions hash table.
102    #[must_use]
103    pub fn function_table(&self) -> Option<&ZendHashTable> {
104        unsafe { self.function_table.as_ref() }
105    }
106
107    /// Attempts to retrieve the global functions hash table as mutable.
108    // TODO: Verify if this is safe to use, as it allows mutating the
109    // hashtable while only having a reference to it. #461
110    #[allow(clippy::mut_from_ref)]
111    #[must_use]
112    pub fn function_table_mut(&self) -> Option<&mut ZendHashTable> {
113        unsafe { self.function_table.as_mut() }
114    }
115
116    /// Retrieves the ini values for all ini directives in the current executor
117    /// context..
118    ///
119    /// # Panics
120    ///
121    /// * If the ini directives are not a valid hash table.
122    /// * If the ini entry is not a string.
123    #[must_use]
124    pub fn ini_values(&self) -> HashMap<String, Option<String>> {
125        let hash_table = unsafe { &*self.ini_directives };
126        let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
127        for (key, value) in hash_table {
128            ini_hash_map.insert(key.to_string(), unsafe {
129                let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
130                if ini_entry.value.is_null() {
131                    None
132                } else {
133                    Some(
134                        (*ini_entry.value)
135                            .as_str()
136                            .expect("Ini value is not a string")
137                            .to_owned(),
138                    )
139                }
140            });
141        }
142        ini_hash_map
143    }
144
145    /// Attempts to retrieve the global constants table.
146    #[must_use]
147    pub fn constants(&self) -> Option<&ZendHashTable> {
148        unsafe { self.zend_constants.as_ref() }
149    }
150
151    /// Attempts to extract the last PHP exception captured by the interpreter.
152    /// Returned inside a [`ZBox`].
153    ///
154    /// This function requires the executor globals to be mutably held, which
155    /// could lead to a deadlock if the globals are already borrowed immutably
156    /// or mutably.
157    #[must_use]
158    pub fn take_exception() -> Option<ZBox<ZendObject>> {
159        {
160            // This avoid a write lock if there is no exception.
161            if Self::get().exception.is_null() {
162                return None;
163            }
164        }
165
166        let mut globals = Self::get_mut();
167
168        let mut exception_ptr = std::ptr::null_mut();
169        std::mem::swap(&mut exception_ptr, &mut globals.exception);
170
171        // SAFETY: `as_mut` checks for null.
172        Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) })
173    }
174
175    /// Checks if the executor globals contain an exception.
176    #[must_use]
177    pub fn has_exception() -> bool {
178        !Self::get().exception.is_null()
179    }
180
181    /// Attempts to extract the last PHP exception captured by the interpreter.
182    /// Returned inside a [`PhpResult`].
183    ///
184    /// This function requires the executor globals to be mutably held, which
185    /// could lead to a deadlock if the globals are already borrowed immutably
186    /// or mutably.
187    ///
188    /// # Errors
189    ///
190    /// If an exception is present, it will be returned as `Err` value inside a
191    /// [`PhpResult`].
192    pub fn throw_if_exception() -> PhpResult<()> {
193        if let Some(e) = Self::take_exception() {
194            Err(crate::error::Error::Exception(e).into())
195        } else {
196            Ok(())
197        }
198    }
199
200    /// Request an interrupt of the PHP VM. This will call the registered
201    /// interrupt handler function.
202    /// set with [`crate::ffi::zend_interrupt_function`].
203    pub fn request_interrupt(&mut self) {
204        cfg_if::cfg_if! {
205            if #[cfg(php82)] {
206                unsafe {
207                    zend_atomic_bool_store(&raw mut self.vm_interrupt, true);
208                }
209            } else {
210                self.vm_interrupt = true;
211            }
212        }
213    }
214
215    /// Cancel a requested an interrupt of the PHP VM.
216    pub fn cancel_interrupt(&mut self) {
217        cfg_if::cfg_if! {
218            if #[cfg(php82)] {
219                unsafe {
220                    zend_atomic_bool_store(&raw mut self.vm_interrupt, false);
221                }
222            } else {
223                self.vm_interrupt = true;
224            }
225        }
226    }
227}
228
229pub type CompilerGlobals = _zend_compiler_globals;
230
231impl CompilerGlobals {
232    /// Returns a reference to the PHP compiler globals.
233    ///
234    /// The compiler globals are guarded by a [`RwLock`]. There can be multiple
235    /// immutable references at one time but only ever one mutable reference.
236    /// Attempting to retrieve the globals while already holding the global
237    /// guard will lead to a deadlock. Dropping the globals guard will release
238    /// the lock.
239    ///
240    /// # Panics
241    ///
242    /// * If static executor globals are not set
243    pub fn get() -> GlobalReadGuard<Self> {
244        // SAFETY: PHP compiler globals are statically declared therefore should never
245        // return an invalid pointer.
246        let globals = unsafe { ext_php_rs_compiler_globals().as_ref() }
247            .expect("Static compiler globals were invalid");
248
249        cfg_if::cfg_if! {
250            if #[cfg(php_zts)] {
251                let guard = lock::GLOBALS_LOCK.with(RwLock::read_arc);
252            } else {
253                let guard = lock::GLOBALS_LOCK.read_arc();
254            }
255        }
256
257        GlobalReadGuard { globals, guard }
258    }
259
260    /// Returns a mutable reference to the PHP compiler globals.
261    ///
262    /// The compiler globals are guarded by a [`RwLock`]. There can be multiple
263    /// immutable references at one time but only ever one mutable reference.
264    /// Attempting to retrieve the globals while already holding the global
265    /// guard will lead to a deadlock. Dropping the globals guard will release
266    /// the lock.
267    ///
268    /// # Panics
269    ///
270    /// * If static compiler globals are not set
271    pub fn get_mut() -> GlobalWriteGuard<Self> {
272        // SAFETY: PHP compiler globals are statically declared therefore should never
273        // return an invalid pointer.
274        let globals = unsafe { ext_php_rs_compiler_globals().as_mut() }
275            .expect("Static compiler globals were invalid");
276
277        cfg_if::cfg_if! {
278            if #[cfg(php_zts)] {
279                let guard = lock::GLOBALS_LOCK.with(RwLock::write_arc);
280            } else {
281                let guard = lock::GLOBALS_LOCK.write_arc();
282            }
283        }
284
285        GlobalWriteGuard { globals, guard }
286    }
287}
288
289/// Stores the SAPI module used in the PHP executor.
290pub type SapiModule = _sapi_module_struct;
291
292impl SapiModule {
293    /// Returns a reference to the PHP SAPI module.
294    ///
295    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
296    /// immutable references at one time but only ever one mutable reference.
297    /// Attempting to retrieve the globals while already holding the global
298    /// guard will lead to a deadlock. Dropping the globals guard will release
299    /// the lock.
300    ///
301    /// # Panics
302    ///
303    /// * If static executor globals are not set
304    pub fn get() -> GlobalReadGuard<Self> {
305        // SAFETY: PHP executor globals are statically declared therefore should never
306        // return an invalid pointer.
307        let globals = unsafe { ext_php_rs_sapi_module().as_ref() }
308            .expect("Static executor globals were invalid");
309        let guard = SAPI_MODULE_LOCK.read_arc();
310        GlobalReadGuard { globals, guard }
311    }
312
313    /// Returns a mutable reference to the PHP executor globals.
314    ///
315    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
316    /// immutable references at one time but only ever one mutable reference.
317    /// Attempting to retrieve the globals while already holding the global
318    /// guard will lead to a deadlock. Dropping the globals guard will release
319    /// the lock.
320    ///
321    /// # Panics
322    ///
323    /// * If static executor globals are not set
324    pub fn get_mut() -> GlobalWriteGuard<Self> {
325        // SAFETY: PHP executor globals are statically declared therefore should never
326        // return an invalid pointer.
327        let globals = unsafe { ext_php_rs_sapi_module().as_mut() }
328            .expect("Static executor globals were invalid");
329        let guard = SAPI_MODULE_LOCK.write_arc();
330        GlobalWriteGuard { globals, guard }
331    }
332}
333
334/// Stores global variables used in the PHP executor.
335pub type ProcessGlobals = php_core_globals;
336
337impl ProcessGlobals {
338    /// Returns a reference to the PHP process globals.
339    ///
340    /// The process globals are guarded by a [`RwLock`]. There can be multiple
341    /// immutable references at one time but only ever one mutable reference.
342    /// Attempting to retrieve the globals while already holding the global
343    /// guard will lead to a deadlock. Dropping the globals guard will release
344    /// the lock.
345    pub fn get() -> GlobalReadGuard<Self> {
346        // SAFETY: PHP executor globals are statically declared therefore should never
347        // return an invalid pointer.
348        let globals = unsafe { &*ext_php_rs_process_globals() };
349
350        cfg_if::cfg_if! {
351            if #[cfg(php_zts)] {
352                let guard = lock::PROCESS_GLOBALS_LOCK.with(RwLock::read_arc);
353            } else {
354                let guard = lock::PROCESS_GLOBALS_LOCK.read_arc();
355            }
356        }
357
358        GlobalReadGuard { globals, guard }
359    }
360
361    /// Returns a mutable reference to the PHP executor globals.
362    ///
363    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
364    /// immutable references at one time but only ever one mutable reference.
365    /// Attempting to retrieve the globals while already holding the global
366    /// guard will lead to a deadlock. Dropping the globals guard will release
367    /// the lock.
368    pub fn get_mut() -> GlobalWriteGuard<Self> {
369        // SAFETY: PHP executor globals are statically declared therefore should never
370        // return an invalid pointer.
371        let globals = unsafe { &mut *ext_php_rs_process_globals() };
372
373        cfg_if::cfg_if! {
374            if #[cfg(php_zts)] {
375                let guard = lock::PROCESS_GLOBALS_LOCK.with(RwLock::write_arc);
376            } else {
377                let guard = lock::PROCESS_GLOBALS_LOCK.write_arc();
378            }
379        }
380
381        GlobalWriteGuard { globals, guard }
382    }
383
384    /// Get the HTTP Server variables. Equivalent of $_SERVER.
385    #[must_use]
386    pub fn http_server_vars(&self) -> Option<&ZendHashTable> {
387        // $_SERVER is lazy-initted, we need to call zend_is_auto_global
388        // if it's not already populated.
389        if !self.http_globals[TRACK_VARS_SERVER as usize].is_array() {
390            let name = ZendStr::new("_SERVER", false).as_mut_ptr();
391            unsafe { zend_is_auto_global(name) };
392        }
393        if self.http_globals[TRACK_VARS_SERVER as usize].is_array() {
394            self.http_globals[TRACK_VARS_SERVER as usize].array()
395        } else {
396            None
397        }
398    }
399
400    /// Get the HTTP POST variables. Equivalent of $_POST.
401    ///
402    /// # Panics
403    ///
404    /// * If the post global is not found or fails to be populated.
405    #[must_use]
406    pub fn http_post_vars(&self) -> &ZendHashTable {
407        self.http_globals[TRACK_VARS_POST as usize]
408            .array()
409            .expect("Type is not a ZendArray")
410    }
411
412    /// Get the HTTP GET variables. Equivalent of $_GET.
413    ///
414    /// # Panics
415    ///
416    /// * If the get global is not found or fails to be populated.
417    #[must_use]
418    pub fn http_get_vars(&self) -> &ZendHashTable {
419        self.http_globals[TRACK_VARS_GET as usize]
420            .array()
421            .expect("Type is not a ZendArray")
422    }
423
424    /// Get the HTTP Cookie variables. Equivalent of $_COOKIE.
425    ///
426    /// # Panics
427    ///
428    /// * If the cookie global is not found or fails to be populated.
429    #[must_use]
430    pub fn http_cookie_vars(&self) -> &ZendHashTable {
431        self.http_globals[TRACK_VARS_COOKIE as usize]
432            .array()
433            .expect("Type is not a ZendArray")
434    }
435
436    /// Get the HTTP Request variables. Equivalent of $_REQUEST.
437    ///
438    /// # Panics
439    ///
440    /// * If the request global is not found or fails to be populated.
441    /// * If the request global is not a [`ZendHashTable`].
442    pub fn http_request_vars(&self) -> Option<&ZendHashTable> {
443        cfg_if::cfg_if! {
444            if #[cfg(php81)] {
445                let key = unsafe {
446                    *zend_known_strings.add(_zend_known_string_id_ZEND_STR_AUTOGLOBAL_REQUEST as usize)
447                };
448            } else {
449                let key = _zend_string::new("_REQUEST", false).as_mut_ptr();
450            }
451        };
452
453        // `$_REQUEST` is lazy-initted, we need to call `zend_is_auto_global` to make
454        // sure it's populated.
455        assert!(
456            unsafe { zend_is_auto_global(key) },
457            "Failed to get request global"
458        );
459
460        let symbol_table = &ExecutorGlobals::get().symbol_table;
461        cfg_if::cfg_if! {
462            if #[cfg(php81)] {
463                let request = unsafe { zend_hash_find_known_hash(symbol_table, key) };
464            } else {
465                let request = unsafe { _zend_hash_find_known_hash(symbol_table, key) };
466            }
467        };
468
469        if request.is_null() {
470            return None;
471        }
472
473        Some(unsafe { (*request).array() }.expect("Type is not a ZendArray"))
474    }
475
476    /// Get the HTTP Environment variables. Equivalent of $_ENV.
477    ///
478    /// # Panics
479    ///
480    /// * If the environment global is not found or fails to be populated.
481    #[must_use]
482    pub fn http_env_vars(&self) -> &ZendHashTable {
483        self.http_globals[TRACK_VARS_ENV as usize]
484            .array()
485            .expect("Type is not a ZendArray")
486    }
487
488    /// Get the HTTP Files variables. Equivalent of $_FILES.
489    ///
490    /// # Panics
491    ///
492    /// * If the files global is not found or fails to be populated.
493    #[must_use]
494    pub fn http_files_vars(&self) -> &ZendHashTable {
495        self.http_globals[TRACK_VARS_FILES as usize]
496            .array()
497            .expect("Type is not a ZendArray")
498    }
499}
500
501/// Stores global variables used in the SAPI.
502pub type SapiGlobals = sapi_globals_struct;
503
504impl SapiGlobals {
505    /// Returns a reference to the PHP process globals.
506    ///
507    /// The process globals are guarded by a [`RwLock`]. There can be multiple
508    /// immutable references at one time but only ever one mutable reference.
509    /// Attempting to retrieve the globals while already holding the global
510    /// guard will lead to a deadlock. Dropping the globals guard will release
511    /// the lock.
512    #[must_use]
513    pub fn get() -> GlobalReadGuard<Self> {
514        // SAFETY: PHP executor globals are statically declared therefore should never
515        // return an invalid pointer.
516        let globals = unsafe { &*ext_php_rs_sapi_globals() };
517
518        cfg_if::cfg_if! {
519            if #[cfg(php_zts)] {
520                let guard = lock::SAPI_GLOBALS_LOCK.with(RwLock::read_arc);
521            } else {
522                let guard = lock::SAPI_GLOBALS_LOCK.read_arc();
523            }
524        }
525
526        GlobalReadGuard { globals, guard }
527    }
528
529    /// Returns a mutable reference to the PHP executor globals.
530    ///
531    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
532    /// immutable references at one time but only ever one mutable reference.
533    /// Attempting to retrieve the globals while already holding the global
534    /// guard will lead to a deadlock. Dropping the globals guard will release
535    /// the lock.
536    pub fn get_mut() -> GlobalWriteGuard<Self> {
537        // SAFETY: PHP executor globals are statically declared therefore should never
538        // return an invalid pointer.
539        let globals = unsafe { &mut *ext_php_rs_sapi_globals() };
540
541        cfg_if::cfg_if! {
542            if #[cfg(php_zts)] {
543                let guard = lock::SAPI_GLOBALS_LOCK.with(RwLock::write_arc);
544            } else {
545                let guard = lock::SAPI_GLOBALS_LOCK.write_arc();
546            }
547        }
548
549        GlobalWriteGuard { globals, guard }
550    }
551
552    /// Get the request info for the Sapi.
553    #[must_use]
554    pub fn request_info(&self) -> &SapiRequestInfo {
555        &self.request_info
556    }
557
558    /// Get the sapi headers for the Sapi.
559    #[must_use]
560    pub fn sapi_headers(&self) -> &SapiHeaders {
561        &self.sapi_headers
562    }
563}
564
565/// Stores SAPI headers. Exposed through `SapiGlobals`.
566pub type SapiHeaders = sapi_headers_struct;
567
568impl<'a> SapiHeaders {
569    /// Create an iterator over the headers.
570    pub fn headers(&'a mut self) -> ZendLinkedListIterator<'a, SapiHeader> {
571        self.headers.iter()
572    }
573}
574
575/// Manage a key/value pair of SAPI headers.
576pub type SapiHeader = sapi_header_struct;
577
578impl<'a> SapiHeader {
579    /// Get the header as a string.
580    ///
581    /// # Panics
582    ///
583    /// * If the header is not a valid UTF-8 string.
584    #[must_use]
585    pub fn as_str(&'a self) -> &'a str {
586        unsafe {
587            let slice = slice::from_raw_parts(self.header as *const u8, self.header_len);
588            str::from_utf8(slice).expect("Invalid header string")
589        }
590    }
591
592    /// Returns the header name (key).
593    #[must_use]
594    pub fn name(&'a self) -> &'a str {
595        self.as_str().split(':').next().unwrap_or("").trim()
596    }
597
598    /// Returns the header value.
599    #[must_use]
600    pub fn value(&'a self) -> Option<&'a str> {
601        self.as_str().split_once(':').map(|(_, value)| value.trim())
602    }
603}
604
605pub type SapiRequestInfo = sapi_request_info;
606
607impl SapiRequestInfo {
608    /// Get the request method.
609    #[must_use]
610    pub fn request_method(&self) -> Option<&str> {
611        if self.request_method.is_null() {
612            return None;
613        }
614        unsafe { CStr::from_ptr(self.request_method).to_str().ok() }
615    }
616
617    /// Get the query string.
618    #[must_use]
619    pub fn query_string(&self) -> Option<&str> {
620        if self.query_string.is_null() {
621            return None;
622        }
623        unsafe { CStr::from_ptr(self.query_string).to_str().ok() }
624    }
625
626    /// Get the cookie data.
627    #[must_use]
628    pub fn cookie_data(&self) -> Option<&str> {
629        if self.cookie_data.is_null() {
630            return None;
631        }
632        unsafe { CStr::from_ptr(self.cookie_data).to_str().ok() }
633    }
634
635    /// Get the content length.
636    #[must_use]
637    pub fn content_length(&self) -> i64 {
638        self.content_length
639    }
640
641    /// Get the path info.
642    #[must_use]
643    pub fn path_translated(&self) -> Option<&str> {
644        if self.path_translated.is_null() {
645            return None;
646        }
647        unsafe { CStr::from_ptr(self.path_translated).to_str().ok() }
648    }
649
650    /// Get the request uri.
651    #[must_use]
652    pub fn request_uri(&self) -> Option<&str> {
653        if self.request_uri.is_null() {
654            return None;
655        }
656        unsafe { CStr::from_ptr(self.request_uri).to_str().ok() }
657    }
658
659    // Todo: request_body _php_stream
660
661    /// Get the content type.
662    #[must_use]
663    pub fn content_type(&self) -> Option<&str> {
664        if self.content_type.is_null() {
665            return None;
666        }
667        unsafe { CStr::from_ptr(self.content_type).to_str().ok() }
668    }
669
670    /// Whether the request consists of headers only.
671    #[must_use]
672    pub fn headers_only(&self) -> bool {
673        self.headers_only
674    }
675
676    /// Whether the request has no headers.
677    #[must_use]
678    pub fn no_headers(&self) -> bool {
679        self.no_headers
680    }
681
682    /// Whether the request headers have been read.
683    #[must_use]
684    pub fn headers_read(&self) -> bool {
685        self.headers_read
686    }
687
688    // Todo: post_entry sapi_post_entry
689
690    /// Get the auth user.
691    #[must_use]
692    pub fn auth_user(&self) -> Option<&str> {
693        if self.auth_user.is_null() {
694            return None;
695        }
696        unsafe { CStr::from_ptr(self.auth_user).to_str().ok() }
697    }
698
699    /// Get the auth password.
700    #[must_use]
701    pub fn auth_password(&self) -> Option<&str> {
702        if self.auth_password.is_null() {
703            return None;
704        }
705        unsafe { CStr::from_ptr(self.auth_password).to_str().ok() }
706    }
707
708    /// Get the auth digest.
709    #[must_use]
710    pub fn auth_digest(&self) -> Option<&str> {
711        if self.auth_digest.is_null() {
712            return None;
713        }
714        unsafe { CStr::from_ptr(self.auth_digest).to_str().ok() }
715    }
716
717    /// Get argv0.
718    #[must_use]
719    pub fn argv0(&self) -> Option<&str> {
720        if self.argv0.is_null() {
721            return None;
722        }
723        unsafe { CStr::from_ptr(self.argv0).to_str().ok() }
724    }
725
726    /// Get the current user.
727    #[must_use]
728    pub fn current_user(&self) -> Option<&str> {
729        if self.current_user.is_null() {
730            return None;
731        }
732        unsafe { CStr::from_ptr(self.current_user).to_str().ok() }
733    }
734
735    /// Get the current user length.
736    #[must_use]
737    pub fn current_user_length(&self) -> i32 {
738        self.current_user_length
739    }
740
741    /// Get argvc.
742    #[must_use]
743    pub fn argvc(&self) -> i32 {
744        self.argc
745    }
746
747    /// Get argv.
748    #[must_use]
749    pub fn argv(&self) -> Option<&str> {
750        if self.argv.is_null() {
751            return None;
752        }
753        unsafe { CStr::from_ptr(*self.argv).to_str().ok() }
754    }
755
756    /// Get the protocol number.
757    #[must_use]
758    pub fn proto_num(&self) -> i32 {
759        self.proto_num
760    }
761}
762
763/// Stores global variables used in the SAPI.
764pub type FileGlobals = php_file_globals;
765
766impl FileGlobals {
767    /// Returns a reference to the PHP process globals.
768    ///
769    /// The process globals are guarded by a [`RwLock`]. There can be multiple
770    /// immutable references at one time but only ever one mutable reference.
771    /// Attempting to retrieve the globals while already holding the global
772    /// guard will lead to a deadlock. Dropping the globals guard will release
773    /// the lock.
774    ///
775    /// # Panics
776    ///
777    /// * If static file globals are not set
778    pub fn get() -> GlobalReadGuard<Self> {
779        // SAFETY: PHP executor globals are statically declared therefore should never
780        // return an invalid pointer.
781        let globals = unsafe { ext_php_rs_file_globals().as_ref() }
782            .expect("Static file globals were invalid");
783
784        cfg_if::cfg_if! {
785            if #[cfg(php_zts)] {
786                let guard = lock::FILE_GLOBALS_LOCK.with(RwLock::read_arc);
787            } else {
788                let guard = lock::FILE_GLOBALS_LOCK.read_arc();
789            }
790        }
791
792        GlobalReadGuard { globals, guard }
793    }
794
795    /// Returns a mutable reference to the PHP executor globals.
796    ///
797    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
798    /// immutable references at one time but only ever one mutable reference.
799    /// Attempting to retrieve the globals while already holding the global
800    /// guard will lead to a deadlock. Dropping the globals guard will release
801    /// the lock.
802    #[must_use]
803    pub fn get_mut() -> GlobalWriteGuard<Self> {
804        // SAFETY: PHP executor globals are statically declared therefore should never
805        // return an invalid pointer.
806        let globals = unsafe { &mut *ext_php_rs_file_globals() };
807
808        cfg_if::cfg_if! {
809            if #[cfg(php_zts)] {
810                let guard = lock::FILE_GLOBALS_LOCK.with(RwLock::write_arc);
811            } else {
812                let guard = lock::FILE_GLOBALS_LOCK.write_arc();
813            }
814        }
815
816        GlobalWriteGuard { globals, guard }
817    }
818
819    /// Returns the stream wrappers
820    #[must_use]
821    pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> {
822        unsafe { self.stream_wrappers.as_ref() }
823    }
824}
825
826/// Executor globals rwlock.
827///
828/// PHP provides no indication if the executor globals are being accessed so
829/// this is only effective on the Rust side.
830#[cfg(not(php_zts))]
831pub(crate) mod lock {
832    use parking_lot::RwLock;
833    use std::sync::{Arc, LazyLock};
834
835    pub(crate) static GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
836        LazyLock::new(|| Arc::new(RwLock::new(())));
837    pub(crate) static PROCESS_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
838        LazyLock::new(|| Arc::new(RwLock::new(())));
839    pub(crate) static SAPI_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
840        LazyLock::new(|| Arc::new(RwLock::new(())));
841    pub(crate) static FILE_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
842        LazyLock::new(|| Arc::new(RwLock::new(())));
843}
844
845/// Executor globals rwlock.
846///
847/// PHP provides no indication if the executor globals are being accessed so
848/// this is only effective on the Rust side.
849#[cfg(php_zts)]
850pub(crate) mod lock {
851    use parking_lot::{const_rwlock, RwLock};
852    use std::sync::Arc;
853
854    thread_local! {
855        pub(crate) static GLOBALS_LOCK: Arc<RwLock<()>> =  Arc::new(const_rwlock(()));
856        pub(crate) static PROCESS_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
857        pub(crate) static SAPI_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
858        pub(crate) static FILE_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
859    }
860}
861
862/// SAPI globals rwlock.
863///
864/// PHP provides no indication if the executor globals are being accessed so
865/// this is only effective on the Rust side.
866static SAPI_MODULE_LOCK: LazyLock<Arc<RwLock<()>>> = LazyLock::new(|| Arc::new(RwLock::new(())));
867
868/// Wrapper guard that contains a reference to a given type `T`. Dropping a
869/// guard releases the lock on the relevant rwlock.
870pub struct GlobalReadGuard<T: 'static> {
871    globals: &'static T,
872    #[allow(dead_code)]
873    guard: ArcRwLockReadGuard<RawRwLock, ()>,
874}
875
876impl<T> Deref for GlobalReadGuard<T> {
877    type Target = T;
878
879    fn deref(&self) -> &Self::Target {
880        self.globals
881    }
882}
883
884/// Wrapper guard that contains a mutable reference to a given type `T`.
885/// Dropping a guard releases the lock on the relevant rwlock.
886pub struct GlobalWriteGuard<T: 'static> {
887    globals: &'static mut T,
888    #[allow(dead_code)]
889    guard: ArcRwLockWriteGuard<RawRwLock, ()>,
890}
891
892impl<T> Deref for GlobalWriteGuard<T> {
893    type Target = T;
894
895    fn deref(&self) -> &Self::Target {
896        self.globals
897    }
898}
899
900impl<T> DerefMut for GlobalWriteGuard<T> {
901    fn deref_mut(&mut self) -> &mut Self::Target {
902        self.globals
903    }
904}
905
906#[cfg(feature = "embed")]
907#[cfg(test)]
908mod embed_tests {
909    use super::*;
910    use crate::embed::Embed;
911    use std::os::raw::c_char;
912
913    #[test]
914    fn test_sapi_header() {
915        Embed::run(|| {
916            let headers = [
917                ("Content-Type: text/html", "Content-Type", "text/html"),
918                ("X: Custom:Header", "X", "Custom:Header"),
919            ];
920
921            for (header_text, name, value) in headers {
922                let header = SapiHeader {
923                    header: header_text.as_bytes().as_ptr() as *mut c_char,
924                    header_len: header_text.len(),
925                };
926                assert_eq!(header.name(), name, "Header name mismatch");
927                assert_eq!(header.value(), Some(value), "Header value mismatch");
928                assert_eq!(
929                    header.as_str(),
930                    format!("{name}: {value}"),
931                    "Header string mismatch"
932                );
933            }
934        });
935    }
936
937    #[test]
938    fn test_executor_globals() {
939        Embed::run(|| {
940            let state = ExecutorGlobals::get().active;
941            ExecutorGlobals::get_mut().active = !state;
942            let changed = ExecutorGlobals::get().active;
943            ExecutorGlobals::get_mut().active = state;
944            assert_eq!(changed, !state);
945        });
946    }
947
948    #[test]
949    fn test_compiler_globals() {
950        Embed::run(|| {
951            let state = CompilerGlobals::get().in_compilation;
952            CompilerGlobals::get_mut().in_compilation = !state;
953            let changed = CompilerGlobals::get().in_compilation;
954            CompilerGlobals::get_mut().in_compilation = state;
955            assert_eq!(changed, !state);
956        });
957    }
958}