apr/
strings.rs

1//! String utilities for safe C string handling.
2use crate::pool::Pool;
3use std::ffi::{c_char, CStr, CString};
4use std::marker::PhantomData;
5
6/// Borrowed byte string backed by pool memory
7///
8/// This represents bytes from C strings, potentially containing non-UTF-8 data.
9/// Use this when you need zero-copy access to C string data.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct BStr<'a> {
12    data: &'a [u8],
13    _pool: PhantomData<&'a Pool<'a>>,
14}
15
16impl<'a> BStr<'a> {
17    /// Create a BStr from a C string pointer (unsafe)
18    ///
19    /// # Safety
20    /// - ptr must be valid for the lifetime 'a
21    /// - ptr must point to null-terminated string
22    /// - The underlying pool must remain alive for 'a
23    pub unsafe fn from_ptr(ptr: *const c_char) -> Self {
24        if ptr.is_null() {
25            BStr {
26                data: &[],
27                _pool: PhantomData,
28            }
29        } else {
30            let cstr = CStr::from_ptr(ptr);
31            BStr {
32                data: cstr.to_bytes(),
33                _pool: PhantomData,
34            }
35        }
36    }
37
38    /// Get the bytes as a slice
39    pub fn as_bytes(&self) -> &[u8] {
40        self.data
41    }
42
43    /// Try to convert to UTF-8 string
44    pub fn to_str(&self) -> Result<&str, std::str::Utf8Error> {
45        std::str::from_utf8(self.data)
46    }
47
48    /// Convert to UTF-8 string with lossy conversion
49    pub fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
50        String::from_utf8_lossy(self.data)
51    }
52
53    /// Check if the string is empty
54    pub fn is_empty(&self) -> bool {
55        self.data.is_empty()
56    }
57
58    /// Get the length in bytes
59    pub fn len(&self) -> usize {
60        self.data.len()
61    }
62}
63
64impl<'a> AsRef<[u8]> for BStr<'a> {
65    fn as_ref(&self) -> &[u8] {
66        self.data
67    }
68}
69
70impl<'a> std::ops::Deref for BStr<'a> {
71    type Target = [u8];
72
73    fn deref(&self) -> &Self::Target {
74        self.data
75    }
76}
77
78impl<'a> std::fmt::Display for BStr<'a> {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}", String::from_utf8_lossy(self.data))
81    }
82}
83
84impl<'a> From<&'a [u8]> for BStr<'a> {
85    fn from(data: &'a [u8]) -> Self {
86        BStr {
87            data,
88            _pool: PhantomData,
89        }
90    }
91}
92
93impl<'a> From<&'a str> for BStr<'a> {
94    fn from(s: &'a str) -> Self {
95        BStr {
96            data: s.as_bytes(),
97            _pool: PhantomData,
98        }
99    }
100}
101
102impl<'a> std::borrow::Borrow<[u8]> for BStr<'a> {
103    fn borrow(&self) -> &[u8] {
104        self.data
105    }
106}
107
108impl<'a> PartialEq<&str> for BStr<'a> {
109    fn eq(&self, other: &&str) -> bool {
110        self.data == other.as_bytes()
111    }
112}
113
114impl<'a> PartialEq<str> for BStr<'a> {
115    fn eq(&self, other: &str) -> bool {
116        self.data == other.as_bytes()
117    }
118}
119
120impl<'a> PartialEq<&[u8]> for BStr<'a> {
121    fn eq(&self, other: &&[u8]) -> bool {
122        self.data == *other
123    }
124}
125
126/// UTF-8 validated borrowed string backed by pool memory
127#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub struct BStrUtf8<'a> {
129    data: &'a str,
130    _pool: PhantomData<&'a Pool<'a>>,
131}
132
133impl<'a> BStrUtf8<'a> {
134    /// Create a BStrUtf8 from a C string pointer, validating UTF-8
135    ///
136    /// # Safety  
137    /// - ptr must be valid for the lifetime 'a
138    /// - ptr must point to null-terminated string
139    /// - The underlying pool must remain alive for 'a
140    pub unsafe fn from_ptr(ptr: *const c_char) -> Result<Self, std::str::Utf8Error> {
141        if ptr.is_null() {
142            Ok(BStrUtf8 {
143                data: "",
144                _pool: PhantomData,
145            })
146        } else {
147            let cstr = CStr::from_ptr(ptr);
148            let s = cstr.to_str()?;
149            Ok(BStrUtf8 {
150                data: s,
151                _pool: PhantomData,
152            })
153        }
154    }
155
156    /// Get the string slice
157    pub fn as_str(&self) -> &str {
158        self.data
159    }
160
161    /// Check if the string is empty
162    pub fn is_empty(&self) -> bool {
163        self.data.is_empty()
164    }
165
166    /// Get the length in bytes
167    pub fn len(&self) -> usize {
168        self.data.len()
169    }
170}
171
172impl<'a> AsRef<str> for BStrUtf8<'a> {
173    fn as_ref(&self) -> &str {
174        self.data
175    }
176}
177
178impl<'a> std::ops::Deref for BStrUtf8<'a> {
179    type Target = str;
180
181    fn deref(&self) -> &Self::Target {
182        self.data
183    }
184}
185
186impl<'a> std::fmt::Display for BStrUtf8<'a> {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        write!(f, "{}", self.data)
189    }
190}
191
192impl<'a> From<&'a str> for BStrUtf8<'a> {
193    fn from(data: &'a str) -> Self {
194        BStrUtf8 {
195            data,
196            _pool: PhantomData,
197        }
198    }
199}
200
201impl<'a> TryFrom<&'a [u8]> for BStrUtf8<'a> {
202    type Error = std::str::Utf8Error;
203
204    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
205        let s = std::str::from_utf8(data)?;
206        Ok(BStrUtf8 {
207            data: s,
208            _pool: PhantomData,
209        })
210    }
211}
212
213impl<'a> std::borrow::Borrow<str> for BStrUtf8<'a> {
214    fn borrow(&self) -> &str {
215        self.data
216    }
217}
218
219impl<'a> PartialEq<&str> for BStrUtf8<'a> {
220    fn eq(&self, other: &&str) -> bool {
221        self.data == *other
222    }
223}
224
225impl<'a> PartialEq<String> for BStrUtf8<'a> {
226    fn eq(&self, other: &String) -> bool {
227        self.data == other.as_str()
228    }
229}
230
231/// Safe wrapper for pool-allocated C strings
232pub struct PoolString<'a> {
233    ptr: *const c_char,
234    _marker: PhantomData<&'a Pool<'a>>,
235}
236
237impl<'a> PoolString<'a> {
238    /// Get the raw C string pointer (for FFI calls)
239    pub fn as_ptr(&self) -> *const c_char {
240        self.ptr
241    }
242
243    /// Get as a BStr (borrowed bytes)
244    pub fn as_bstr(&self) -> BStr<'a> {
245        unsafe { BStr::from_ptr(self.ptr) }
246    }
247
248    /// Try to get as UTF-8 string
249    pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
250        unsafe {
251            let cstr = CStr::from_ptr(self.ptr);
252            cstr.to_str()
253        }
254    }
255
256    /// Get as bytes
257    pub fn as_bytes(&self) -> &[u8] {
258        unsafe {
259            let cstr = CStr::from_ptr(self.ptr);
260            cstr.to_bytes()
261        }
262    }
263
264    /// Get length in bytes
265    pub fn len(&self) -> usize {
266        self.as_bstr().len()
267    }
268
269    /// Check if empty
270    pub fn is_empty(&self) -> bool {
271        self.len() == 0
272    }
273}
274
275impl<'a> std::fmt::Display for PoolString<'a> {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        match self.as_str() {
278            Ok(s) => write!(f, "{}", s),
279            Err(_) => write!(f, "{:?}", self.as_bytes()),
280        }
281    }
282}
283
284impl<'a> std::fmt::Debug for PoolString<'a> {
285    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        match self.as_str() {
287            Ok(s) => write!(f, "PoolString({:?})", s),
288            Err(_) => write!(f, "PoolString({:?})", self.as_bytes()),
289        }
290    }
291}
292
293/// Duplicate a Rust string into pool-allocated memory as a C string
294pub fn pstrdup<'a>(s: &str, pool: &'a Pool) -> Result<PoolString<'a>, std::ffi::NulError> {
295    let cstring = CString::new(s)?;
296    let ptr = unsafe { apr_sys::apr_pstrdup(pool.as_mut_ptr(), cstring.as_ptr()) };
297    Ok(PoolString {
298        ptr,
299        _marker: PhantomData,
300    })
301}
302
303/// Get raw pointer version (for advanced users)
304pub fn pstrdup_raw(s: &str, pool: &Pool<'_>) -> Result<*const c_char, std::ffi::NulError> {
305    Ok(pstrdup(s, pool)?.as_ptr())
306}
307
308/// Create a pool-allocated C string from a Rust string
309///
310/// This is a convenience function that allocates a null-terminated C string
311/// in the given pool's memory and returns it as a `CStr` reference.
312///
313/// # Example
314/// ```
315/// use apr::pool::Pool;
316/// use apr::strings::make_cstring;
317///
318/// let pool = Pool::new();
319/// let cstr = make_cstring("hello", &pool).unwrap();
320/// assert_eq!(cstr.to_str().unwrap(), "hello");
321/// ```
322pub fn make_cstring<'a>(s: &str, pool: &'a Pool) -> Result<&'a CStr, std::ffi::NulError> {
323    let ptr = pstrdup_raw(s, pool)?;
324    Ok(unsafe { CStr::from_ptr(ptr) })
325}
326
327/// Duplicate a limited portion of a Rust string into pool-allocated memory
328pub fn pstrndup<'a>(
329    s: &str,
330    n: usize,
331    pool: &'a Pool,
332) -> Result<PoolString<'a>, std::ffi::NulError> {
333    let cstring = CString::new(s)?;
334    let ptr = unsafe { apr_sys::apr_pstrndup(pool.as_mut_ptr(), cstring.as_ptr(), n) };
335    Ok(PoolString {
336        ptr,
337        _marker: PhantomData,
338    })
339}
340
341/// Copy bytes into pool-allocated memory (not null-terminated)
342///
343/// Returns an immutable slice since Pool is borrowed immutably
344pub fn pmemdup<'a>(data: &[u8], pool: &'a Pool) -> &'a [u8] {
345    unsafe {
346        let ptr = apr_sys::apr_pmemdup(
347            pool.as_mut_ptr(),
348            data.as_ptr() as *const std::ffi::c_void,
349            data.len(),
350        ) as *const u8;
351        std::slice::from_raw_parts(ptr, data.len())
352    }
353}
354
355// Note: apr_pstrcat is a varargs function which is hard to call from Rust.
356// If needed, concatenate strings manually and use pstrdup.
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361
362    #[test]
363    fn test_bstr() {
364        let test_str = "Hello, world!";
365        let cstring = CString::new(test_str).unwrap();
366
367        unsafe {
368            let bstr = BStr::from_ptr(cstring.as_ptr());
369            assert_eq!(bstr.as_bytes(), test_str.as_bytes());
370            assert_eq!(bstr.to_str().unwrap(), test_str);
371            assert!(!bstr.is_empty());
372            assert_eq!(bstr.len(), test_str.len());
373        }
374    }
375
376    #[test]
377    fn test_bstr_utf8() {
378        let test_str = "Hello, 世界!";
379        let cstring = CString::new(test_str).unwrap();
380
381        unsafe {
382            let bstr_utf8 = BStrUtf8::from_ptr(cstring.as_ptr()).unwrap();
383            assert_eq!(bstr_utf8.as_str(), test_str);
384            assert!(!bstr_utf8.is_empty());
385            assert_eq!(bstr_utf8.len(), test_str.len());
386        }
387    }
388
389    #[test]
390    fn test_pool_string_operations() {
391        let pool = Pool::new();
392
393        let pooled = pstrdup("test string", &pool).unwrap();
394        assert_eq!(pooled.as_str().unwrap(), "test string");
395        assert_eq!(pooled.len(), 11);
396        assert!(!pooled.is_empty());
397
398        let bstr = pooled.as_bstr();
399        assert_eq!(bstr.to_str().unwrap(), "test string");
400
401        // Test pmemdup
402        let data = b"binary data";
403        let copied = pmemdup(data, &pool);
404        assert_eq!(copied, data);
405    }
406
407    #[test]
408    fn test_make_cstring() {
409        let pool = Pool::new();
410
411        // Test basic functionality
412        let cstr = make_cstring("hello world", &pool).unwrap();
413        assert_eq!(cstr.to_str().unwrap(), "hello world");
414        assert_eq!(cstr.to_bytes(), b"hello world");
415
416        // Test with empty string
417        let empty_cstr = make_cstring("", &pool).unwrap();
418        assert_eq!(empty_cstr.to_str().unwrap(), "");
419        assert_eq!(empty_cstr.to_bytes(), b"");
420
421        // Test with Unicode
422        let unicode_cstr = make_cstring("Hello, 世界!", &pool).unwrap();
423        assert_eq!(unicode_cstr.to_str().unwrap(), "Hello, 世界!");
424
425        // Test that null bytes in the middle cause an error
426        let result = make_cstring("hello\0world", &pool);
427        assert!(result.is_err());
428    }
429
430    #[test]
431    fn test_pool_string_display() {
432        let pool = Pool::new();
433        let pooled = pstrdup("hello", &pool).unwrap();
434        assert_eq!(format!("{}", pooled), "hello");
435        assert_eq!(format!("{:?}", pooled), "PoolString(\"hello\")");
436    }
437
438    #[test]
439    fn test_bstr_traits() {
440        let data = b"hello world";
441        let bstr = BStr::from(&data[..]);
442
443        // Test Clone, Copy
444        let bstr2 = bstr;
445        // Intentionally testing that Clone works even though Copy is implemented
446        #[allow(clippy::clone_on_copy)]
447        let bstr3 = bstr.clone();
448        assert_eq!(bstr, bstr2);
449        assert_eq!(bstr2, bstr3);
450
451        // Test Display and Deref
452        assert_eq!(format!("{}", bstr), "hello world");
453        assert_eq!(bstr.len(), 11);
454        assert_eq!(&bstr[0..5], b"hello");
455
456        // Test From conversions
457        let from_str = BStr::from("test");
458        assert_eq!(from_str.as_bytes(), b"test");
459    }
460
461    #[test]
462    fn test_bstr_utf8_traits() {
463        let s = "hello 🦀";
464        let bstr_utf8 = BStrUtf8::from(s);
465
466        // Test Clone, Copy, PartialEq
467        let bstr2 = bstr_utf8;
468        assert_eq!(bstr_utf8, bstr2);
469
470        // Test Display and Deref
471        assert_eq!(format!("{}", bstr_utf8), "hello 🦀");
472        assert_eq!(bstr_utf8.len(), 10); // UTF-8 bytes
473
474        // Test TryFrom
475        let from_bytes = BStrUtf8::try_from("hello".as_bytes()).unwrap();
476        assert_eq!(from_bytes.as_str(), "hello");
477
478        // Test invalid UTF-8
479        let invalid = BStrUtf8::try_from(&[0xFF, 0xFF][..]);
480        assert!(invalid.is_err());
481    }
482
483    #[test]
484    fn test_advanced_string_traits() {
485        // Test BStr with various PartialEq implementations
486        let bstr = BStr::from("hello");
487        assert_eq!(bstr, "hello");
488        assert_eq!(bstr, "hello");
489        assert_eq!(bstr, &b"hello"[..]);
490
491        // Test Borrow trait
492        let borrowed: &[u8] = std::borrow::Borrow::borrow(&bstr);
493        assert_eq!(borrowed, b"hello");
494
495        // Test BStrUtf8 PartialEq implementations
496        let bstr_utf8 = BStrUtf8::from("hello");
497        assert_eq!(bstr_utf8, "hello");
498        assert_eq!(bstr_utf8, String::from("hello"));
499
500        // Test Borrow trait for BStrUtf8
501        let borrowed: &str = std::borrow::Borrow::borrow(&bstr_utf8);
502        assert_eq!(borrowed, "hello");
503    }
504}