• 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

66.67
/src/embed/mod.rs
1
//! Provides implementations for running php code from rust.
2
//! It only works on linux for now and you should have `php-embed` installed
3
//!
4
//! This crate was only test with PHP 8.2 please report any issue with other
5
//! version You should only use this crate for test purpose, it's not production
6
//! ready
7

8
mod ffi;
9
mod sapi;
10

11
use crate::boxed::ZBox;
12
use crate::ffi::{
13
    _zend_file_handle__bindgen_ty_1, php_execute_script, zend_eval_string, zend_file_handle,
14
    zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS,
15
};
16
use crate::types::{ZendObject, Zval};
17
use crate::zend::{panic_wrapper, try_catch, ExecutorGlobals};
18
use parking_lot::{const_rwlock, RwLock};
19
use std::ffi::{c_char, c_void, CString, NulError};
20
use std::panic::{resume_unwind, RefUnwindSafe};
21
use std::path::Path;
22
use std::ptr::null_mut;
23

24
pub use ffi::*;
25
pub use sapi::SapiModule;
26

27
/// The embed module provides a way to run php code from rust
28
pub struct Embed;
29

30
/// Error type for the embed module
31
#[derive(Debug)]
32
pub enum EmbedError {
33
    /// Failed to initialize
34
    InitError,
35
    /// The script exited with a non-zero code
36
    ExecuteError(Option<ZBox<ZendObject>>),
37
    /// The script exited with a non-zero code
38
    ExecuteScriptError,
39
    /// The script is not a valid [`CString`]
40
    InvalidEvalString(NulError),
41
    /// Failed to open the script file at the given path
42
    InvalidPath,
43
    /// The script was executed but an exception was thrown
44
    CatchError,
45
}
46

47
impl EmbedError {
48
    /// Check if the error is a bailout
49
    #[must_use]
50
    pub fn is_bailout(&self) -> bool {
1✔
51
        matches!(self, EmbedError::CatchError)
1✔
52
    }
53
}
54

55
static RUN_FN_LOCK: RwLock<()> = const_rwlock(());
56

57
impl Embed {
58
    /// Run a php script from a file
59
    ///
60
    /// This function will only work correctly when used inside the `Embed::run`
61
    /// function otherwise behavior is unexpected
62
    ///
63
    /// # Returns
64
    ///
65
    /// * `Ok(())` - The script was executed successfully
66
    ///
67
    /// # Errors
68
    ///
69
    /// * `Err(EmbedError)` - An error occurred during the execution of the
70
    ///   script
71
    ///
72
    /// # Example
73
    ///
74
    /// ```
75
    /// use ext_php_rs::embed::Embed;
76
    ///
77
    /// Embed::run(|| {
78
    ///     let result = Embed::run_script("src/embed/test-script.php");
79
    ///
80
    ///     assert!(result.is_ok());
81
    /// });
82
    /// ```
83
    pub fn run_script<P: AsRef<Path>>(path: P) -> Result<(), EmbedError> {
4✔
84
        let path = match path.as_ref().to_str() {
8✔
85
            Some(path) => match CString::new(path) {
8✔
UNCOV
86
                Ok(path) => path,
×
87
                Err(err) => return Err(EmbedError::InvalidEvalString(err)),
×
88
            },
UNCOV
89
            None => return Err(EmbedError::InvalidPath),
×
90
        };
91

92
        let mut file_handle = zend_file_handle {
93
            #[allow(clippy::used_underscore_items)]
UNCOV
94
            handle: _zend_file_handle__bindgen_ty_1 { fp: null_mut() },
×
95
            filename: null_mut(),
×
96
            opened_path: null_mut(),
×
97
            type_: 0,
98
            primary_script: false,
99
            in_list: false,
UNCOV
100
            buf: null_mut(),
×
101
            len: 0,
102
        };
103

104
        unsafe {
UNCOV
105
            zend_stream_init_filename(&raw mut file_handle, path.as_ptr());
×
106
        }
107

108
        let exec_result = try_catch(|| unsafe { php_execute_script(&raw mut file_handle) });
12✔
109

UNCOV
110
        match exec_result {
×
111
            Err(_) => Err(EmbedError::CatchError),
×
112
            Ok(true) => Ok(()),
3✔
113
            Ok(false) => Err(EmbedError::ExecuteScriptError),
1✔
114
        }
115
    }
116

117
    /// Start and run embed sapi engine
118
    ///
119
    /// This function will allow to run php code from rust, the same PHP context
120
    /// is keep between calls inside the function passed to this method.
121
    /// Which means subsequent calls to `Embed::eval` or `Embed::run_script`
122
    /// will be able to access variables defined in previous calls
123
    ///
124
    /// # Returns
125
    ///
126
    /// * R - The result of the function passed to this method
127
    ///
128
    /// R must implement [`Default`] so it can be returned in case of a bailout
129
    ///
130
    /// # Example
131
    ///
132
    /// ```
133
    /// use ext_php_rs::embed::Embed;
134
    ///
135
    /// Embed::run(|| {
136
    ///    let _ = Embed::eval("$foo = 'foo';");
137
    ///    let foo = Embed::eval("$foo;");
138
    ///    assert!(foo.is_ok());
139
    ///    assert_eq!(foo.unwrap().string().unwrap(), "foo");
140
    /// });
141
    /// ```
142
    pub fn run<R, F: FnMut() -> R + RefUnwindSafe>(func: F) -> R
55✔
143
    where
144
        R: Default,
145
    {
146
        // @TODO handle php thread safe
147
        //
148
        // This is to prevent multiple threads from running php at the same time
149
        // At some point we should detect if php is compiled with thread safety and
150
        // avoid doing that in this case
151
        let _guard = RUN_FN_LOCK.write();
165✔
152

153
        let panic = unsafe {
154
            ext_php_rs_embed_callback(
155
                0,
156
                null_mut(),
55✔
157
                panic_wrapper::<R, F>,
55✔
158
                (&raw const func).cast::<c_void>(),
110✔
159
            )
160
        };
161

162
        // This can happen if there is a bailout
163
        if panic.is_null() {
110✔
164
            return R::default();
6✔
165
        }
166

UNCOV
167
        match unsafe { *Box::from_raw(panic.cast::<std::thread::Result<R>>()) } {
×
168
            Ok(r) => r,
94✔
169
            Err(err) => {
2✔
170
                // we resume the panic here so it can be caught correctly by the test framework
UNCOV
171
                resume_unwind(err);
×
172
            }
173
        }
174
    }
175

176
    /// Evaluate a php code
177
    ///
178
    /// This function will only work correctly when used inside the `Embed::run`
179
    /// function
180
    ///
181
    /// # Returns
182
    ///
183
    /// * `Ok(Zval)` - The result of the evaluation
184
    ///
185
    /// # Errors
186
    ///
187
    /// * `Err(EmbedError)` - An error occurred during the evaluation
188
    ///
189
    /// # Example
190
    ///
191
    /// ```
192
    /// use ext_php_rs::embed::Embed;
193
    ///
194
    /// Embed::run(|| {
195
    ///    let foo = Embed::eval("$foo = 'foo';");
196
    ///    assert!(foo.is_ok());
197
    /// });
198
    /// ```
199
    pub fn eval(code: &str) -> Result<Zval, EmbedError> {
10✔
200
        let cstr = match CString::new(code) {
20✔
201
            Ok(cstr) => cstr,
UNCOV
202
            Err(err) => return Err(EmbedError::InvalidEvalString(err)),
×
203
        };
204

205
        let mut result = Zval::new();
206

207
        let exec_result = try_catch(|| unsafe {
208
            zend_eval_string(
10✔
209
                cstr.as_ptr().cast::<c_char>(),
20✔
210
                &raw mut result,
10✔
211
                c"run".as_ptr().cast(),
30✔
212
            )
213
        });
214

215
        match exec_result {
216
            Err(_) => Err(EmbedError::CatchError),
1✔
217
            Ok(ZEND_RESULT_CODE_SUCCESS) => Ok(result),
8✔
218
            Ok(_) => Err(EmbedError::ExecuteError(ExecutorGlobals::take_exception())),
1✔
219
        }
220
    }
221
}
222

