ext_php_rs/zend/
globals.rs

1//! Types related to the PHP executor, sapi and process globals.
2
3use std::collections::HashMap;
4use std::ffi::CStr;
5use std::ops::{Deref, DerefMut};
6use std::slice;
7use std::str;
8
9use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard};
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_executor_globals, ext_php_rs_executor_globals,
17    ext_php_rs_file_globals, ext_php_rs_process_globals, ext_php_rs_sapi_globals,
18    ext_php_rs_sapi_module, php_core_globals, php_file_globals, sapi_globals_struct,
19    sapi_header_struct, sapi_headers_struct, sapi_request_info, zend_ini_entry,
20    zend_is_auto_global, TRACK_VARS_COOKIE, TRACK_VARS_ENV, TRACK_VARS_FILES, TRACK_VARS_GET,
21    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
38/// Stores the SAPI module used in the PHP executor.
39pub type SapiModule = _sapi_module_struct;
40
41impl ExecutorGlobals {
42    /// Returns a reference to the PHP executor globals.
43    ///
44    /// The executor globals are guarded by a RwLock. There can be multiple
45    /// immutable references at one time but only ever one mutable reference.
46    /// Attempting to retrieve the globals while already holding the global
47    /// guard will lead to a deadlock. Dropping the globals guard will release
48    /// the lock.
49    pub fn get() -> GlobalReadGuard<Self> {
50        // SAFETY: PHP executor globals are statically declared therefore should never
51        // return an invalid pointer.
52        let globals = unsafe { ext_php_rs_executor_globals().as_ref() }
53            .expect("Static executor globals were invalid");
54        let guard = GLOBALS_LOCK.read();
55        GlobalReadGuard { globals, guard }
56    }
57
58    /// Returns a mutable reference to the PHP executor globals.
59    ///
60    /// The executor globals are guarded by a RwLock. There can be multiple
61    /// immutable references at one time but only ever one mutable reference.
62    /// Attempting to retrieve the globals while already holding the global
63    /// guard will lead to a deadlock. Dropping the globals guard will release
64    /// the lock.
65    pub fn get_mut() -> GlobalWriteGuard<Self> {
66        // SAFETY: PHP executor globals are statically declared therefore should never
67        // return an invalid pointer.
68        let globals = unsafe { ext_php_rs_executor_globals().as_mut() }
69            .expect("Static executor globals were invalid");
70        let guard = GLOBALS_LOCK.write();
71        GlobalWriteGuard { globals, guard }
72    }
73
74    /// Attempts to retrieve the global class hash table.
75    pub fn class_table(&self) -> Option<&ZendHashTable> {
76        unsafe { self.class_table.as_ref() }
77    }
78
79    /// Attempts to retrieve the global functions hash table.
80    pub fn function_table(&self) -> Option<&ZendHashTable> {
81        unsafe { self.function_table.as_ref() }
82    }
83
84    /// Attempts to retrieve the global functions hash table as mutable.
85    pub fn function_table_mut(&self) -> Option<&mut ZendHashTable> {
86        unsafe { self.function_table.as_mut() }
87    }
88
89    /// Retrieves the ini values for all ini directives in the current executor
90    /// context..
91    pub fn ini_values(&self) -> HashMap<String, Option<String>> {
92        let hash_table = unsafe { &*self.ini_directives };
93        let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
94        for (key, value) in hash_table.iter() {
95            ini_hash_map.insert(key.to_string(), unsafe {
96                let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
97                if ini_entry.value.is_null() {
98                    None
99                } else {
100                    Some(
101                        (*ini_entry.value)
102                            .as_str()
103                            .expect("Ini value is not a string")
104                            .to_owned(),
105                    )
106                }
107            });
108        }
109        ini_hash_map
110    }
111
112    /// Attempts to retrieve the global constants table.
113    pub fn constants(&self) -> Option<&ZendHashTable> {
114        unsafe { self.zend_constants.as_ref() }
115    }
116
117    /// Attempts to extract the last PHP exception captured by the interpreter.
118    /// Returned inside a [`ZBox`].
119    ///
120    /// This function requires the executor globals to be mutably held, which
121    /// could lead to a deadlock if the globals are already borrowed immutably
122    /// or mutably.
123    pub fn take_exception() -> Option<ZBox<ZendObject>> {
124        {
125            // This avoid a write lock if there is no exception.
126            if Self::get().exception.is_null() {
127                return None;
128            }
129        }
130
131        let mut globals = Self::get_mut();
132
133        let mut exception_ptr = std::ptr::null_mut();
134        std::mem::swap(&mut exception_ptr, &mut globals.exception);
135
136        // SAFETY: `as_mut` checks for null.
137        Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) })
138    }
139
140    /// Checks if the executor globals contain an exception.
141    pub fn has_exception() -> bool {
142        !Self::get().exception.is_null()
143    }
144
145    /// Attempts to extract the last PHP exception captured by the interpreter.
146    /// Returned inside a [`PhpResult`].
147    ///
148    /// This function requires the executor globals to be mutably held, which
149    /// could lead to a deadlock if the globals are already borrowed immutably
150    /// or mutably.
151    pub fn throw_if_exception() -> PhpResult<()> {
152        if let Some(e) = Self::take_exception() {
153            Err(crate::error::Error::Exception(e).into())
154        } else {
155            Ok(())
156        }
157    }
158
159    /// Request an interrupt of the PHP VM. This will call the registered
160    /// interrupt handler function.
161    /// set with [`crate::ffi::zend_interrupt_function`].
162    pub fn request_interrupt(&mut self) {
163        cfg_if::cfg_if! {
164            if #[cfg(php82)] {
165                unsafe {
166                    zend_atomic_bool_store(&mut self.vm_interrupt, true);
167                }
168            } else {
169                self.vm_interrupt = true;
170            }
171        }
172    }
173
174    /// Cancel a requested an interrupt of the PHP VM.
175    pub fn cancel_interrupt(&mut self) {
176        cfg_if::cfg_if! {
177            if #[cfg(php82)] {
178                unsafe {
179                    zend_atomic_bool_store(&mut self.vm_interrupt, false);
180                }
181            } else {
182                self.vm_interrupt = true;
183            }
184        }
185    }
186}
187
188impl SapiModule {
189    /// Returns a reference to the PHP SAPI module.
190    ///
191    /// The executor globals are guarded by a RwLock. There can be multiple
192    /// immutable references at one time but only ever one mutable reference.
193    /// Attempting to retrieve the globals while already holding the global
194    /// guard will lead to a deadlock. Dropping the globals guard will release
195    /// the lock.
196    pub fn get() -> GlobalReadGuard<Self> {
197        // SAFETY: PHP executor globals are statically declared therefore should never
198        // return an invalid pointer.
199        let globals = unsafe { ext_php_rs_sapi_module().as_ref() }
200            .expect("Static executor globals were invalid");
201        let guard = SAPI_MODULE_LOCK.read();
202        GlobalReadGuard { globals, guard }
203    }
204
205    /// Returns a mutable reference to the PHP executor globals.
206    ///
207    /// The executor globals are guarded by a RwLock. There can be multiple
208    /// immutable references at one time but only ever one mutable reference.
209    /// Attempting to retrieve the globals while already holding the global
210    /// guard will lead to a deadlock. Dropping the globals guard will release
211    /// the lock.
212    pub fn get_mut() -> GlobalWriteGuard<Self> {
213        // SAFETY: PHP executor globals are statically declared therefore should never
214        // return an invalid pointer.
215        let globals = unsafe { ext_php_rs_sapi_module().as_mut() }
216            .expect("Static executor globals were invalid");
217        let guard = SAPI_MODULE_LOCK.write();
218        GlobalWriteGuard { globals, guard }
219    }
220}
221
222/// Stores global variables used in the PHP executor.
223pub type ProcessGlobals = php_core_globals;
224
225impl ProcessGlobals {
226    /// Returns a reference to the PHP process globals.
227    ///
228    /// The process globals are guarded by a RwLock. There can be multiple
229    /// immutable references at one time but only ever one mutable reference.
230    /// Attempting to retrieve the globals while already holding the global
231    /// guard will lead to a deadlock. Dropping the globals guard will release
232    /// the lock.
233    pub fn get() -> GlobalReadGuard<Self> {
234        // SAFETY: PHP executor globals are statically declared therefore should never
235        // return an invalid pointer.
236        let globals = unsafe { &*ext_php_rs_process_globals() };
237        let guard = PROCESS_GLOBALS_LOCK.read();
238        GlobalReadGuard { globals, guard }
239    }
240
241    /// Returns a mutable reference to the PHP executor globals.
242    ///
243    /// The executor globals are guarded by a RwLock. There can be multiple
244    /// immutable references at one time but only ever one mutable reference.
245    /// Attempting to retrieve the globals while already holding the global
246    /// guard will lead to a deadlock. Dropping the globals guard will release
247    /// the lock.
248    pub fn get_mut() -> GlobalWriteGuard<Self> {
249        // SAFETY: PHP executor globals are statically declared therefore should never
250        // return an invalid pointer.
251        let globals = unsafe { &mut *ext_php_rs_process_globals() };
252        let guard = PROCESS_GLOBALS_LOCK.write();
253        GlobalWriteGuard { globals, guard }
254    }
255
256    /// Get the HTTP Server variables. Equivalent of $_SERVER.
257    pub fn http_server_vars(&self) -> Option<&ZendHashTable> {
258        // $_SERVER is lazy-initted, we need to call zend_is_auto_global
259        // if it's not already populated.
260        if !self.http_globals[TRACK_VARS_SERVER as usize].is_array() {
261            let name = ZendStr::new("_SERVER", false).as_mut_ptr();
262            unsafe { zend_is_auto_global(name) };
263        }
264        if self.http_globals[TRACK_VARS_SERVER as usize].is_array() {
265            self.http_globals[TRACK_VARS_SERVER as usize].array()
266        } else {
267            None
268        }
269    }
270
271    /// Get the HTTP POST variables. Equivalent of $_POST.
272    pub fn http_post_vars(&self) -> &ZendHashTable {
273        self.http_globals[TRACK_VARS_POST as usize]
274            .array()
275            .expect("Type is not a ZendArray")
276    }
277
278    /// Get the HTTP GET variables. Equivalent of $_GET.
279    pub fn http_get_vars(&self) -> &ZendHashTable {
280        self.http_globals[TRACK_VARS_GET as usize]
281            .array()
282            .expect("Type is not a ZendArray")
283    }
284
285    /// Get the HTTP Cookie variables. Equivalent of $_COOKIE.
286    pub fn http_cookie_vars(&self) -> &ZendHashTable {
287        self.http_globals[TRACK_VARS_COOKIE as usize]
288            .array()
289            .expect("Type is not a ZendArray")
290    }
291
292    /// Get the HTTP Request variables. Equivalent of $_REQUEST.
293    ///
294    /// # Panics
295    /// - If the request global is not found or fails to be populated.
296    /// - If the request global is not a ZendArray.
297    pub fn http_request_vars(&self) -> Option<&ZendHashTable> {
298        cfg_if::cfg_if! {
299            if #[cfg(php81)] {
300                let key = unsafe {
301                    *zend_known_strings.add(_zend_known_string_id_ZEND_STR_AUTOGLOBAL_REQUEST as usize)
302                };
303            } else {
304                let key = _zend_string::new("_REQUEST", false).as_mut_ptr();
305            }
306        };
307
308        // `$_REQUEST` is lazy-initted, we need to call `zend_is_auto_global` to make
309        // sure it's populated.
310        if !unsafe { zend_is_auto_global(key) } {
311            panic!("Failed to get request global");
312        }
313
314        let symbol_table = &ExecutorGlobals::get().symbol_table;
315        cfg_if::cfg_if! {
316            if #[cfg(php81)] {
317                let request = unsafe { zend_hash_find_known_hash(symbol_table, key) };
318            } else {
319                let request = unsafe { _zend_hash_find_known_hash(symbol_table, key) };
320            }
321        };
322
323        if request.is_null() {
324            return None;
325        }
326
327        Some(unsafe { (*request).array() }.expect("Type is not a ZendArray"))
328    }
329
330    /// Get the HTTP Environment variables. Equivalent of $_ENV.
331    pub fn http_env_vars(&self) -> &ZendHashTable {
332        self.http_globals[TRACK_VARS_ENV as usize]
333            .array()
334            .expect("Type is not a ZendArray")
335    }
336
337    /// Get the HTTP Files variables. Equivalent of $_FILES.
338    pub fn http_files_vars(&self) -> &ZendHashTable {
339        self.http_globals[TRACK_VARS_FILES as usize]
340            .array()
341            .expect("Type is not a ZendArray")
342    }
343}
344
345/// Stores global variables used in the SAPI.
346pub type SapiGlobals = sapi_globals_struct;
347
348impl SapiGlobals {
349    /// Returns a reference to the PHP process globals.
350    ///
351    /// The process globals are guarded by a RwLock. There can be multiple
352    /// immutable references at one time but only ever one mutable reference.
353    /// Attempting to retrieve the globals while already holding the global
354    /// guard will lead to a deadlock. Dropping the globals guard will release
355    /// the lock.
356    pub fn get() -> GlobalReadGuard<Self> {
357        // SAFETY: PHP executor globals are statically declared therefore should never
358        // return an invalid pointer.
359        let globals = unsafe { &*ext_php_rs_sapi_globals() };
360        let guard = SAPI_GLOBALS_LOCK.read();
361        GlobalReadGuard { globals, guard }
362    }
363
364    /// Returns a mutable reference to the PHP executor globals.
365    ///
366    /// The executor globals are guarded by a RwLock. There can be multiple
367    /// immutable references at one time but only ever one mutable reference.
368    /// Attempting to retrieve the globals while already holding the global
369    /// guard will lead to a deadlock. Dropping the globals guard will release
370    /// the lock.
371    pub fn get_mut() -> GlobalWriteGuard<Self> {
372        // SAFETY: PHP executor globals are statically declared therefore should never
373        // return an invalid pointer.
374        let globals = unsafe { &mut *ext_php_rs_sapi_globals() };
375        let guard = SAPI_GLOBALS_LOCK.write();
376        GlobalWriteGuard { globals, guard }
377    }
378    // Get the request info for the Sapi.
379    pub fn request_info(&self) -> &SapiRequestInfo {
380        &self.request_info
381    }
382
383    pub fn sapi_headers(&self) -> &SapiHeaders {
384        &self.sapi_headers
385    }
386}
387
388pub type SapiHeaders = sapi_headers_struct;
389
390impl<'a> SapiHeaders {
391    pub fn headers(&'a mut self) -> ZendLinkedListIterator<'a, SapiHeader> {
392        self.headers.iter()
393    }
394}
395
396pub type SapiHeader = sapi_header_struct;
397
398impl<'a> SapiHeader {
399    pub fn as_str(&'a self) -> &'a str {
400        unsafe {
401            let slice = slice::from_raw_parts(self.header as *const u8, self.header_len);
402            str::from_utf8(slice).expect("Invalid header string")
403        }
404    }
405
406    pub fn name(&'a self) -> &'a str {
407        self.as_str().split(':').next().unwrap_or("").trim()
408    }
409
410    pub fn value(&'a self) -> Option<&'a str> {
411        self.as_str().split(':').nth(1).map(|s| s.trim())
412    }
413}
414
415pub type SapiRequestInfo = sapi_request_info;
416
417impl SapiRequestInfo {
418    pub fn request_method(&self) -> Option<&str> {
419        if self.request_method.is_null() {
420            return None;
421        }
422        unsafe { CStr::from_ptr(self.request_method).to_str().ok() }
423    }
424
425    pub fn query_string(&self) -> Option<&str> {
426        if self.query_string.is_null() {
427            return None;
428        }
429        unsafe { CStr::from_ptr(self.query_string).to_str().ok() }
430    }
431
432    pub fn cookie_data(&self) -> Option<&str> {
433        if self.cookie_data.is_null() {
434            return None;
435        }
436        unsafe { CStr::from_ptr(self.cookie_data).to_str().ok() }
437    }
438
439    pub fn content_length(&self) -> i64 {
440        self.content_length
441    }
442
443    pub fn path_translated(&self) -> Option<&str> {
444        if self.path_translated.is_null() {
445            return None;
446        }
447        unsafe { CStr::from_ptr(self.path_translated).to_str().ok() }
448    }
449
450    pub fn request_uri(&self) -> Option<&str> {
451        if self.request_uri.is_null() {
452            return None;
453        }
454        unsafe { CStr::from_ptr(self.request_uri).to_str().ok() }
455    }
456
457    // Todo: request_body _php_stream
458
459    pub fn content_type(&self) -> Option<&str> {
460        if self.content_type.is_null() {
461            return None;
462        }
463        unsafe { CStr::from_ptr(self.content_type).to_str().ok() }
464    }
465
466    pub fn headers_only(&self) -> bool {
467        self.headers_only
468    }
469
470    pub fn no_headers(&self) -> bool {
471        self.no_headers
472    }
473
474    pub fn headers_read(&self) -> bool {
475        self.headers_read
476    }
477
478    // Todo: post_entry sapi_post_entry
479
480    pub fn auth_user(&self) -> Option<&str> {
481        if self.auth_user.is_null() {
482            return None;
483        }
484        unsafe { CStr::from_ptr(self.auth_user).to_str().ok() }
485    }
486
487    pub fn auth_password(&self) -> Option<&str> {
488        if self.auth_password.is_null() {
489            return None;
490        }
491        unsafe { CStr::from_ptr(self.auth_password).to_str().ok() }
492    }
493
494    pub fn auth_digest(&self) -> Option<&str> {
495        if self.auth_digest.is_null() {
496            return None;
497        }
498        unsafe { CStr::from_ptr(self.auth_digest).to_str().ok() }
499    }
500
501    pub fn argv0(&self) -> Option<&str> {
502        if self.argv0.is_null() {
503            return None;
504        }
505        unsafe { CStr::from_ptr(self.argv0).to_str().ok() }
506    }
507
508    pub fn current_user(&self) -> Option<&str> {
509        if self.current_user.is_null() {
510            return None;
511        }
512        unsafe { CStr::from_ptr(self.current_user).to_str().ok() }
513    }
514
515    pub fn current_user_length(&self) -> i32 {
516        self.current_user_length
517    }
518
519    pub fn argvc(&self) -> i32 {
520        self.argc
521    }
522
523    pub fn argv(&self) -> Option<&str> {
524        if self.argv.is_null() {
525            return None;
526        }
527        unsafe { CStr::from_ptr(*self.argv).to_str().ok() }
528    }
529
530    pub fn proto_num(&self) -> i32 {
531        self.proto_num
532    }
533}
534
535/// Stores global variables used in the SAPI.
536pub type FileGlobals = php_file_globals;
537
538impl FileGlobals {
539    /// Returns a reference to the PHP process globals.
540    ///
541    /// The process globals are guarded by a RwLock. There can be multiple
542    /// immutable references at one time but only ever one mutable reference.
543    /// Attempting to retrieve the globals while already holding the global
544    /// guard will lead to a deadlock. Dropping the globals guard will release
545    /// the lock.
546    pub fn get() -> GlobalReadGuard<Self> {
547        // SAFETY: PHP executor globals are statically declared therefore should never
548        // return an invalid pointer.
549        let globals = unsafe { ext_php_rs_file_globals().as_ref() }
550            .expect("Static file globals were invalid");
551        let guard = FILE_GLOBALS_LOCK.read();
552        GlobalReadGuard { globals, guard }
553    }
554
555    /// Returns a mutable reference to the PHP executor globals.
556    ///
557    /// The executor globals are guarded by a RwLock. There can be multiple
558    /// immutable references at one time but only ever one mutable reference.
559    /// Attempting to retrieve the globals while already holding the global
560    /// guard will lead to a deadlock. Dropping the globals guard will release
561    /// the lock.
562    pub fn get_mut() -> GlobalWriteGuard<Self> {
563        // SAFETY: PHP executor globals are statically declared therefore should never
564        // return an invalid pointer.
565        let globals = unsafe { &mut *ext_php_rs_file_globals() };
566        let guard = SAPI_GLOBALS_LOCK.write();
567        GlobalWriteGuard { globals, guard }
568    }
569
570    pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> {
571        unsafe { self.stream_wrappers.as_ref() }
572    }
573}
574
575/// Executor globals rwlock.
576///
577/// PHP provides no indication if the executor globals are being accessed so
578/// this is only effective on the Rust side.
579static GLOBALS_LOCK: RwLock<()> = const_rwlock(());
580static PROCESS_GLOBALS_LOCK: RwLock<()> = const_rwlock(());
581static SAPI_GLOBALS_LOCK: RwLock<()> = const_rwlock(());
582static FILE_GLOBALS_LOCK: RwLock<()> = const_rwlock(());
583
584/// SAPI globals rwlock.
585///
586/// PHP provides no indication if the executor globals are being accessed so
587/// this is only effective on the Rust side.
588static SAPI_MODULE_LOCK: RwLock<()> = const_rwlock(());
589
590/// Wrapper guard that contains a reference to a given type `T`. Dropping a
591/// guard releases the lock on the relevant rwlock.
592pub struct GlobalReadGuard<T: 'static> {
593    globals: &'static T,
594    #[allow(dead_code)]
595    guard: RwLockReadGuard<'static, ()>,
596}
597
598impl<T> Deref for GlobalReadGuard<T> {
599    type Target = T;
600
601    fn deref(&self) -> &Self::Target {
602        self.globals
603    }
604}
605
606/// Wrapper guard that contains a mutable reference to a given type `T`.
607/// Dropping a guard releases the lock on the relevant rwlock.
608pub struct GlobalWriteGuard<T: 'static> {
609    globals: &'static mut T,
610    #[allow(dead_code)]
611    guard: RwLockWriteGuard<'static, ()>,
612}
613
614impl<T> Deref for GlobalWriteGuard<T> {
615    type Target = T;
616
617    fn deref(&self) -> &Self::Target {
618        self.globals
619    }
620}
621
622impl<T> DerefMut for GlobalWriteGuard<T> {
623    fn deref_mut(&mut self) -> &mut Self::Target {
624        self.globals
625    }
626}