• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidcole1340 / ext-php-rs / 15788182852

20 Jun 2025 09:32PM UTC coverage: 22.034%. Remained the same
15788182852

Pull #460

github

web-flow
Merge d4fc01bb5 into 660f308c0
Pull Request #460: chore(clippy): flowing lifetimes warning and clippy::mut_from_ref

6 of 56 new or added lines in 13 files covered. (10.71%)

3 existing lines in 2 files now uncovered.

871 of 3953 relevant lines covered (22.03%)

2.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

18.72
/src/zend/globals.rs
1
//! Types related to the PHP executor, sapi and process globals.
2

3
use parking_lot::{ArcRwLockReadGuard, ArcRwLockWriteGuard, RawRwLock, RwLock};
4
use std::collections::HashMap;
5
use std::ffi::CStr;
6
use std::ops::{Deref, DerefMut};
7
use std::slice;
8
use std::str;
9
use std::sync::{Arc, LazyLock};
10

11
use crate::boxed::ZBox;
12
use crate::exception::PhpResult;
13
#[cfg(php82)]
14
use crate::ffi::zend_atomic_bool_store;
15
use 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))]
24
use crate::ffi::{_zend_hash_find_known_hash, _zend_string};
25
#[cfg(php81)]
26
use crate::ffi::{
27
    _zend_known_string_id_ZEND_STR_AUTOGLOBAL_REQUEST, zend_hash_find_known_hash,
28
    zend_known_strings,
29
};
30

31
use crate::types::{ZendHashTable, ZendObject, ZendStr};
32

33
use super::linked_list::ZendLinkedListIterator;
34

35
/// Stores global variables used in the PHP executor.
36
pub type ExecutorGlobals = _zend_executor_globals;
37

38
impl 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> {
59✔
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() }
59✔
54
            .expect("Static executor globals were invalid");
55

56
        cfg_if::cfg_if! {
59✔
57
            if #[cfg(php_zts)] {
59✔
58
                let guard = lock::GLOBALS_LOCK.with(RwLock::read_arc);
59✔
59
            } else {
60
                let guard = lock::GLOBALS_LOCK.read_arc();
59✔
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> {
3✔
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() }
3✔
82
            .expect("Static executor globals were invalid");
83

84
        cfg_if::cfg_if! {
3✔
85
            if #[cfg(php_zts)] {
3✔
86
                let guard = lock::GLOBALS_LOCK.with(RwLock::write_arc);
3✔
87
            } else {
88
                let guard = lock::GLOBALS_LOCK.write_arc();
3✔
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
    #[must_use]
109
    pub fn function_table_mut(&self) -> Option<&mut ZendHashTable> {
×
110
        unsafe { self.function_table.as_mut() }
×
111
    }
112

113
    /// Retrieves the ini values for all ini directives in the current executor
114
    /// context..
115
    ///
116
    /// # Panics
117
    ///
118
    /// * If the ini directives are not a valid hash table.
119
    /// * If the ini entry is not a string.
120
    #[must_use]
121
    pub fn ini_values(&self) -> HashMap<String, Option<String>> {
×
122
        let hash_table = unsafe { &*self.ini_directives };
×
123
        let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
×
124
        for (key, value) in hash_table {
×
125
            ini_hash_map.insert(key.to_string(), unsafe {
×
126
                let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
×
127
                if ini_entry.value.is_null() {
×
128
                    None
×
129
                } else {
130
                    Some(
×
131
                        (*ini_entry.value)
×
132
                            .as_str()
×
133
                            .expect("Ini value is not a string")
×
134
                            .to_owned(),
×
135
                    )
136
                }
137
            });
138
        }
139
        ini_hash_map
×
140
    }
141

142
    /// Attempts to retrieve the global constants table.
143
    #[must_use]
144
    pub fn constants(&self) -> Option<&ZendHashTable> {
×
145
        unsafe { self.zend_constants.as_ref() }
×
146
    }
147

148
    /// Attempts to extract the last PHP exception captured by the interpreter.
149
    /// Returned inside a [`ZBox`].
150
    ///
151
    /// This function requires the executor globals to be mutably held, which
152
    /// could lead to a deadlock if the globals are already borrowed immutably
153
    /// or mutably.
154
    #[must_use]
155
    pub fn take_exception() -> Option<ZBox<ZendObject>> {
1✔
156
        {
157
            // This avoid a write lock if there is no exception.
158
            if Self::get().exception.is_null() {
1✔
159
                return None;
×
160
            }
161
        }
162

163
        let mut globals = Self::get_mut();
1✔
164

165
        let mut exception_ptr = std::ptr::null_mut();
1✔
166
        std::mem::swap(&mut exception_ptr, &mut globals.exception);
1✔
167

168
        // SAFETY: `as_mut` checks for null.
169
        Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) })
