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

davidcole1340 / ext-php-rs / 16323306954

16 Jul 2025 03:10PM UTC coverage: 22.222% (+0.6%) from 21.654%
16323306954

Pull #482

github

web-flow
Merge de76d2402 into 1166e2910
Pull Request #482: feat(cargo-php)!: escalate privilege and to copy extension and edit ini file

0 of 48 new or added lines in 1 file covered. (0.0%)

193 existing lines in 10 files now uncovered.

870 of 3915 relevant lines covered (22.22%)

3.63 hits per line

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

15.26
/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() }
177✔
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();
118✔
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() }
9✔
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();
6✔
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>> {
1✔
159
        {
160
            // This avoid a write lock if there is no exception.
161
            if Self::get().exception.is_null() {
2✔
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()?) })
1✔
173
    }
174

175
    /// Checks if the executor globals contain an exception.
176
    #[must_use]
177
    pub fn has_exception() -> bool {
56✔
178
        !Self::get().exception.is_null()
56✔
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

229
pub type CompilerGlobals = _zend_compiler_globals;
230

231
impl 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> {
2✔
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() }
6✔
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();
4✔
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> {
2✔
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() }
6✔
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();
4✔
282
            }
283
        }
284

285
        GlobalWriteGuard { globals, guard }
286
    }
287
}
288

289
/// Stores the SAPI module used in the PHP executor.
290
pub type SapiModule = _sapi_module_struct;
291

292
impl 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.
335
pub type ProcessGlobals = php_core_globals;
336

337
impl 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.
502
pub type SapiGlobals = sapi_globals_struct;
503