223
#[cfg(test)]
224
mod tests {
225
    #![allow(clippy::unwrap_used)]
226
    use super::Embed;
227

228
    #[test]
229
    fn test_run() {
230
        Embed::run(|| {
231
            let result = Embed::eval("$foo = 'foo';");
232

233
            assert!(result.is_ok());
234
        });
235
    }
236

237
    #[test]
238
    fn test_run_error() {
239
        Embed::run(|| {
240
            let result = Embed::eval("stupid code;");
241

242
            assert!(result.is_err());
243
        });
244
    }
245

246
    #[test]
247
    fn test_run_script() {
248
        Embed::run(|| {
249
            let result = Embed::run_script("src/embed/test-script.php");
250

251
            assert!(result.is_ok());
252

253
            let zval = Embed::eval("$foo;").unwrap();
254

255
            assert!(zval.is_object());
256

257
            let obj = zval.object().unwrap();
258

259
            assert_eq!(obj.get_class_name().unwrap(), "Test");
260
        });
261
    }
262

263
    #[test]
264
    fn test_run_script_error() {
265
        Embed::run(|| {
266
            let result = Embed::run_script("src/embed/test-script-exception.php");
267

268
            assert!(result.is_err());
269
        });
270
    }
271

272
    #[test]
273
    #[should_panic(expected = "test panic")]
274
    fn test_panic() {
275
        Embed::run::<(), _>(|| {
276
            panic!("test panic");
277
        });
278
    }
279

280
    #[test]
281
    fn test_return() {
282
        let foo = Embed::run(|| "foo");
283

284
        assert_eq!(foo, "foo");
285
    }
286

287
    #[test]
288
    fn test_eval_bailout() {
289
        Embed::run(|| {
290
            // TODO: For PHP 8.5, this needs to be replaced, as `E_USER_ERROR` is deprecated.
291
            //       Currently, this seems to still be the best way to trigger a bailout.
292
            let result = Embed::eval("trigger_error(\"Fatal error\", E_USER_ERROR);");
293

294
            assert!(result.is_err());
295
            assert!(result.unwrap_err().is_bailout());
296
        });
297
    }
298
}
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