1✔
170
    }
171

172
    /// Checks if the executor globals contain an exception.
173
    #[must_use]
174
    pub fn has_exception() -> bool {
56✔
175
        !Self::get().exception.is_null()
56✔
176
    }
177

178
    /// Attempts to extract the last PHP exception captured by the interpreter.
179
    /// Returned inside a [`PhpResult`].
180
    ///
181
    /// This function requires the executor globals to be mutably held, which
182
    /// could lead to a deadlock if the globals are already borrowed immutably
183
    /// or mutably.
184
    ///
185
    /// # Errors
186
    ///
187
    /// If an exception is present, it will be returned as `Err` value inside a
188
    /// [`PhpResult`].
189
    pub fn throw_if_exception() -> PhpResult<()> {
×
190
        if let Some(e) = Self::take_exception() {
×
191
            Err(crate::error::Error::Exception(e).into())
×
192
        } else {
193
            Ok(())
×
194
        }
195
    }
196

197
    /// Request an interrupt of the PHP VM. This will call the registered
198
    /// interrupt handler function.
199
    /// set with [`crate::ffi::zend_interrupt_function`].
200
    pub fn request_interrupt(&mut self) {
×
201
        cfg_if::cfg_if! {
×
202
            if #[cfg(php82)] {
×
203
                unsafe {
×
NEW
204
                    zend_atomic_bool_store(&raw mut self.vm_interrupt, true);
×
205
                }
206
            } else {
207
                self.vm_interrupt = true;
×
208
            }
209
        }
210
    }
211

212
    /// Cancel a requested an interrupt of the PHP VM.
213
    pub fn cancel_interrupt(&mut self) {
×
214
        cfg_if::cfg_if! {
×
215
            if #[cfg(php82)] {
×
216
                unsafe {
×
NEW
217
                    zend_atomic_bool_store(&raw mut self.vm_interrupt, false);
×
218
                }
219
            } else {
220
                self.vm_interrupt = true;
×
221
            }
222
        }
223
    }
224
}
225

226
pub type CompilerGlobals = _zend_compiler_globals;
227

228
impl CompilerGlobals {
229
    /// Returns a reference to the PHP compiler globals.
230
    ///
231
    /// The compiler globals are guarded by a [`RwLock`]. There can be multiple
232
    /// immutable references at one time but only ever one mutable reference.
233
    /// Attempting to retrieve the globals while already holding the global
234
    /// guard will lead to a deadlock. Dropping the globals guard will release
235
    /// the lock.
236
    ///
237
    /// # Panics
238
    ///
239
    /// * If static executor globals are not set
240
    pub fn get() -> GlobalReadGuard<Self> {
2✔
241
        // SAFETY: PHP compiler globals are statically declared therefore should never
242
        // return an invalid pointer.
243
        let globals = unsafe { ext_php_rs_compiler_globals().as_ref() }
2✔
244
            .expect("Static compiler globals were invalid");
245

246
        cfg_if::cfg_if! {
2✔
247
            if #[cfg(php_zts)] {
2✔
248
                let guard = lock::GLOBALS_LOCK.with(RwLock::read_arc);
2✔
249
            } else {
250
                let guard = lock::GLOBALS_LOCK.read_arc();
2✔
251
            }
252
        }
253

254
        GlobalReadGuard { globals, guard }
255
    }
256

257
    /// Returns a mutable reference to the PHP compiler globals.
258
    ///
259
    /// The compiler globals are guarded by a [`RwLock`]. There can be multiple
260
    /// immutable references at one time but only ever one mutable reference.
261
    /// Attempting to retrieve the globals while already holding the global
262
    /// guard will lead to a deadlock. Dropping the globals guard will release
263
    /// the lock.
264
    ///
265
    /// # Panics
266
    ///
267
    /// * If static compiler globals are not set
