Skip to content

Commit 2b51f12

Browse files
authored
fix: support Laravel (#38)
1 parent 7531c58 commit 2b51f12

File tree

7 files changed

+132
-157
lines changed

7 files changed

+132
-157
lines changed

__test__/handler.spec.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ test('Capture exceptions', async (t) => {
7070
const res = await php.handleRequest(req)
7171

7272
// TODO: should exceptions be thrown rather than message-captured?
73-
t.assert(/Hello, from PHP!/.test(res.exception))
73+
t.assert(/Uncaught Exception: Hello, from PHP!/.test(res.log))
7474
})
7575

7676
test('Support request and response headers', async (t) => {

crates/php/src/embed.rs

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use std::{
22
env::Args,
3-
ffi::CString,
43
ops::DerefMut,
54
path::{Path, PathBuf},
65
sync::Arc,
76
};
87

98
use ext_php_rs::{
9+
alloc::{efree, estrdup},
1010
error::Error,
1111
ffi::{php_execute_script, sapi_get_default_content_type},
1212
zend::{try_catch, try_catch_first, ExecutorGlobals, SapiGlobals},
@@ -17,7 +17,7 @@ use lang_handler::{rewrite::Rewriter, Handler, Request, Response};
1717
use super::{
1818
sapi::{ensure_sapi, Sapi},
1919
scopes::{FileHandleScope, RequestScope},
20-
strings::{cstr, nullable_cstr, translate_path},
20+
strings::translate_path,
2121
EmbedRequestError, EmbedStartError, RequestContext,
2222
};
2323

@@ -206,34 +206,28 @@ impl Handler for Embed {
206206
.to_string();
207207

208208
// Convert REQUEST_URI and PATH_TRANSLATED to C strings
209-
let request_uri = cstr(request_uri)
210-
.map_err(|_| EmbedRequestError::FailedToSetRequestInfo("request_uri".into()))?;
211-
let path_translated = cstr(translated_path.clone())
212-
.map_err(|_| EmbedRequestError::FailedToSetRequestInfo("path_translated".into()))?;
209+
let request_uri = estrdup(request_uri);
210+
let path_translated = estrdup(translated_path.clone());
213211

214212
// Extract request method, query string, and headers
215-
let request_method = cstr(request.method())
216-
.map_err(|_| EmbedRequestError::FailedToSetRequestInfo("request_method".into()))?;
217-
let query_string = cstr(url.query().unwrap_or(""))
218-
.map_err(|_| EmbedRequestError::FailedToSetRequestInfo("query_string".into()))?;
213+
let request_method = estrdup(request.method());
214+
let query_string = estrdup(url.query().unwrap_or(""));
219215

220216
let headers = request.headers();
221-
let content_type = nullable_cstr(headers.get("Content-Type"))
222-
.map_err(|_| EmbedRequestError::FailedToSetRequestInfo("content_type".into()))?;
217+
let content_type = headers
218+
.get("Content-Type")
219+
.map(estrdup)
220+
.unwrap_or(std::ptr::null_mut());
223221
let content_length = headers
224222
.get("Content-Length")
225223
.map(|v| v.parse::<i64>().unwrap_or(0))
226224
.unwrap_or(0);
227-
let cookie_data = nullable_cstr(headers.get("Cookie"))
228-
.map_err(|_| EmbedRequestError::FailedToSetRequestInfo("cookie_data".into()))?;
229225

230226
// Prepare argv and argc
231227
let argc = self.args.len() as i32;
232228
let mut argv_ptrs = vec![];
233229
for arg in self.args.iter() {
234-
let string = CString::new(arg.as_bytes())
235-
.map_err(|_| EmbedRequestError::FailedToSetRequestInfo("argv".into()))?;
236-
argv_ptrs.push(string.into_raw());
230+
argv_ptrs.push(estrdup(arg.to_owned()));
237231
}
238232

239233
let script_name = translated_path.clone();
@@ -263,55 +257,53 @@ impl Handler for Embed {
263257

264258
globals.request_info.content_type = content_type;
265259
globals.request_info.content_length = content_length;
266-
globals.request_info.cookie_data = cookie_data;
267260
}
268261

269262
let response_builder = {
270263
let _request_scope = RequestScope::new()?;
271264

272265
// Run script in its own try/catch so bailout doesn't skip request shutdown.
273-
try_catch(|| {
266+
{
274267
let mut file_handle = FileHandleScope::new(script_name.clone());
275-
if !unsafe { php_execute_script(file_handle.deref_mut()) } {
276-
// return Err(EmbedException::ExecuteError);
268+
try_catch(|| unsafe { php_execute_script(file_handle.deref_mut()) })
269+
.map_err(|_| EmbedRequestError::Bailout)?;
270+
}
271+
272+
if let Some(err) = ExecutorGlobals::take_exception() {
273+
{
274+
let mut globals = SapiGlobals::get_mut();
275+
globals.sapi_headers.http_response_code = 500;
277276
}
278277

279-
if let Some(err) = ExecutorGlobals::take_exception() {
280-
{
281-
let mut globals = SapiGlobals::get_mut();
282-
globals.sapi_headers.http_response_code = 500;
283-
}
278+
let ex = Error::Exception(err);
284279

285-
let ex = Error::Exception(err);
286-
287-
if let Some(ctx) = RequestContext::current() {
288-
ctx.response_builder().exception(ex.to_string());
289-
}
290-
291-
// TODO: Should exceptions be raised or only captured on
292-
// the response builder?
293-
return Err(EmbedRequestError::Exception(ex.to_string()));
280+
if let Some(ctx) = RequestContext::current() {
281+
ctx.response_builder().exception(ex.to_string());
294282
}
295283

296-
Ok(())
297-
})
298-
.unwrap_or(Err(EmbedRequestError::Bailout))?;
284+
return Err(EmbedRequestError::Exception(ex.to_string()));
285+
};
299286

300-
let (mimetype, http_response_code) = {
301-
let globals = SapiGlobals::get();
302-
(
303-
globals.sapi_headers.mimetype,
304-
globals.sapi_headers.http_response_code,
305-
)
287+
let (mut mimetype, http_response_code) = {
288+
let h = SapiGlobals::get().sapi_headers;
289+
(h.mimetype, h.http_response_code)
306290
};
307291

308-
let mime_ptr = unsafe { (mimetype as *mut std::ffi::c_char).as_ref() }
309-
.or(unsafe { sapi_get_default_content_type().as_ref() })
310-
.ok_or(EmbedRequestError::FailedToDetermineContentType)?;
292+
if mimetype.is_null() {
293+
mimetype = unsafe { sapi_get_default_content_type() };
294+
}
295+
296+
let mime_ptr =
297+
unsafe { mimetype.as_ref() }.ok_or(EmbedRequestError::FailedToDetermineContentType)?;
311298

312299
let mime = unsafe { std::ffi::CStr::from_ptr(mime_ptr) }
313300
.to_str()
314-
.map_err(|_| EmbedRequestError::FailedToDetermineContentType)?;
301+
.map_err(|_| EmbedRequestError::FailedToDetermineContentType)?
302+
.to_owned();
303+
304+
unsafe {
305+
efree(mimetype.cast::<u8>());
306+
}
315307

316308
RequestContext::current()
317309
.map(|ctx| {

crates/php/src/sapi.rs

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use std::{
88
use bytes::Buf;
99

1010
use ext_php_rs::{
11+
alloc::{efree, estrdup},
1112
builders::SapiBuilder,
1213
embed::SapiModule,
13-
exception::register_error_observer,
14+
// exception::register_error_observer,
1415
ffi::{
1516
ext_php_rs_sapi_per_thread_init, ext_php_rs_sapi_shutdown, ext_php_rs_sapi_startup,
1617
php_module_shutdown, php_module_startup, php_register_variable, sapi_send_headers,
@@ -22,10 +23,7 @@ use ext_php_rs::{
2223

2324
use once_cell::sync::OnceCell;
2425

25-
use crate::{
26-
strings::{cstr, drop_str, reclaim_str},
27-
EmbedRequestError, EmbedStartError, RequestContext,
28-
};
26+
use crate::{EmbedRequestError, EmbedStartError, RequestContext};
2927
use lang_handler::Header;
3028

3129
// This is a helper to ensure that PHP is initialized and deinitialized at the
@@ -76,17 +74,17 @@ impl Sapi {
7674
// writing to the ResponseBuilder here. When php_execute_script fails it
7775
// should read that and could return an error or write it to the
7876
// ResponseBuilder there.
79-
register_error_observer(|_error_type, _file, _line, message| {
80-
message
81-
.as_str()
82-
.inspect(|msg| {
83-
if let Some(ctx) = RequestContext::current() {
84-
ctx.response_builder().exception(*msg);
85-
}
86-
})
87-
// TODO: Report this error somehow?
88-
.ok();
89-
});
77+
// TODO: Having error observers registered crashes Laravel.
78+
// register_error_observer(|error_type, file, line, message| {
79+
// let file = file.as_str().expect("should convert zend_string to str");
80+
// // TODO: Report this error somehow?
81+
// if let Ok(msg) = message.as_str() {
82+
// println!("PHP Error #{}: {}\n\tfrom {}:{}", error_type, msg, file, line);
83+
// if let Some(ctx) = RequestContext::current() {
84+
// ctx.response_builder().exception(msg);
85+
// }
86+
// }
87+
// });
9088

9189
Ok(Sapi(RwLock::new(boxed)))
9290
}
@@ -130,12 +128,6 @@ impl Drop for Sapi {
130128
fn drop(&mut self) {
131129
self.shutdown().unwrap();
132130

133-
{
134-
let sapi = self.0.write().expect("should get writable sapi");
135-
136-
reclaim_str(sapi.executable_location);
137-
}
138-
139131
unsafe {
140132
sapi_shutdown();
141133
ext_php_rs_sapi_shutdown();
@@ -239,25 +231,34 @@ pub extern "C" fn sapi_module_deactivate() -> c_int {
239231
let mut globals = SapiGlobals::get_mut();
240232

241233
for i in 0..globals.request_info.argc {
242-
drop_str(unsafe { *globals.request_info.argv.offset(i as isize) });
234+
maybe_efree(unsafe { *globals.request_info.argv.offset(i as isize) }.cast::<u8>());
243235
}
244236

245237
globals.request_info.argc = 0;
246238
globals.request_info.argv = std::ptr::null_mut();
247239

248-
drop_str(globals.request_info.request_method as *mut c_char);
249-
drop_str(globals.request_info.query_string as *mut c_char);
250-
drop_str(globals.request_info.request_uri as *mut c_char);
251-
drop_str(globals.request_info.path_translated as *mut c_char);
252-
drop_str(globals.request_info.content_type as *mut c_char);
253-
drop_str(globals.request_info.cookie_data as *mut c_char);
254-
drop_str(globals.request_info.auth_user as *mut c_char);
255-
drop_str(globals.request_info.auth_password as *mut c_char);
256-
drop_str(globals.request_info.auth_digest as *mut c_char);
240+
maybe_efree(globals.request_info.request_method as *mut u8);
241+
maybe_efree(globals.request_info.content_type as *mut u8);
242+
maybe_efree(globals.request_info.query_string.cast::<u8>());
243+
maybe_efree(globals.request_info.request_uri.cast::<u8>());
244+
maybe_efree(globals.request_info.path_translated.cast::<u8>());
245+
maybe_efree(globals.request_info.auth_user.cast::<u8>());
246+
maybe_efree(globals.request_info.auth_password.cast::<u8>());
247+
maybe_efree(globals.request_info.auth_digest.cast::<u8>());
248+
249+
maybe_efree(globals.request_info.cookie_data.cast::<u8>());
257250

258251
ZEND_RESULT_CODE_SUCCESS
259252
}
260253

254+
fn maybe_efree(ptr: *mut u8) {
255+
if !ptr.is_null() {
256+
unsafe {
257+
efree(ptr);
258+
}
259+
}
260+
}
261+
261262
#[no_mangle]
262263
pub extern "C" fn sapi_module_ub_write(str: *const c_char, str_length: usize) -> usize {
263264
if str.is_null() || str_length == 0 {
@@ -275,14 +276,7 @@ pub extern "C" fn sapi_module_ub_write(str: *const c_char, str_length: usize) ->
275276

276277
#[no_mangle]
277278
pub extern "C" fn sapi_module_flush(_server_context: *mut c_void) {
278-
if let Some(ctx) = RequestContext::current() {
279-
unsafe { sapi_send_headers() };
280-
let mut globals = SapiGlobals::get_mut();
281-
globals.headers_sent = 1;
282-
ctx
283-
.response_builder()
284-
.status(globals.sapi_headers.http_response_code);
285-
}
279+
unsafe { sapi_send_headers() };
286280
}
287281

288282
#[no_mangle]
@@ -329,7 +323,12 @@ pub extern "C" fn sapi_module_read_post(buffer: *mut c_char, length: usize) -> u
329323

330324
#[no_mangle]
331325
pub extern "C" fn sapi_module_read_cookies() -> *mut c_char {
332-
SapiGlobals::get().request_info.cookie_data
326+
RequestContext::current()
327+
.map(|ctx| match ctx.request().headers().get("Cookie") {
328+
Some(cookie) => estrdup(cookie),
329+
None => std::ptr::null_mut(),
330+
})
331+
.unwrap_or(std::ptr::null_mut())
333332
}
334333

335334
fn env_var<K, V>(
@@ -341,10 +340,9 @@ where
341340
K: AsRef<str>,
342341
V: AsRef<str>,
343342
{
344-
let c_value = cstr(value.as_ref())
345-
.map_err(|_| EmbedRequestError::FailedToSetServerVar(key.as_ref().to_owned()))?;
343+
let c_value = estrdup(value.as_ref());
346344
env_var_c(vars, key, c_value)?;
347-
reclaim_str(c_value);
345+
maybe_efree(c_value.cast::<u8>());
348346

349347
Ok(())
350348
}
@@ -357,12 +355,11 @@ fn env_var_c<K>(
357355
where
358356
K: AsRef<str>,
359357
{
360-
let c_key = cstr(key.as_ref())
361-
.map_err(|_| EmbedRequestError::FailedToSetServerVar(key.as_ref().to_owned()))?;
358+
let c_key = estrdup(key.as_ref());
362359
unsafe {
363360
php_register_variable(c_key, c_value, vars);
364361
}
365-
reclaim_str(c_key);
362+
maybe_efree(c_key.cast::<u8>());
366363

367364
Ok(())
368365
}
@@ -400,7 +397,7 @@ pub extern "C" fn sapi_module_register_server_variables(vars: *mut ext_php_rs::t
400397
let req_info = &globals.request_info;
401398

402399
let docroot = ctx.docroot();
403-
let docroot_cstr = cstr(docroot.display().to_string())?;
400+
let docroot_str = docroot.display().to_string();
404401

405402
let script_filename = req_info.path_translated;
406403
let script_name = if !req_info.request_uri.is_null() {
@@ -414,12 +411,18 @@ pub extern "C" fn sapi_module_register_server_variables(vars: *mut ext_php_rs::t
414411
env_var(vars, "SERVER_ADMIN", "webmaster@localhost")?;
415412
env_var(vars, "GATEWAY_INTERFACE", "CGI/1.1")?;
416413

417-
env_var_c(vars, "PHP_SELF", script_name as *mut c_char)?;
418-
env_var_c(vars, "SCRIPT_NAME", script_name as *mut c_char)?;
414+
// Laravel seems to think "/register" should be "/index.php/register"?
415+
// env_var_c(vars, "PHP_SELF", script_name as *mut c_char)?;
416+
env_var(vars, "PHP_SELF", request.url().path())?;
417+
418+
// TODO: is "/register", should be "/index.php"
419+
env_var(vars, "SCRIPT_NAME", request.url().path())?;
420+
// env_var_c(vars, "SCRIPT_NAME", script_name as *mut c_char)?;
421+
env_var_c(vars, "PATH_INFO", script_name as *mut c_char)?;
419422
env_var_c(vars, "SCRIPT_FILENAME", script_filename)?;
420423
env_var_c(vars, "PATH_TRANSLATED", script_filename)?;
421-
env_var_c(vars, "DOCUMENT_ROOT", docroot_cstr)?;
422-
env_var_c(vars, "CONTEXT_DOCUMENT_ROOT", docroot_cstr)?;
424+
env_var(vars, "DOCUMENT_ROOT", docroot_str.clone())?;
425+
env_var(vars, "CONTEXT_DOCUMENT_ROOT", docroot_str)?;
423426

424427
if let Ok(server_name) = hostname::get() {
425428
if let Some(server_name) = server_name.to_str() {

0 commit comments

Comments
 (0)