diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 78dc8e76e0..d47dd36908 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -94,6 +94,7 @@ bind! { zend_hash_str_del, zend_hash_str_find, zend_hash_str_update, + zend_hash_update_ind, zend_internal_arg_info, zend_is_callable, zend_is_identical, @@ -116,6 +117,7 @@ bind! { zend_resource, zend_string, zend_string_init_interned, + zend_throw_error, zend_throw_exception_ex, zend_throw_exception_object, zend_type, diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index c788145efd..2056ecd939 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -1,14 +1,19 @@ -use std::{ffi::c_void, mem::MaybeUninit, os::raw::c_int, ptr}; +use std::{ + ffi::{c_void, CString}, + mem::MaybeUninit, + os::raw::c_int, + ptr, +}; use crate::{ class::RegisteredClass, exception::PhpResult, ffi::{ - std_object_handlers, zend_is_true, zend_object_handlers, zend_object_std_dtor, - zend_std_get_properties, zend_std_has_property, zend_std_read_property, - zend_std_write_property, + _zend_class_entry, std_object_handlers, zend_hash_update_ind, zend_is_true, + zend_object_handlers, zend_object_std_dtor, zend_std_get_properties, zend_std_has_property, + zend_std_read_property, zend_std_write_property, zend_throw_error, }, - flags::ZvalTypeFlags, + flags::{PropertyFlags, ZvalTypeFlags}, types::{ZendClassObject, ZendHashTable, ZendObject, ZendStr, Zval}, }; @@ -92,6 +97,7 @@ impl ZendObjectHandlers { let prop_name = member .as_ref() .ok_or("Invalid property name pointer given")?; + let self_ = &mut **obj; let props = T::get_metadata().get_properties(); let prop = props.get(prop_name.as_str()?); @@ -102,6 +108,9 @@ impl ZendObjectHandlers { Ok(match prop { Some(prop_info) => { + if prop_info.flags.contains(PropertyFlags::Private) { + bad_property_access::(prop_name.as_str()?); + } prop_info.prop.get(self_, rv_mut)?; rv } @@ -148,6 +157,9 @@ impl ZendObjectHandlers { Ok(match prop { Some(prop_info) => { + if prop_info.flags.contains(PropertyFlags::Private) { + bad_property_access::(prop_name.as_str()?); + } prop_info.prop.set(self_, value_mut)?; value } @@ -186,9 +198,19 @@ impl ZendObjectHandlers { if val.prop.get(self_, &mut zv).is_err() { continue; } - props.insert(name, zv).map_err(|e| { - format!("Failed to insert value into properties hashtable: {e:?}") - })?; + + let name = if val.flags.contains(PropertyFlags::Private) { + println!("Private property {name}"); + format!("\0{}\0{name}", T::CLASS_NAME) + } else if val.flags.contains(PropertyFlags::Protected) { + println!("Protected property {name}"); + format!("\0*\0{name}") + } else { + (*name).to_string() + }; + let mut name = ZendStr::new(name, false); + + zend_hash_update_ind(props, name.as_mut_ptr(), &mut zv); } Ok(()) @@ -296,3 +318,22 @@ impl ZendObjectHandlers { } } } + +/// Throws the error if a property is accessed incorrectly. +/// This only throws the error and does not perform any checks. +/// +/// # Panics +/// +/// * If the property name is not a valid c string. +unsafe fn bad_property_access(prop_name: &str) { + zend_throw_error( + ptr::null_mut::<_zend_class_entry>(), + CString::new(format!( + "Cannot access private property {}::${}", + T::CLASS_NAME, + prop_name + )) + .expect("Failed to convert property name") + .as_ptr(), + ); +} diff --git a/tests/src/integration/class/class.php b/tests/src/integration/class/class.php index a6295f6e0e..0adc596567 100644 --- a/tests/src/integration/class/class.php +++ b/tests/src/integration/class/class.php @@ -1,8 +1,12 @@ getString() === 'lorem ipsum'); @@ -25,4 +29,7 @@ assert($class::staticCall('Php') === 'Hello Php'); // Call static from class -assert(TestClass::staticCall('Php') === 'Hello Php'); +assert(Foo\TestClass::staticCall('Php') === 'Hello Php'); + +assert_exception_thrown(fn() => $class->private_string = 'private2'); +assert_exception_thrown(fn() => $class->private_string); diff --git a/tests/src/integration/class/mod.rs b/tests/src/integration/class/mod.rs index c94f5e7761..cc43a84cd6 100644 --- a/tests/src/integration/class/mod.rs +++ b/tests/src/integration/class/mod.rs @@ -1,11 +1,16 @@ use ext_php_rs::prelude::*; #[php_class] +#[php(name = "Foo\\TestClass")] pub struct TestClass { string: String, number: i32, #[php(prop)] boolean: bool, + #[php(prop, flags = "ext_php_rs::flags::PropertyFlags::Private")] + private_string: String, + #[php(prop, flags = ext_php_rs::flags::PropertyFlags::Protected)] + protected_string: String, } #[php_impl] @@ -41,6 +46,8 @@ pub fn test_class(string: String, number: i32) -> TestClass { string, number, boolean: true, + private_string: "private".to_string(), + protected_string: "protected".to_string(), } } diff --git a/tests/src/integration/mod.rs b/tests/src/integration/mod.rs index bd16cc75c3..afe5ba49ac 100644 --- a/tests/src/integration/mod.rs +++ b/tests/src/integration/mod.rs @@ -64,8 +64,8 @@ mod test { stderr: {} ", output.status, - String::from_utf8(output.stdout).unwrap(), - String::from_utf8(output.stderr).unwrap() + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) ); } }