268
    pub fn get_mut() -> GlobalWriteGuard<Self> {
2✔
269
        // SAFETY: PHP compiler globals are statically declared therefore should never
270
        // return an invalid pointer.
271
        let globals = unsafe { ext_php_rs_compiler_globals().as_mut() }
2✔
272
            .expect("Static compiler globals were invalid");
273

274
        cfg_if::cfg_if! {
2✔
275
            if #[cfg(php_zts)] {
2✔
276
                let guard = lock::GLOBALS_LOCK.with(RwLock::write_arc);
2✔
277
            } else {
278
                let guard = lock::GLOBALS_LOCK.write_arc();
2✔
279
            }
280
        }
281

282
        GlobalWriteGuard { globals, guard }
283
    }
284
}
285

286
/// Stores the SAPI module used in the PHP executor.
287
pub type SapiModule = _sapi_module_struct;
288

289
impl SapiModule {
290
    /// Returns a reference to the PHP SAPI module.
291
    ///
292
    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
293
    /// immutable references at one time but only ever one mutable reference.
294
    /// Attempting to retrieve the globals while already holding the global
295
    /// guard will lead to a deadlock. Dropping the globals guard will release
296
    /// the lock.
297
    ///
298
    /// # Panics
299
    ///
300
    /// * If static executor globals are not set
301
    pub fn get() -> GlobalReadGuard<Self> {
×
302
        // SAFETY: PHP executor globals are statically declared therefore should never
303
        // return an invalid pointer.
304
        let globals = unsafe { ext_php_rs_sapi_module().as_ref() }
×
305
            .expect("Static executor globals were invalid");
306
        let guard = SAPI_MODULE_LOCK.read_arc();
×
307
        GlobalReadGuard { globals, guard }
308
    }
309

310
    /// Returns a mutable reference to the PHP executor globals.
311
    ///
312
    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
313
    /// immutable references at one time but only ever one mutable reference.
314
    /// Attempting to retrieve the globals while already holding the global
315
    /// guard will lead to a deadlock. Dropping the globals guard will release
316
    /// the lock.
317
    ///
318
    /// # Panics
319
    ///
320
    /// * If static executor globals are not set
321
    pub fn get_mut() -> GlobalWriteGuard<Self> {
×
322
        // SAFETY: PHP executor globals are statically declared therefore should never
323
        // return an invalid pointer.
324
        let globals = unsafe { ext_php_rs_sapi_module().as_mut() }
×
325
            .expect("Static executor globals were invalid");
326
        let guard = SAPI_MODULE_LOCK.write_arc();
×
327
        GlobalWriteGuard { globals, guard }
328
    }
329
}
330

331
/// Stores global variables used in the PHP executor.
332
pub type ProcessGlobals = php_core_globals;
333

334
impl ProcessGlobals {
335
    /// Returns a reference to the PHP process globals.
336
    ///
337
    /// The process globals are guarded by a [`RwLock`]. There can be multiple
338
    /// immutable references at one time but only ever one mutable reference.
339
    /// Attempting to retrieve the globals while already holding the global
340
    /// guard will lead to a deadlock. Dropping the globals guard will release
341
    /// the lock.
342
    pub fn get() -> GlobalReadGuard<Self> {
×
343
        // SAFETY: PHP executor globals are statically declared therefore should never
344
        // return an invalid pointer.
345
        let globals = unsafe { &*ext_php_rs_process_globals() };
×
346

347
        cfg_if::cfg_if! {
×
348
            if #[cfg(php_zts)] {
×
NEW
349
                let guard = lock::PROCESS_GLOBALS_LOCK.with(RwLock::read_arc);
×
350
            } else {
351
                let guard = lock::PROCESS_GLOBALS_LOCK.read_arc();
×
352
            }
353
        }
354

355
        GlobalReadGuard { globals, guard }
356
    }
357

358
    /// Returns a mutable reference to the PHP executor globals.
359
    ///
360
    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
361
    /// immutable references at one time but only ever one mutable reference.
362
    /// Attempting to retrieve the globals while already holding the global
363
    /// guard will lead to a deadlock. Dropping the globals guard will release
364
    /// the lock.
365
    pub fn get_mut() -> GlobalWriteGuard<Self> {
×
366
        // SAFETY: PHP executor globals are statically declared therefore should never
367
        // return an invalid pointer.
368
        let globals = unsafe { &mut *ext_php_rs_process_globals() };
×
369

370
        cfg_if::cfg_if! {
×
371
            if #[cfg(php_zts)] {
×
NEW
372
                let guard = lock::PROCESS_GLOBALS_LOCK.with(RwLock::write_arc);
×
373
            } else {
374
                let guard = lock::PROCESS_GLOBALS_LOCK.write_arc();
×
375
            }
376
        }
