1use 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
35pub type ExecutorGlobals = _zend_executor_globals;
37
38impl ExecutorGlobals {
39 pub fn get() -> GlobalReadGuard<Self> {
51 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 pub fn get_mut() -> GlobalWriteGuard<Self> {
79 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 #[must_use]
97 pub fn class_table(&self) -> Option<&ZendHashTable> {
98 unsafe { self.class_table.as_ref() }
99 }
100
101 #[must_use]
103 pub fn function_table(&self) -> Option<&ZendHashTable> {
104 unsafe { self.function_table.as_ref() }
105 }
106
107 #[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 #[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 #[must_use]
147 pub fn constants(&self) -> Option<&ZendHashTable> {
148 unsafe { self.zend_constants.as_ref() }
149 }
150
151 #[must_use]
158 pub fn take_exception() -> Option<ZBox<ZendObject>> {
159 {
160 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 Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) })
173 }
174
175 #[must_use]
177 pub fn has_exception() -> bool {
178 !Self::get().exception.is_null()
179 }
180
181 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 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 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 pub fn get() -> GlobalReadGuard<Self> {
244 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 pub fn get_mut() -> GlobalWriteGuard<Self> {
272 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
289pub type SapiModule = _sapi_module_struct;
291
292impl SapiModule {
293 pub fn get() -> GlobalReadGuard<Self> {
305 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 pub fn get_mut() -> GlobalWriteGuard<Self> {
325 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
334pub type ProcessGlobals = php_core_globals;
336
337impl ProcessGlobals {
338 pub fn get() -> GlobalReadGuard<Self> {
346 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 pub fn get_mut() -> GlobalWriteGuard<Self> {
369 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 #[must_use]
386 pub fn http_server_vars(&self) -> Option<&ZendHashTable> {
387 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 #[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 #[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 #[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 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 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 #[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 #[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
501pub type SapiGlobals = sapi_globals_struct;
503
504impl SapiGlobals {
505 #[must_use]
513 pub fn get() -> GlobalReadGuard<Self> {
514 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 pub fn get_mut() -> GlobalWriteGuard<Self> {
537 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 #[must_use]
554 pub fn request_info(&self) -> &SapiRequestInfo {
555 &self.request_info
556 }
557
558 #[must_use]
560 pub fn sapi_headers(&self) -> &SapiHeaders {
561 &self.sapi_headers
562 }
563}
564
565pub type SapiHeaders = sapi_headers_struct;
567
568impl<'a> SapiHeaders {
569 pub fn headers(&'a mut self) -> ZendLinkedListIterator<'a, SapiHeader> {
571 self.headers.iter()
572 }
573}
574
575pub type SapiHeader = sapi_header_struct;
577
578impl<'a> SapiHeader {
579 #[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 #[must_use]
594 pub fn name(&'a self) -> &'a str {
595 self.as_str().split(':').next().unwrap_or("").trim()
596 }
597
598 #[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 #[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 #[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 #[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 #[must_use]
637 pub fn content_length(&self) -> i64 {
638 self.content_length
639 }
640
641 #[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 #[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 #[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 #[must_use]
672 pub fn headers_only(&self) -> bool {
673 self.headers_only
674 }
675
676 #[must_use]
678 pub fn no_headers(&self) -> bool {
679 self.no_headers
680 }
681
682 #[must_use]
684 pub fn headers_read(&self) -> bool {
685 self.headers_read
686 }
687
688 #[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 #[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 #[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 #[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 #[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 #[must_use]
737 pub fn current_user_length(&self) -> i32 {
738 self.current_user_length
739 }
740
741 #[must_use]
743 pub fn argvc(&self) -> i32 {
744 self.argc
745 }
746
747 #[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 #[must_use]
758 pub fn proto_num(&self) -> i32 {
759 self.proto_num
760 }
761}
762
763pub type FileGlobals = php_file_globals;
765
766impl FileGlobals {
767 pub fn get() -> GlobalReadGuard<Self> {
779 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 #[must_use]
803 pub fn get_mut() -> GlobalWriteGuard<Self> {
804 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 #[must_use]
821 pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> {
822 unsafe { self.stream_wrappers.as_ref() }
823 }
824}
825
826#[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#[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
862static SAPI_MODULE_LOCK: LazyLock<Arc<RwLock<()>>> = LazyLock::new(|| Arc::new(RwLock::new(())));
867
868pub 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
884pub 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}