Skip to content

Commit

Permalink
allows ZendStr to contain null bytes
Browse files Browse the repository at this point in the history
Closes #200
  • Loading branch information
ju1ius committed Nov 26, 2022
1 parent a331213 commit 81928ad
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 81 deletions.
2 changes: 1 addition & 1 deletion src/builders/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ impl ClassBuilder {
///
/// Returns an [`Error`] variant if the class could not be registered.
pub fn build(mut self) -> Result<&'static mut ClassEntry> {
self.ce.name = ZendStr::new_interned(&self.name, true)?.into_raw();
self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();

self.methods.push(FunctionEntry::end());
let func = Box::into_raw(self.methods.into_boxed_slice()) as *const FunctionEntry;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub enum Error {
/// The string could not be converted into a C-string due to the presence of
/// a NUL character.
InvalidCString,
/// The string could not be converted into a valid Utf8 string
InvalidUtf8,
/// Could not call the given function.
Callable,
/// An invalid exception type was thrown.
Expand Down Expand Up @@ -82,6 +84,7 @@ impl Display for Error {
f,
"String given contains NUL-bytes which cannot be present in a C string."
),
Error::InvalidUtf8 => write!(f, "Invalid Utf8 byte sequence."),
Error::Callable => write!(f, "Could not call given function."),
Error::InvalidException(flags) => {
write!(f, "Invalid exception type was thrown: {:?}", flags)
Expand Down
6 changes: 3 additions & 3 deletions src/types/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl ZendObject {
return Err(Error::InvalidProperty);
}

let mut name = ZendStr::new(name, false)?;
let mut name = ZendStr::new(name, false);
let mut rv = Zval::new();

let zv = unsafe {
Expand All @@ -162,7 +162,7 @@ impl ZendObject {
/// * `name` - The name of the property.
/// * `value` - The value to set the property to.
pub fn set_property(&mut self, name: &str, value: impl IntoZval) -> Result<()> {
let mut name = ZendStr::new(name, false)?;
let mut name = ZendStr::new(name, false);
let mut value = value.into_zval(false)?;

unsafe {
Expand All @@ -187,7 +187,7 @@ impl ZendObject {
/// * `name` - The name of the property.
/// * `query` - The 'query' to classify if a property exists.
pub fn has_property(&self, name: &str, query: PropertyQuery) -> Result<bool> {
let mut name = ZendStr::new(name, false)?;
let mut name = ZendStr::new(name, false);

Ok(unsafe {
self.handlers()?.has_property.ok_or(Error::InvalidScope)?(
Expand Down
134 changes: 69 additions & 65 deletions src/types/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::{
borrow::Cow,
convert::TryFrom,
convert::{TryFrom, TryInto},
ffi::{CStr, CString},
fmt::Debug,
slice,
Expand All @@ -30,7 +30,7 @@ use crate::{
/// cannot represent unsized types, an array of size 1 is used at the end of the
/// type to represent the contents of the string, therefore this type is
/// actually unsized. All constructors return [`ZBox<ZendStr>`], the owned
/// varaint.
/// variant.
///
/// Once the `ptr_metadata` feature lands in stable rust, this type can
/// potentially be changed to a DST using slices and metadata. See the tracking issue here: <https://github.com/rust-lang/rust/issues/81513>
Expand All @@ -46,20 +46,14 @@ static INTERNED_LOCK: Mutex<()> = const_mutex(());
// on the alias `ZendStr` :( <https://github.com/rust-lang/rust-clippy/issues/7702>
#[allow(clippy::len_without_is_empty)]
impl ZendStr {
/// Creates a new Zend string from a [`str`].
/// Creates a new Zend string from a slice of bytes.
///
/// # Parameters
///
/// * `str` - String content.
/// * `persistent` - Whether the string should persist through the request
/// boundary.
///
/// # Returns
///
/// Returns a result containing the Zend string if successful. Returns an
/// error if the given string contains NUL bytes, which cannot be
/// contained inside a C string.
///
/// # Panics
///
/// Panics if the function was unable to allocate memory for the Zend
Expand All @@ -78,10 +72,17 @@ impl ZendStr {
/// ```no_run
/// use ext_php_rs::types::ZendStr;
///
/// let s = ZendStr::new("Hello, world!", false).unwrap();
/// let s = ZendStr::new("Hello, world!", false);
/// let php = ZendStr::new([80, 72, 80], false);
/// ```
pub fn new(str: &str, persistent: bool) -> Result<ZBox<Self>> {
Ok(Self::from_c_str(&CString::new(str)?, persistent))
pub fn new(str: impl AsRef<[u8]>, persistent: bool) -> ZBox<Self> {
let s = str.as_ref();
unsafe {
let ptr = ext_php_rs_zend_string_init(s.as_ptr().cast(), s.len(), persistent)
.as_mut()
.expect("Failed to allocate memory for new Zend string");
ZBox::from_raw(ptr)
}
}

/// Creates a new Zend string from a [`CStr`].
Expand Down Expand Up @@ -126,7 +127,7 @@ impl ZendStr {
}
}

/// Creates a new interned Zend string from a [`str`].
/// Creates a new interned Zend string from a slice of bytes.
///
/// An interned string is only ever stored once and is immutable. PHP stores
/// the string in an internal hashtable which stores the interned
Expand All @@ -145,16 +146,12 @@ impl ZendStr {
/// * `persistent` - Whether the string should persist through the request
/// boundary.
///
/// # Returns
///
/// Returns a result containing the Zend string if successful. Returns an
/// error if the given string contains NUL bytes, which cannot be
/// contained inside a C string.
///
/// # Panics
///
/// Panics if the function was unable to allocate memory for the Zend
/// string.
/// Panics under the following circumstances:
///
/// * The function used to create interned strings has not been set.
/// * The function could not allocate enough memory for the Zend string.
///
/// # Safety
///
Expand All @@ -171,8 +168,16 @@ impl ZendStr {
///
/// let s = ZendStr::new_interned("PHP", true);
/// ```
pub fn new_interned(str: &str, persistent: bool) -> Result<ZBox<Self>> {
Ok(Self::interned_from_c_str(&CString::new(str)?, persistent))
pub fn new_interned(str: impl AsRef<[u8]>, persistent: bool) -> ZBox<Self> {
let _lock = INTERNED_LOCK.lock();
let s = str.as_ref();
unsafe {
let init = zend_string_init_interned.expect("`zend_string_init_interned` not ready");
let ptr = init(s.as_ptr().cast(), s.len() as _, persistent)
.as_mut()
.expect("Failed to allocate memory for new Zend string");
ZBox::from_raw(ptr)
}
}

/// Creates a new interned Zend string from a [`CStr`].
Expand Down Expand Up @@ -222,11 +227,8 @@ impl ZendStr {
let _lock = INTERNED_LOCK.lock();

unsafe {
let ptr = zend_string_init_interned.expect("`zend_string_init_interned` not ready")(
str.as_ptr(),
str.to_bytes().len() as _,
persistent,
);
let init = zend_string_init_interned.expect("`zend_string_init_interned` not ready");
let ptr = init(str.as_ptr(), str.to_bytes().len() as _, persistent);

ZBox::from_raw(
ptr.as_mut()
Expand All @@ -242,7 +244,7 @@ impl ZendStr {
/// ```no_run
/// use ext_php_rs::types::ZendStr;
///
/// let s = ZendStr::new("hello, world!", false).unwrap();
/// let s = ZendStr::new("hello, world!", false);
/// assert_eq!(s.len(), 13);
/// ```
pub fn len(&self) -> usize {
Expand All @@ -256,7 +258,7 @@ impl ZendStr {
/// ```no_run
/// use ext_php_rs::types::ZendStr;
///
/// let s = ZendStr::new("hello, world!", false).unwrap();
/// let s = ZendStr::new("hello, world!", false);
/// assert_eq!(s.is_empty(), false);
/// ```
pub fn is_empty(&self) -> bool {
Expand All @@ -266,29 +268,30 @@ impl ZendStr {
/// Returns a reference to the underlying [`CStr`] inside the Zend string.
pub fn as_c_str(&self) -> &CStr {
// SAFETY: Zend strings store their readable length in a fat pointer.
unsafe {
let slice = slice::from_raw_parts(self.val.as_ptr() as *const u8, self.len() + 1);
CStr::from_bytes_with_nul_unchecked(slice)
}
unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes()) }
}

/// Attempts to return a reference to the underlying [`str`] inside the Zend
/// Attempts to return a reference to the underlying bytes inside the Zend
/// string.
///
/// Returns the [`None`] variant if the [`CStr`] contains non-UTF-8
/// characters.
/// Returns an [Error::InvalidUtf8] variant if the [`str`] contains
/// non-UTF-8 characters.
///
/// # Example
///
/// ```no_run
/// use ext_php_rs::types::ZendStr;
///
/// let s = ZendStr::new("hello, world!", false).unwrap();
/// let as_str = s.as_str();
/// assert_eq!(as_str, Some("hello, world!"));
/// let s = ZendStr::new("hello, world!", false);
/// assert!(s.as_str().is_ok());
/// ```
pub fn as_str(&self) -> Option<&str> {
self.as_c_str().to_str().ok()
pub fn as_str(&self) -> Result<&str> {
std::str::from_utf8(self.as_bytes()).map_err(|_| Error::InvalidUtf8)
}

/// Returns a reference to the underlying bytes inside the Zend string.
pub fn as_bytes(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.val.as_ptr().cast(), self.len() + 1) }
}
}

Expand All @@ -300,22 +303,30 @@ unsafe impl ZBoxable for ZendStr {

impl Debug for ZendStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_c_str().fmt(f)
self.as_str().fmt(f)
}
}

impl ToOwned for ZendStr {
type Owned = ZBox<ZendStr>;
impl AsRef<[u8]> for ZendStr {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}

fn to_owned(&self) -> Self::Owned {
Self::from_c_str(self.as_c_str(), false)
impl<T> PartialEq<T> for ZendStr
where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
self.as_ref() == other.as_ref()
}
}

impl PartialEq for ZendStr {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_c_str().eq(other.as_c_str())
impl ToOwned for ZendStr {
type Owned = ZBox<ZendStr>;

fn to_owned(&self) -> Self::Owned {
Self::new(self.as_bytes(), false)
}
}

Expand All @@ -329,18 +340,15 @@ impl<'a> TryFrom<&'a ZendStr> for &'a str {
type Error = Error;

fn try_from(value: &'a ZendStr) -> Result<Self> {
value.as_str().ok_or(Error::InvalidCString)
value.as_str()
}
}

impl TryFrom<&ZendStr> for String {
type Error = Error;

fn try_from(value: &ZendStr) -> Result<Self> {
value
.as_str()
.map(|s| s.to_string())
.ok_or(Error::InvalidCString)
value.as_str().map(ToString::to_string)
}
}

Expand All @@ -362,18 +370,14 @@ impl From<CString> for ZBox<ZendStr> {
}
}

impl TryFrom<&str> for ZBox<ZendStr> {
type Error = Error;

fn try_from(value: &str) -> Result<Self> {
ZendStr::new(value, false)
impl From<&str> for ZBox<ZendStr> {
fn from(value: &str) -> Self {
ZendStr::new(value.as_bytes(), false)
}
}

impl TryFrom<String> for ZBox<ZendStr> {
type Error = Error;

fn try_from(value: String) -> Result<Self> {
impl From<String> for ZBox<ZendStr> {
fn from(value: String) -> Self {
ZendStr::new(value.as_str(), false)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/types/zval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl Zval {
/// convert other types into a [`String`], as it could not pass back a
/// [`&str`] in those cases.
pub fn str(&self) -> Option<&str> {
self.zend_str().and_then(|zs| zs.as_str())
self.zend_str().and_then(|zs| zs.as_str().ok())
}

/// Returns the value of the zval if it is a string and can be unpacked into
Expand Down Expand Up @@ -340,7 +340,7 @@ impl Zval {
/// * `val` - The value to set the zval as.
/// * `persistent` - Whether the string should persist between requests.
pub fn set_string(&mut self, val: &str, persistent: bool) -> Result<()> {
self.set_zend_string(ZendStr::new(val, persistent)?);
self.set_zend_string(ZendStr::new(val, persistent));
Ok(())
}

Expand Down Expand Up @@ -374,7 +374,7 @@ impl Zval {
/// * `val` - The value to set the zval as.
/// * `persistent` - Whether the string should persist between requests.
pub fn set_interned_string(&mut self, val: &str, persistent: bool) -> Result<()> {
self.set_zend_string(ZendStr::new_interned(val, persistent)?);
self.set_zend_string(ZendStr::new_interned(val, persistent));
Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions src/zend/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl ClassEntry {
/// could not be found or the class table has not been initialized.
pub fn try_find(name: &str) -> Option<&'static Self> {
ExecutorGlobals::get().class_table()?;
let mut name = ZendStr::new(name, false).ok()?;
let mut name = ZendStr::new(name, false);

unsafe {
crate::ffi::zend_lookup_class_ex(name.deref_mut(), std::ptr::null_mut(), 0).as_ref()
Expand Down Expand Up @@ -77,7 +77,7 @@ impl ClassEntry {
unsafe { self.__bindgen_anon_1.parent.as_ref() }
} else {
let name = unsafe { self.__bindgen_anon_1.parent_name.as_ref()? };
Self::try_find(name.as_str()?)
Self::try_find(name.as_str().ok()?)
}
}
}
Expand Down
10 changes: 3 additions & 7 deletions src/zend/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ impl ZendObjectHandlers {
.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()
.ok_or("Invalid property name was given")?,
);
let prop = props.get(prop_name.as_str()?);

// retval needs to be treated as initialized, so we set the type to null
let rv_mut = rv.as_mut().ok_or("Invalid return zval given")?;
Expand Down Expand Up @@ -138,7 +134,7 @@ impl ZendObjectHandlers {
.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().ok_or("Invalid property name given")?);
let prop = props.get(prop_name.as_str()?);
let value_mut = value.as_mut().ok_or("Invalid return zval given")?;

Ok(match prop {
Expand Down Expand Up @@ -220,7 +216,7 @@ impl ZendObjectHandlers {
.as_ref()
.ok_or("Invalid property name pointer given")?;
let props = T::get_metadata().get_properties();
let prop = props.get(prop_name.as_str().ok_or("Invalid property name given")?);
let prop = props.get(prop_name.as_str()?);
let self_ = &mut **obj;

match has_set_exists {
Expand Down

0 comments on commit 81928ad

Please sign in to comment.