377

378
        GlobalWriteGuard { globals, guard }
379
    }
380

381
    /// Get the HTTP Server variables. Equivalent of $_SERVER.
382
    #[must_use]
383
    pub fn http_server_vars(&self) -> Option<&ZendHashTable> {
×
384
        // $_SERVER is lazy-initted, we need to call zend_is_auto_global
385
        // if it's not already populated.
386
        if !self.http_globals[TRACK_VARS_SERVER as usize].is_array() {
×
387
            let name = ZendStr::new("_SERVER", false).as_mut_ptr();
×
388
            unsafe { zend_is_auto_global(name) };
×
389
        }
390
        if self.http_globals[TRACK_VARS_SERVER as usize].is_array() {
×
391
            self.http_globals[TRACK_VARS_SERVER as usize].array()
×
392
        } else {
393
            None
×
394
        }
395
    }
396

397
    /// Get the HTTP POST variables. Equivalent of $_POST.
398
    ///
399
    /// # Panics
400
    ///
401
    /// * If the post global is not found or fails to be populated.
402
    #[must_use]
403
    pub fn http_post_vars(&self) -> &ZendHashTable {
×
404
        self.http_globals[TRACK_VARS_POST as usize]
×
405
            .array()
406
            .expect("Type is not a ZendArray")
407
    }
408

409
    /// Get the HTTP GET variables. Equivalent of $_GET.
410
    ///
411
    /// # Panics
412
    ///
413
    /// * If the get global is not found or fails to be populated.
414
    #[must_use]
415
    pub fn http_get_vars(&self) -> &ZendHashTable {
×
416
        self.http_globals[TRACK_VARS_GET as usize]
×
417
            .array()
418
            .expect("Type is not a ZendArray")
419
    }
420

421
    /// Get the HTTP Cookie variables. Equivalent of $_COOKIE.
422
    ///
423
    /// # Panics
424
    ///
425
    /// * If the cookie global is not found or fails to be populated.
426
    #[must_use]
427
    pub fn http_cookie_vars(&self) -> &ZendHashTable {
×
428
        self.http_globals[TRACK_VARS_COOKIE as usize]
×
429
            .array()
430
            .expect("Type is not a ZendArray")
431
    }
432

433
    /// Get the HTTP Request variables. Equivalent of $_REQUEST.
434
    ///
435
    /// # Panics
436
    ///
437
    /// * If the request global is not found or fails to be populated.
438
    /// * If the request global is not a [`ZendHashTable`].
439
    pub fn http_request_vars(&self) -> Option<&ZendHashTable> {
×
440
        cfg_if::cfg_if! {
×
441
            if #[cfg(php81)] {
×
442
                let key = unsafe {
×
443
                    *zend_known_strings.add(_zend_known_string_id_ZEND_STR_AUTOGLOBAL_REQUEST as usize)
×
444
                };
445
            } else {
446
                let key = _zend_string::new("_REQUEST", false).as_mut_ptr();
×
447
            }
448
        };
449

450
        // `$_REQUEST` is lazy-initted, we need to call `zend_is_auto_global` to make
451
        // sure it's populated.
452
        assert!(
×
453
            unsafe { zend_is_auto_global(key) },
×
454
            "Failed to get request global"
×
455
        );
456

457
        let symbol_table = &ExecutorGlobals::get().symbol_table;
×
458
        cfg_if::cfg_if! {
×
459
            if #[cfg(php81)] {
×
460
                let request = unsafe { zend_hash_find_known_hash(symbol_table, key) };
×
461
            } else {
462
                let request = unsafe { _zend_hash_find_known_hash(symbol_table, key) };
×
463
            }
464
        };
465

466
        if request.is_null() {
×
467
            return None;
×
468
        }
469

470
        Some(unsafe { (*request).array() }.expect("Type is not a ZendArray"))
×
471
    }
472

473
    /// Get the HTTP Environment variables. Equivalent of $_ENV.
474
    ///
475
    /// # Panics
476
    ///
477
    /// * If the environment global is not found or fails to be populated.
478
    #[must_use]
