1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use std::ffi::CString;
use std::ptr::{self, NonNull};
use crate::{
error::Error,
ffi::{
php_register_url_stream_wrapper, php_register_url_stream_wrapper_volatile, php_stream,
php_stream_context, php_stream_locate_url_wrapper, php_stream_wrapper,
php_stream_wrapper_ops, php_unregister_url_stream_wrapper,
php_unregister_url_stream_wrapper_volatile, zend_string,
},
types::ZendStr,
};
/// Wrapper for PHP streams
pub type StreamWrapper = php_stream_wrapper;
/// Stream opener function
pub type StreamOpener = unsafe extern "C" fn(
*mut StreamWrapper,
*const std::ffi::c_char,
*const std::ffi::c_char,
i32,
*mut *mut zend_string,
*mut php_stream_context,
i32,
*const std::ffi::c_char,
u32,
*const std::ffi::c_char,
u32,
) -> *mut Stream;
impl StreamWrapper {
/// Locates the stream wrapper registered for `name`.
///
/// # Safety
///
/// The returned wrapper is owned by PHP and lives in the per-request stream
/// wrapper table, so the caller must not hold `'a` beyond the current
/// request. The lifetime is unconstrained by the arguments and must be
/// chosen to reflect that.
#[inline]
#[must_use]
pub unsafe fn get<'a>(name: &str) -> Option<&'a Self> {
let ptr = Self::locate(name)?;
// SAFETY: `locate` returned a non-null wrapper pointer owned by PHP, and
// the caller guarantees `'a` does not outlive the current request.
Some(unsafe { ptr.as_ref() })
}
/// Locates the stream wrapper registered for `name` for mutation.
///
/// # Safety
///
/// In addition to the request-scoped validity required by [`Self::get`], the
/// caller must ensure no other reference to the same wrapper is live for
/// `'a`. The wrapper table is process-wide state shared with PHP and with
/// every other extension, so this function cannot check exclusivity.
#[inline]
#[must_use]
pub unsafe fn get_mut<'a>(name: &str) -> Option<&'a mut Self> {
let mut ptr = Self::locate(name)?;
// SAFETY: `locate` returned a non-null wrapper pointer owned by PHP, and
// the caller guarantees both request-scoped validity and exclusivity.
Some(unsafe { ptr.as_mut() })
}
/// Looks up the wrapper pointer for `name`.
///
/// `name` is copied into a NUL-terminated C string because
/// `php_stream_locate_url_wrapper` scans the path until it reaches a
/// character outside `[A-Za-z0-9+-.]`, which would read past the end of a
/// non-terminated Rust string slice.
#[inline]
fn locate(name: &str) -> Option<NonNull<Self>> {
let name = CString::new(name).ok()?;
// SAFETY: `name` is a valid NUL-terminated C string that outlives the
// call, and a null `path_for_open` is accepted by PHP.
let result = unsafe { php_stream_locate_url_wrapper(name.as_ptr(), ptr::null_mut(), 0) };
NonNull::new(result)
}
/// Register stream wrapper for name
///
/// # Errors
///
/// * `Error::StreamWrapperRegistrationFailure` - If the stream wrapper
/// could not be registered
/// * `Error::InvalidCString` - If the name contains a NUL byte
pub fn register(self, name: &str) -> Result<Self, Error> {
let name = std::ffi::CString::new(name)?;
// We have to convert it to a static so owned streamwrapper doesn't get dropped.
let copy = Box::new(self);
let copy = Box::leak(copy);
let result = unsafe { php_register_url_stream_wrapper(name.as_ptr(), copy) };
if result == 0 {
Ok(*copy)
} else {
Err(Error::StreamWrapperRegistrationFailure)
}
}
/// Register volatile stream wrapper for name
///
/// # Errors
///
/// * `Error::StreamWrapperRegistrationFailure` - If the stream wrapper
/// could not be registered
pub fn register_volatile(self, name: &str) -> Result<Self, Error> {
// We have to convert it to a static so owned streamwrapper doesn't get dropped.
let copy = Box::new(self);
let copy = Box::leak(copy);
let name = ZendStr::new(name, false);
let result =
unsafe { php_register_url_stream_wrapper_volatile((*name).as_ptr().cast_mut(), copy) };
if result == 0 {
Ok(*copy)
} else {
Err(Error::StreamWrapperRegistrationFailure)
}
}
/// Unregister stream wrapper by name
///
/// # Errors
///
/// * `Error::StreamWrapperUnregistrationFailure` - If the stream wrapper
/// could not be unregistered
/// * `Error::InvalidCString` - If the name contains a NUL byte
pub fn unregister(name: &str) -> Result<(), Error> {
let name = std::ffi::CString::new(name)?;
match unsafe { php_unregister_url_stream_wrapper(name.as_ptr()) } {
0 => Ok(()),
_ => Err(Error::StreamWrapperUnregistrationFailure),
}
}
/// Unregister volatile stream wrapper by name
///
/// # Errors
///
/// * `Error::StreamWrapperUnregistrationFailure` - If the stream wrapper
/// could not be unregistered
pub fn unregister_volatile(name: &str) -> Result<(), Error> {
let name = ZendStr::new(name, false);
match unsafe { php_unregister_url_stream_wrapper_volatile((*name).as_ptr().cast_mut()) } {
0 => Ok(()),
_ => Err(Error::StreamWrapperUnregistrationFailure),
}
}
/// Get the operations the stream wrapper can perform
#[must_use]
pub fn wops(&self) -> &php_stream_wrapper_ops {
unsafe { &*self.wops }
}
/// Get the mutable operations the stream can perform
pub fn wops_mut(&mut self) -> &mut php_stream_wrapper_ops {
unsafe { &mut *(self.wops.cast_mut()) }
}
}
/// A PHP stream
pub type Stream = php_stream;
/// Operations that can be performed with a stream wrapper
pub type StreamWrapperOps = php_stream_wrapper_ops;
#[cfg(feature = "embed")]
#[cfg(test)]
mod tests {
use super::StreamWrapper;
use crate::error::Error;
#[test]
fn unregister_rejects_a_name_with_a_nul_byte() {
let err = StreamWrapper::unregister("we\0ird");
assert!(matches!(err, Err(Error::InvalidCString { position: 2 })));
}
}