504
impl 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`.
566
pub type SapiHeaders = sapi_headers_struct;
567

568
impl<'a> SapiHeaders {
569
    /// Create an iterator over the headers.
570
    pub fn headers(&'a mut self) -> ZendLinkedListIterator<'a, SapiHeader> {
×
UNCOV
571
        self.headers.iter()
×
572
    }
573
}
574

575
/// Manage a key/value pair of SAPI headers.
576
pub type SapiHeader = sapi_header_struct;
577

578
impl<'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 {
6✔
586
        unsafe {
587
            let slice = slice::from_raw_parts(self.header as *const u8, self.header_len);
24✔
588
            str::from_utf8(slice).expect("Invalid header string")
18✔
589
        }
590
    }
591

592
    /// Returns the header name (key).
593
    #[must_use]
594
    pub fn name(&'a self) -> &'a str {
2✔
595
        self.as_str().split(':').next().unwrap_or("").trim()
6✔
596
    }
597

598
    /// Returns the header value.
599
    #[must_use]
600
    pub fn value(&'a self) -> Option<&'a str> {
2✔
601
        self.as_str().split_once(':').map(|(_, value)| value.trim())
10✔
602
    }
603
}
604

605
pub type SapiRequestInfo = sapi_request_info;
606

607
impl SapiRequestInfo {
608
    /// Get the request method.
609
    #[must_use]
610
    pub fn request_method(&self) -> Option<&str> {
×
UNCOV
611
        if self.request_method.is_null() {
×
612
            return None;
×
613
        }
UNCOV
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> {
×
UNCOV
620
        if self.query_string.is_null() {
×
621
            return None;
×
622
        }
UNCOV
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> {
×
UNCOV
629
        if self.cookie_data.is_null() {
×
630
            return None;
×
631
        }
UNCOV
632
        unsafe { CStr::from_ptr(self.cookie_data).to_str().ok() }
×
633
    }
634

635
    /// Get the content length.
636
    #[must_use]
UNCOV
637
    pub fn content_length(&self) -> i64 {
×
UNCOV
638
        self.content_length
×
639
    }
640

641
    /// Get the path info.
642
    #[must_use]
643
    pub fn path_translated(&self) -> Option<&str> {
×
UNCOV
644
        if self.path_translated.is_null() {
×
645
            return None;
×
646
        }
UNCOV
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> {
×
UNCOV
653
        if self.request_uri.is_null() {
×
654
            return None;
×
655
        }
UNCOV
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> {
×
UNCOV
664
        if self.content_type.is_null() {
×
665
            return None;
×
666
        }
UNCOV
667
        unsafe { CStr::from_ptr(self.content_type).to_str().ok() }
×
668
    }
669

670
    /// Whether the request consists of headers only.
671
    #[must_use]
UNCOV
672
    pub fn headers_only(&self) -> bool {
×
UNCOV
673
        self.headers_only
×
674
    }
675

676
    /// Whether the request has no headers.
677
    #[must_use]
UNCOV
678
    pub fn no_headers(&self) -> bool {
×
UNCOV
679
        self.no_headers
×
680
    }
681

682
    /// Whether the request headers have been read.
683
    #[must_use]
UNCOV
684
    pub fn headers_read(&self) -> bool {
×
UNCOV
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> {
×
UNCOV
693
        if self.auth_user.is_null() {
×
694
            return None;
×
695
        }
UNCOV
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> {
×
UNCOV
702
        if self.auth_password.is_null() {
×
703
            return None;
×
704
        }
UNCOV
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> {
×
UNCOV
711
        if self.auth_digest.is_null() {
×
712
            return None;
×
713
        }
UNCOV
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> {
×
UNCOV
720
        if self.argv0.is_null() {
×
721
            return None;
×
722
        }
UNCOV
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> {
×
UNCOV
729
        if self.current_user.is_null() {
×
730
            return None;
×
731
        }
UNCOV
732
        unsafe { CStr::from_ptr(self.current_user).to_str().ok() }
×
733
    }
734

735
    /// Get the current user length.
736
    #[must_use]
UNCOV
737
    pub fn current_user_length(&self) -> i32 {
×
UNCOV
738
        self.current_user_length
×
739
    }
740

741
    /// Get argvc.
742
    #[must_use]
UNCOV
743
    pub fn argvc(&self) -> i32 {
×
UNCOV
744
        self.argc
×
745
    }
746

747
    /// Get argv.
748
    #[must_use]
749
    pub fn argv(&self) -> Option<&str> {
×
UNCOV
750
        if self.argv.is_null() {
×
751
            return None;
×
752
        }
UNCOV
753
        unsafe { CStr::from_ptr(*self.argv).to_str().ok() }
×
754
    }
755

756
    /// Get the protocol number.
757
    #[must_use]
UNCOV
758
    pub fn proto_num(&self) -> i32 {
×
UNCOV
759
        self.proto_num
×
760
    }
761
}
762

763
/// Stores global variables used in the SAPI.
764
pub type FileGlobals = php_file_globals;
765

766
impl 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
UNCOV
778
    pub fn get() -> GlobalReadGuard<Self> {
×
779
        // SAFETY: PHP executor globals are statically declared therefore should never
780
        // return an invalid pointer.
UNCOV
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 {
UNCOV
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]
UNCOV
803
    pub fn get_mut() -> GlobalWriteGuard<Self> {
×
804
        // SAFETY: PHP executor globals are statically declared therefore should never
805
        // return an invalid pointer.
UNCOV
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 {
UNCOV
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]
UNCOV
821
    pub fn stream_wrappers(&self) -> Option<&'static ZendHashTable> {
×
UNCOV
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))]
831
pub(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(())));
3✔
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<()>>> =
UNCOV
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)]
850
pub(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.
UNCOV
866
static 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.
870
pub struct GlobalReadGuard<T: 'static> {
871
    globals: &'static T,
872
    #[allow(dead_code)]
873
    guard: ArcRwLockReadGuard<RawRwLock, ()>,
874
}
875

876
impl<T> Deref for GlobalReadGuard<T> {
877
    type Target = T;
878

879
    fn deref(&self) -> &Self::Target {
61✔
880
        self.globals
61✔
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.
886
pub struct GlobalWriteGuard<T: 'static> {
887
    globals: &'static mut T,
888
    #[allow(dead_code)]
889
    guard: ArcRwLockWriteGuard<RawRwLock, ()>,
890
}
891

892
impl<T> Deref for GlobalWriteGuard<T> {
893
    type Target = T;
894

UNCOV
895
    fn deref(&self) -> &Self::Target {
×
UNCOV
896
        self.globals
×
897
    }
898
}
899

900
impl<T> DerefMut for GlobalWriteGuard<T> {
901
    fn deref_mut(&mut self) -> &mut Self::Target {
5✔
902
        self.globals
5✔
903
    }
904
}
905

906
#[cfg(feature = "embed")]
907
#[cfg(test)]
908
mod 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
}
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