479
    pub fn http_env_vars(&self) -> &ZendHashTable {
×
480
        self.http_globals[TRACK_VARS_ENV as usize]
×
481
            .array()
482
            .expect("Type is not a ZendArray")
483
    }
484

485
    /// Get the HTTP Files variables. Equivalent of $_FILES.
486
    ///
487
    /// # Panics
488
    ///
489
    /// * If the files global is not found or fails to be populated.
490
    #[must_use]
491
    pub fn http_files_vars(&self) -> &ZendHashTable {
×
492
        self.http_globals[TRACK_VARS_FILES as usize]
×
493
            .array()
494
            .expect("Type is not a ZendArray")
495
    }
496
}
497

498
/// Stores global variables used in the SAPI.
499
pub type SapiGlobals = sapi_globals_struct;
500

501
impl SapiGlobals {
502
    /// Returns a reference to the PHP process globals.
503
    ///
504
    /// The process globals are guarded by a [`RwLock`]. There can be multiple
505
    /// immutable references at one time but only ever one mutable reference.
506
    /// Attempting to retrieve the globals while already holding the global
507
    /// guard will lead to a deadlock. Dropping the globals guard will release
508
    /// the lock.
509
    #[must_use]
UNCOV
510
    pub fn get() -> GlobalReadGuard<Self> {
×
511
        // SAFETY: PHP executor globals are statically declared therefore should never
512
        // return an invalid pointer.
513
        let globals = unsafe { &*ext_php_rs_sapi_globals() };
×
514

515
        cfg_if::cfg_if! {
×
516
            if #[cfg(php_zts)] {
×
NEW
517
                let guard = lock::SAPI_GLOBALS_LOCK.with(RwLock::read_arc);
×
518
            } else {
519
                let guard = lock::SAPI_GLOBALS_LOCK.read_arc();
×
520
            }
521
        }
522

523
        GlobalReadGuard { globals, guard }
524
    }
525

526
    /// Returns a mutable reference to the PHP executor globals.
527
    ///
528
    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
529
    /// immutable references at one time but only ever one mutable reference.
530
    /// Attempting to retrieve the globals while already holding the global
531
    /// guard will lead to a deadlock. Dropping the globals guard will release
532
    /// the lock.
533
    pub fn get_mut() -> GlobalWriteGuard<Self> {
×
534
        // SAFETY: PHP executor globals are statically declared therefore should never
535
        // return an invalid pointer.
536
        let globals = unsafe { &mut *ext_php_rs_sapi_globals() };
×
537

538
        cfg_if::cfg_if! {
×
539
            if #[cfg(php_zts)] {
×
NEW
540
                let guard = lock::SAPI_GLOBALS_LOCK.with(RwLock::write_arc);
×
541
            } else {
542
                let guard = lock::SAPI_GLOBALS_LOCK.write_arc();
×
543
            }
544
        }
545

546
        GlobalWriteGuard { globals, guard }
547
    }
548

549
    /// Get the request info for the Sapi.
550
    #[must_use]
551
    pub fn request_info(&self) -> &SapiRequestInfo {
×
552
        &self.request_info
×
553
    }
554

555
    /// Get the sapi headers for the Sapi.
556
    #[must_use]
557
    pub fn sapi_headers(&self) -> &SapiHeaders {
×
558
        &self.sapi_headers
×
559
    }
560
}
561

562
pub type SapiHeaders = sapi_headers_struct;
563

564
impl<'a> SapiHeaders {
565
    /// Create an iterator over the headers.
566
    pub fn headers(&'a mut self) -> ZendLinkedListIterator<'a, SapiHeader> {
×
567
        self.headers.iter()
×
568
    }
569
}
570

571
pub type SapiHeader = sapi_header_struct;
572

573
impl<'a> SapiHeader {
574
    /// Get the header as a string.
575
    ///
576
    /// # Panics
577
    ///
578
    /// * If the header is not a valid UTF-8 string.
579
    #[must_use]
