diff --git a/example/skel/.gitignore b/example/skel/.gitignore index 72b1d08cc0..43db77f061 100644 --- a/example/skel/.gitignore +++ b/example/skel/.gitignore @@ -1,4 +1,6 @@ /target Cargo.lock /.vscode -expand.rs \ No newline at end of file +expand.rs +vendor +composer.lock diff --git a/example/skel/composer.json b/example/skel/composer.json new file mode 100644 index 0000000000..fb8425595f --- /dev/null +++ b/example/skel/composer.json @@ -0,0 +1,13 @@ +{ + "name": "rust/skel", + "license": "MIT OR Apache-2.0", + "authors": [ + { + "name": "David Cole", + "email": "david.cole1340@gmail.com" + } + ], + "require": { + "psy/psysh": "^0.10.8" + } +} diff --git a/example/skel/src/lib.rs b/example/skel/src/lib.rs index a474a80336..6939558262 100644 --- a/example/skel/src/lib.rs +++ b/example/skel/src/lib.rs @@ -12,7 +12,10 @@ use ext_php_rs::{ flags::MethodFlags, function::FunctionBuilder, module::{ModuleBuilder, ModuleEntry}, - types::{array::ZendHashTable, long::ZendLong, object::ZendClassObject, zval::Zval}, + types::{ + array::ZendHashTable, long::ZendLong, object::ZendClassObject, string::ZendString, + zval::Zval, + }, }, ZendObjectHandler, }; @@ -133,12 +136,18 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry { .returns(DataType::Array, false, false) .build(); + let iter = FunctionBuilder::new("skel_unpack", skel_unpack) + .arg(Arg::new("arr", DataType::String)) + .returns(DataType::String, false, false) + .build(); + ModuleBuilder::new("ext-skel", "0.1.0") .info_function(php_module_info) .startup_function(module_init) .function(funct) .function(array) .function(t) + .function(iter) .build() .into_raw() } @@ -178,3 +187,15 @@ pub extern "C" fn skeleton_array(execute_data: &mut ExecutionData, _retval: &mut pub extern "C" fn test_array(_execute_data: &mut ExecutionData, retval: &mut Zval) { retval.set_array(vec![1, 2, 3, 4]); } + +pub extern "C" fn skel_unpack(execute_data: &mut ExecutionData, retval: &mut Zval) { + let mut packed = Arg::new("arr", DataType::String); + parse_args!(execute_data, packed); + + let zv = packed.zval().unwrap(); + let val = unsafe { zv.binary::() }; + dbg!(val); + //let v = vec![1i32, 2, 4, 8]; + let v = [1i32, 2, 4, 8]; + retval.set_binary(v); +} diff --git a/example/skel/test.php b/example/skel/test.php index 84de41b76b..38a1703694 100644 --- a/example/skel/test.php +++ b/example/skel/test.php @@ -1,2 +1,7 @@ ) -> *mut ZendString; + + /// Unpacks a given Zend binary string into a Rust vector. Can be used to pass data from `pack` + /// in PHP to Rust without encoding into another format. Note that the data *must* be all one + /// type, as this implementation only unpacks one type. + /// + /// # Safety + /// + /// This is an unsafe function. There is no way to tell if the data passed from the PHP + /// function is indeed the correct format. Exercise caution when using the `unpack` functions. + /// In fact, even when used correctly, the results can differ depending on the platform and the + /// size of each type on the platform. Consult the [`pack`](https://www.php.net/manual/en/function.pack.php) + /// function documentation for more details. + /// + /// # Parameters + /// + /// * `s` - The Zend string containing the binary data. + unsafe fn unpack_into(s: &ZendString) -> Vec; +} + +/// Implements the [`Pack`] trait for a given type. +/// The first argument is the type and the second argument is the factor of size difference between +/// the given type and an 8-bit integer e.g. impl Unpack for i32, factor = 4 => 4 * 8 = 32 +#[macro_use] +macro_rules! pack_impl { + ($t: ty, $d: expr) => { + unsafe impl Pack for $t { + fn pack_into(vec: Vec) -> *mut ZendString { + let len = vec.len() * $d; + let ptr = Box::into_raw(vec.into_boxed_slice()); + unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, len as _, false) } + } + + unsafe fn unpack_into(s: &ZendString) -> Vec { + let len = s.len / $d; + let mut result = Vec::with_capacity(len as _); + let ptr = s.val.as_ptr() as *const $t; + + for i in 0..len { + result.push(*ptr.offset(i as _)); + } + + result + } + } + }; +} + +pack_impl!(u8, 1); +pack_impl!(i8, 1); + +pack_impl!(u16, 2); +pack_impl!(i16, 2); + +pack_impl!(u32, 4); +pack_impl!(i32, 4); + +pack_impl!(u64, 8); +pack_impl!(i64, 8); + +pack_impl!(f32, 4); +pack_impl!(f64, 8); diff --git a/src/php/types/zval.rs b/src/php/types/zval.rs index 5a4f207d60..f7c4e12fc5 100644 --- a/src/php/types/zval.rs +++ b/src/php/types/zval.rs @@ -11,6 +11,7 @@ use crate::{ zval, }, errors::{Error, Result}, + php::pack::Pack, }; use crate::php::{ @@ -76,9 +77,11 @@ impl<'a> Zval { // We can safely cast our *const c_char into a *const u8 as both // only occupy one byte. unsafe { - let len = (*self.value.str_).len; - let ptr = (*self.value.str_).val.as_ptr() as *const u8; - let _str = std::str::from_utf8(slice::from_raw_parts(ptr, len as usize)).ok()?; + let _str = std::str::from_utf8(slice::from_raw_parts( + (*self.value.str_).val.as_ptr() as *const u8, + (*self.value.str_).len as usize, + )) + .ok()?; Some(_str.to_string()) } @@ -87,6 +90,24 @@ impl<'a> Zval { } } + /// Returns the value of the zval if it is a string and can be unpacked into a vector of a + /// given type. Similar to the [`unpack`](https://www.php.net/manual/en/function.unpack.php) + /// in PHP, except you can only unpack one type. + /// + /// # Safety + /// + /// There is no way to tell if the data stored in the string is actually of the given type. + /// The results of this function can also differ from platform-to-platform due to the different + /// representation of some types on different platforms. Consult the [`pack`](https://www.php.net/manual/en/function.pack.php) + /// function documentation for more details. + pub unsafe fn binary(&self) -> Option> { + if self.is_string() { + Some(T::unpack_into(self.value.str_.as_ref()?)) + } else { + None + } + } + /// Returns the value of the zval if it is a resource. pub fn resource(&self) -> Option<*mut zend_resource> { // TODO: Can we improve this function? I haven't done much research into @@ -262,6 +283,17 @@ impl<'a> Zval { self.u1.type_info = ZvalTypeFlags::StringEx.bits(); } + /// Sets the value of the zval as a binary string. + /// + /// # Parameters + /// + /// * `val` - The value to set the zval as. + pub fn set_binary>(&mut self, val: U) { + let ptr = T::pack_into(val.as_ref().to_vec()); + self.value.str_ = ptr; + self.u1.type_info = ZvalTypeFlags::StringEx.bits(); + } + /// Sets the value of the zval as a persistent string. /// This means that the zend string will persist between /// request lifetime.