580
    pub fn as_str(&'a self) -> &'a str {
6✔
581
        unsafe {
582
            let slice = slice::from_raw_parts(self.header as *const u8, self.header_len);
6✔
583
            str::from_utf8(slice).expect("Invalid header string")
6✔
584
        }
585
    }
586

587
    /// Returns the header name (key).
588
    #[must_use]
589
    pub fn name(&'a self) -> &'a str {
2✔
590
        self.as_str().split(':').next().unwrap_or("").trim()
2✔
591
    }
592

593
    /// Returns the header value.
594
    #[must_use]
595
    pub fn value(&'a self) -> Option<&'a str> {
2✔
596
        self.as_str().split_once(':').map(|(_, value)| value.trim())
6✔
597
    }
598
}
599

600
pub type SapiRequestInfo = sapi_request_info;
601

602
impl SapiRequestInfo {
603
    /// Get the request method.
604
    #[must_use]
605
    pub fn request_method(&self) -> Option<&str> {
×
606
        if self.request_method.is_null() {
×
607
            return None;
×
608
        }
609
        unsafe { CStr::from_ptr(self.request_method).to_str().ok() }
×
610
    }
611

612
    /// Get the query string.
613
    #[must_use]
614
    pub fn query_string(&self) -> Option<&str> {
×
615
        if self.query_string.is_null() {
×
616
            return None;
×
617
        }
618
        unsafe { CStr::from_ptr(self.query_string).to_str().ok() }
×
619
    }
620

621
    /// Get the cookie data.
622
    #[must_use]
623
    pub fn cookie_data(&self) -> Option<&str> {
×
624
        if self.cookie_data.is_null() {
×
625
            return None;
×
626
        }
627
        unsafe { CStr::from_ptr(self.cookie_data).to_str().ok() }
×
628
    }
629

630
    /// Get the content length.
631
    #[must_use]
632
    pub fn content_length(&self) -> i64 {
×
633
        self.content_length
×
634
    }
635

636
    /// Get the path info.
637
    #[must_use]
638
    pub fn path_translated(&self) -> Option<&str> {
×
639
        if self.path_translated.is_null() {
×
640
            return None;
×
641
        }
642
        unsafe { CStr::from_ptr(self.path_translated).to_str().ok() }
×
643
    }
644

645
    /// Get the request uri.
646
    #[must_use]
647
    pub fn request_uri(&self) -> Option<&str> {
×
648
        if self.request_uri.is_null() {
×
649
            return None;
×
650
        }
651
        unsafe { CStr::from_ptr(self.request_uri).to_str().ok() }
×
652
    }
653

654
    // Todo: request_body _php_stream
655

656
    /// Get the content type.
657
    #[must_use]
658
    pub fn content_type(&self) -> Option<&str> {
×
659
        if self.content_type.is_null() {
×
660
            return None;
×
661
        }
662
        unsafe { CStr::from_ptr(self.content_type).to_str().ok() }
×
663
    }
664

665
    /// Whether the request consists of headers only.
666
    #[must_use]
667
    pub fn headers_only(&self) -> bool {
×
668
        self.headers_only
×
669
    }
670

671
    /// Whether the request has no headers.
672
    #[must_use]
673
    pub fn no_headers(&self) -> bool {
×
674
        self.no_headers
×
675
    }
676

677
    /// Whether the request headers have been read.
678
    #[must_use]
679
    pub fn headers_read(&self) -> bool {
×
680
        self.headers_read
×
681
    }
682

683
    // Todo: post_entry sapi_post_entry
684

685
    /// Get the auth user.
686
    #[must_use]
687
    pub fn auth_user(&self) -> Option<&str> {
×
688
        if self.auth_user.is_null() {
×
689
            return None;
×
690
        }
691
        unsafe { CStr::from_ptr(self.auth_user).to_str().ok() }
×
692
    }
693

694
    /// Get the auth password.
695
    #[must_use]
696
    pub fn auth_password(&self) -> Option<&str> {
×
697
        if self.auth_password.is_null() {
×
698
            return None;
×
699
        }
700
        unsafe { CStr::from_ptr(self.auth_password).to_str().ok() }
×
701
    }
702

703
    /// Get the auth digest.
704
    #[must_use]
705
    pub fn auth_digest(&self) -> Option<&str> {
×
706
        if self.auth_digest.is_null() {
×
707
            return None;
×
708
        }
709
        unsafe { CStr::from_ptr(self.auth_digest).to_str().ok() }
×
710
    }
711

712
    /// Get argv0.
713
    #[must_use]
714
    pub fn argv0(&self) -> Option<&str> {
×
715
        if self.argv0.is_null() {
×
716
            return None;
×
717
        }
718
        unsafe { CStr::from_ptr(self.argv0).to_str().ok() }
×
719
    }
720

721
    /// Get the current user.
722
    #[must_use]
723
    pub fn current_user(&self) -> Option<&str> {
×
724
        if self.current_user.is_null() {
×
725
            return None;
×
726
        }
727
        unsafe { CStr::from_ptr(self.current_user).to_str().ok() }
×
728
    }
729

730
    /// Get the current user length.
731
    #[must_use]
732
    pub fn current_user_length(&self) -> i32 {
×
733
        self.current_user_length
×
734
    }
735

736
    /// Get argvc.
737
    #[must_use]
738
    pub fn argvc(&self) -> i32 {
×
739
        self.argc
×
740
    }
741

742
    /// Get argv.
743
    #[must_use]
744
    pub fn argv(&self) -> Option<&str> {
×
745
        if self.argv.is_null() {
×
746
            return None;
×
747
        }
748
        unsafe { CStr::from_ptr(*self.argv).to_str().ok() }
×
749
    }
750

751
    /// Get the protocol number.
752
    #[must_use]
753
    pub fn proto_num(&self) -> i32 {
×
754
        self.proto_num
×
755
    }
756
}
757

758
/// Stores global variables used in the SAPI.
759
pub type FileGlobals = php_file_globals;
760

761
impl FileGlobals {
762
    /// Returns a reference to the PHP process globals.
763
    ///
764
    /// The process globals are guarded by a [`RwLock`]. There can be multiple
765
    /// immutable references at one time but only ever one mutable reference.
766
    /// Attempting to retrieve the globals while already holding the global
767
    /// guard will lead to a deadlock. Dropping the globals guard will release
768
    /// the lock.
769
    ///
770
    /// # Panics
771
    ///
772
    /// * If static file globals are not set
773
    pub fn get() -> GlobalReadGuard<Self> {
×
774
        // SAFETY: PHP executor globals are statically declared therefore should never
775
        // return an invalid pointer.
776
        let globals = unsafe { ext_php_rs_file_globals().as_ref() }
×
777
            .expect("Static file globals were invalid");
778

779
        cfg_if::cfg_if! {
×
780
            if #[cfg(php_zts)] {
×
NEW
781
                let guard = lock::FILE_GLOBALS_LOCK.with(RwLock::read_arc);
×
782
            } else {
783
                let guard = lock::FILE_GLOBALS_LOCK.read_arc();
×
784
            }
785
        }
786

787
        GlobalReadGuard { globals, guard }
788
    }
789

790
    /// Returns a mutable reference to the PHP executor globals.
791
    ///
792
    /// The executor globals are guarded by a [`RwLock`]. There can be multiple
793
    /// immutable references at one time but only ever one mutable reference.
794
    /// Attempting to retrieve the globals while already holding the global
795
    /// guard will lead to a deadlock. Dropping the globals guard will release
796
    /// the lock.
797
    #[must_use]
UNCOV
798
    pub fn get_mut() -> GlobalWriteGuard<Self> {
×
799
        // SAFETY: PHP executor globals are statically declared therefore should never
800
        // return an invalid pointer.
801
        let globals = unsafe { &mut *ext_php_rs_file_globals() };
×
802

803
        cfg_if::cfg_if! {
×
804
            if #[cfg(php_zts)] {
×
NEW
805
                let guard = lock::FILE_GLOBALS_LOCK.with(RwLock::write_arc);
×
806
            } else {
807
                let guard = lock::FILE_GLOBALS_LOCK.write_arc();
×
808
            }
809
        }
810

811
        GlobalWriteGuard { globals, guard }
812
    }
813

814
    /// Returns the stream wrappers
815
    #[must_use]
816
    pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> {
×
817
        unsafe { self.stream_wrappers.as_ref() }
×
818
    }
819
}
820

821
/// Executor globals rwlock.
822
///
823
/// PHP provides no indication if the executor globals are being accessed so
824
/// this is only effective on the Rust side.
825
#[cfg(not(php_zts))]
826
pub(crate) mod lock {
827
    use parking_lot::RwLock;
828
    use std::sync::{Arc, LazyLock};
829

830
    pub(crate) static GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
831
        LazyLock::new(|| Arc::new(RwLock::new(())));
1✔
832
    pub(crate) static PROCESS_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
833
        LazyLock::new(|| Arc::new(RwLock::new(())));
×
834
    pub(crate) static SAPI_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
835
        LazyLock::new(|| Arc::new(RwLock::new(())));
×
836
    pub(crate) static FILE_GLOBALS_LOCK: LazyLock<Arc<RwLock<()>>> =
837
        LazyLock::new(|| Arc::new(RwLock::new(())));
×
838
}
839

840
/// Executor globals rwlock.
841
///
842
/// PHP provides no indication if the executor globals are being accessed so
843
/// this is only effective on the Rust side.
844
#[cfg(php_zts)]
845
pub(crate) mod lock {
846
    use parking_lot::{const_rwlock, RwLock};
847
    use std::sync::Arc;
848

849
    thread_local! {
850
        pub(crate) static GLOBALS_LOCK: Arc<RwLock<()>> =  Arc::new(const_rwlock(()));
851
        pub(crate) static PROCESS_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
852
        pub(crate) static SAPI_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
853
        pub(crate) static FILE_GLOBALS_LOCK: Arc<RwLock<()>> = Arc::new( const_rwlock(()) );
854
    }
855
}
856

857
/// SAPI globals rwlock.
858
///
859
/// PHP provides no indication if the executor globals are being accessed so
860
/// this is only effective on the Rust side.
861
static SAPI_MODULE_LOCK: LazyLock<Arc<RwLock<()>>> = LazyLock::new(|| Arc::new(RwLock::new(())));
×
862

863
/// Wrapper guard that contains a reference to a given type `T`. Dropping a
864
/// guard releases the lock on the relevant rwlock.
865
pub struct GlobalReadGuard<T: 'static> {
866
    globals: &'static T,
867
    #[allow(dead_code)]
868
    guard: ArcRwLockReadGuard<RawRwLock, ()>,
869
}
870

871
impl<T> Deref for GlobalReadGuard<T> {
872
    type Target = T;
873

874
    fn deref(&self) -> &Self::Target {
61✔
875
        self.globals
61✔
876
    }
877
}
878

879
/// Wrapper guard that contains a mutable reference to a given type `T`.
880
/// Dropping a guard releases the lock on the relevant rwlock.
881
pub struct GlobalWriteGuard<T: 'static> {
882
    globals: &'static mut T,
883
    #[allow(dead_code)]
884
    guard: ArcRwLockWriteGuard<RawRwLock, ()>,
885
}
886

887
impl<T> Deref for GlobalWriteGuard<T> {
888
    type Target = T;
889

890
    fn deref(&self) -> &Self::Target {
×
891
        self.globals
×
892
    }
893
}
894

895
impl<T> DerefMut for GlobalWriteGuard<T> {
896
    fn deref_mut(&mut self) -> &mut Self::Target {
5✔
897
        self.globals
5✔
898
    }
899
}
900

901
#[cfg(feature = "embed")]
902
#[cfg(test)]
903
mod embed_tests {
904
    use super::*;
905
    use crate::embed::Embed;
906
    use std::os::raw::c_char;
907

908
    #[test]
909
    fn test_sapi_header() {
910
        Embed::run(|| {
911
            let headers = [
912
                ("Content-Type: text/html", "Content-Type", "text/html"),
913
                ("X: Custom:Header", "X", "Custom:Header"),
914
            ];
915

916
            for (header_text, name, value) in headers {
917
                let header = SapiHeader {
918
                    header: header_text.as_bytes().as_ptr() as *mut c_char,
919
                    header_len: header_text.len(),
920
                };
921
                assert_eq!(header.name(), name, "Header name mismatch");
922
                assert_eq!(header.value(), Some(value), "Header value mismatch");
923
                assert_eq!(
924
                    header.as_str(),
925
                    format!("{name}: {value}"),
926
                    "Header string mismatch"
927
                );
928
            }
929
        });
930
    }
931

932
    #[test]
933
    fn test_executor_globals() {
934
        let state = ExecutorGlobals::get().active;
935
        ExecutorGlobals::get_mut().active = !state;
936
        let changed = ExecutorGlobals::get().active;
937
        ExecutorGlobals::get_mut().active = state;
938
        assert_eq!(changed, !state);
939
    }
940

941
    #[test]
942
    fn test_compiler_globals() {
943
        let state = CompilerGlobals::get().in_compilation;
944
        CompilerGlobals::get_mut().in_compilation = !state;
945
        let changed = CompilerGlobals::get().in_compilation;
946
        CompilerGlobals::get_mut().in_compilation = state;
947
        assert_eq!(changed, !state);
948
    }
949
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc