ffizz_string/
utilfns.rs

1use crate::{fz_string_t, FzString};
2use std::ffi::{CStr, CString};
3
4// These functions are used in downstream creates via the `reexport!` macro, which generates a
5// function in that crate, wrapping one of these functions.  As a result, none of these functions
6// are `extern "C"`, and all are tagged with `inline(always)` so that they are inlined into the
7// downstream crate.
8//
9// NOTE: if you add a function to this module, also add it to `reexport!` in string/src/macros.rs.
10
11// This type is used in the `reexport!` macro.
12#[doc(hidden)]
13pub type c_char = libc::c_char;
14
15/// Create a new fz_string_t containing a pointer to the given C string.
16///
17/// # Safety
18///
19/// The C string must remain valid and unchanged until after the `fz_string_t` is freed.  It's
20/// typically easiest to ensure this by using a static string.
21///
22/// The resulting `fz_string_t` must be freed.
23///
24/// ```c
25/// fz_string_t fz_string_borrow(const char *);
26/// ```
27#[inline(always)]
28pub unsafe fn fz_string_borrow(cstr: *const c_char) -> fz_string_t {
29    debug_assert!(!cstr.is_null());
30    // SAFETY:
31    //  - cstr is not NULL (promised by caller, verified by assertion)
32    //  - cstr's lifetime exceeds that of the fz_string_t (promised by caller)
33    //  - cstr contains a valid NUL terminator (promised by caller)
34    //  - cstr's content will not change before it is destroyed (promised by caller)
35    let cstr: &CStr = unsafe { CStr::from_ptr(cstr) };
36    // SAFETY:
37    //  - caller promises to free this string
38    unsafe { FzString::return_val(FzString::CStr(cstr)) }
39}
40
41#[allow(clippy::missing_safety_doc)] // not actually terribly unsafe
42/// Create a new, null `fz_string_t`.  Note that this is _not_ the zero value of `fz_string_t`.
43///
44/// # Safety
45///
46/// The resulting `fz_string_t` must be freed.
47///
48/// ```c
49/// fz_string_t fz_string_null();
50/// ```
51#[inline(always)]
52pub unsafe fn fz_string_null() -> fz_string_t {
53    // SAFETY:
54    //  - caller promises to free this string
55    unsafe { FzString::return_val(FzString::Null) }
56}
57
58/// Create a new `fz_string_t` by cloning the content of the given C string.  The resulting `fz_string_t`
59/// is independent of the given string.
60///
61/// # Safety
62///
63/// The given pointer must not be NULL.
64/// The resulting `fz_string_t` must be freed.
65///
66/// ```c
67/// fz_string_t fz_string_clone(const char *);
68/// ```
69#[inline(always)]
70pub unsafe fn fz_string_clone(cstr: *const c_char) -> fz_string_t {
71    debug_assert!(!cstr.is_null());
72    // SAFETY:
73    //  - cstr is not NULL (promised by caller, verified by assertion)
74    //  - cstr's lifetime exceeds that of this function (by C convention)
75    //  - cstr contains a valid NUL terminator (promised by caller)
76    //  - cstr's content will not change before it is destroyed (by C convention)
77    let cstr: &CStr = unsafe { CStr::from_ptr(cstr) };
78    let cstring: CString = cstr.into();
79    // SAFETY:
80    //  - caller promises to free this string
81    unsafe { FzString::return_val(FzString::CString(cstring)) }
82}
83
84/// Create a new `fz_string_t` containing the given string with the given length. This allows creation
85/// of strings containing embedded NUL characters.  As with `fz_string_clone`, the resulting
86/// `fz_string_t` is independent of the passed buffer.
87///
88/// The given length should _not_ include any NUL terminator.  The given length must be less than
89/// half the maximum value of usize.
90///
91/// # Safety
92///
93/// The given pointer must not be NULL.
94/// The resulting `fz_string_t` must be freed.
95///
96/// ```c
97/// fz_string_t fz_string_clone_with_len(const char *ptr, usize len);
98/// ```
99#[inline(always)]
100pub unsafe fn fz_string_clone_with_len(buf: *const c_char, len: usize) -> fz_string_t {
101    debug_assert!(!buf.is_null());
102    debug_assert!(len < isize::MAX as usize);
103    // SAFETY:
104    //  - buf is valid for len bytes (by C convention)
105    //  - (no alignment requirements for a byte slice)
106    //  - content of buf will not be mutated during the lifetime of this slice (lifetime
107    //    does not outlive this function call)
108    //  - the length of the buffer is less than isize::MAX (promised by caller)
109    let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) };
110
111    // allocate and copy into Rust-controlled memory
112    let vec = slice.to_vec();
113
114    // SAFETY:
115    //  - caller promises to free this string
116    unsafe { FzString::return_val(FzString::Bytes(vec)) }
117}
118
119/// Get the content of the string as a regular C string.
120///
121/// A string contianing NUL bytes will result in a NULL return value.  In general, prefer
122/// `fz_string_content_with_len` except when it's certain that the string is NUL-free.
123///
124/// The Null variant also results in a NULL return value.
125///
126/// This function takes the `fz_string_t` by pointer because it may be modified in-place to add a NUL
127/// terminator.  The pointer must not be NULL.
128///
129/// # Safety
130///
131/// The returned string is "borrowed" and remains valid only until the `fz_string_t` is freed or
132/// passed to any other API function.
133///
134/// ```c
135/// const char *fz_string_content(fz_string_t *);
136/// ```
137#[inline(always)]
138pub unsafe fn fz_string_content(fzstr: *mut fz_string_t) -> *const c_char {
139    // SAFETY;
140    //  - fzstr is not NULL (promised by caller, verified)
141    //  - *fzstr is valid (promised by caller)
142    //  - *fzstr is not accessed concurrently (single-threaded)
143    unsafe {
144        FzString::with_ref_mut(fzstr, |fzstr| match fzstr.as_cstr() {
145            // SAFETY:
146            //  - implied lifetime here is FzString's lifetime; valid until another mutable
147            //    reference is made (see docstring)
148            Ok(Some(cstr)) => cstr.as_ptr(),
149            _ => std::ptr::null(),
150        })
151    }
152}
153
154/// Get the content of the string as a pointer and length.
155///
156/// This function can return any string, even one including NUL bytes or invalid UTF-8.
157/// If the FzString is the Null variant, this returns NULL and the length is set to zero.
158///
159/// # Safety
160///
161/// The returned string is "borrowed" and remains valid only until the `fz_string_t` is freed or
162/// passed to any other API function.
163///
164/// ```c
165/// const char *fz_string_content_with_len(const struct fz_string_t *, len_out *usize);
166/// ```
167#[inline(always)]
168pub unsafe fn fz_string_content_with_len(
169    fzstr: *mut fz_string_t,
170    len_out: *mut usize,
171) -> *const c_char {
172    // SAFETY;
173    //  - fzstr is not NULL (promised by caller)
174    //  - *fzstr is valid (promised by caller)
175    //  - *fzstr is not accessed concurrently (single-threaded)
176    unsafe {
177        FzString::with_ref(fzstr, |fzstr| {
178            let bytes = match fzstr.as_bytes() {
179                Some(bytes) => bytes,
180                None => {
181                    // SAFETY:
182                    //  - len_out is not NULL (promised by caller)
183                    //  - len_out points to valid memory (promised by caller)
184                    //  - len_out is properly aligned (C convention)
185                    unsafe {
186                        *len_out = 0;
187                    }
188                    return std::ptr::null();
189                }
190            };
191
192            // SAFETY:
193            //  - len_out is not NULL (promised by caller)
194            //  - len_out points to valid memory (promised by caller)
195            //  - len_out is properly aligned (C convention)
196            unsafe {
197                *len_out = bytes.len();
198            }
199            bytes.as_ptr() as *const c_char
200        })
201    }
202}
203
204#[allow(clippy::missing_safety_doc)] // NULL pointer is OK so not actually unsafe
205/// Determine whether the given `fz_string_t` is a Null variant.
206///
207/// ```c
208/// bool fz_string_is_null(fz_string_t *);
209/// ```
210#[inline(always)]
211pub unsafe fn fz_string_is_null(fzstr: *const fz_string_t) -> bool {
212    unsafe { FzString::with_ref(fzstr, |fzstr| fzstr.is_null()) }
213}
214
215/// Free a `fz_string_t`.
216///
217/// # Safety
218///
219/// The string must not be used after this function returns, and must not be freed more than once.
220/// It is safe to free Null-variant strings.
221///
222/// ```c
223/// fz_string_free(fz_string_t *);
224/// ```
225#[inline(always)]
226pub unsafe fn fz_string_free(fzstr: *mut fz_string_t) {
227    // SAFETY:
228    //  - fzstr is not NULL (promised by caller)
229    //  - caller will not use this value after return
230    drop(unsafe { FzString::take_ptr(fzstr) });
231}
232
233#[cfg(test)]
234mod test {
235    use super::*;
236
237    const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28";
238
239    #[test]
240    fn borrow() {
241        let s = CString::new("hello!").unwrap();
242        let ptr = unsafe { s.as_ptr() };
243
244        let mut fzstr = unsafe { fz_string_borrow(ptr) };
245        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
246
247        let content = unsafe { CStr::from_ptr(fz_string_content(&mut fzstr as *mut fz_string_t)) };
248        assert_eq!(content.to_str().unwrap(), "hello!");
249
250        drop(s); // make sure s lasts long enough!
251
252        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
253    }
254
255    #[test]
256    fn borrow_invalid_utf8() {
257        let s = CString::new(INVALID_UTF8).unwrap();
258        let ptr = unsafe { s.as_ptr() };
259
260        let mut fzstr = unsafe { fz_string_borrow(ptr) };
261        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
262
263        let mut len: usize = 0;
264        let ptr = unsafe {
265            fz_string_content_with_len(&mut fzstr as *mut fz_string_t, &mut len as *mut usize)
266        };
267        let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };
268        assert_eq!(slice, INVALID_UTF8);
269
270        drop(s); // make sure s lasts long enough!
271
272        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
273    }
274
275    #[test]
276    fn clone() {
277        let s = CString::new("hello!").unwrap();
278        let ptr = unsafe { s.as_ptr() };
279
280        let mut fzstr = unsafe { fz_string_clone(ptr) };
281        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
282
283        drop(s); // fzstr contains a clone of s, so deallocate
284
285        let content = unsafe { CStr::from_ptr(fz_string_content(&mut fzstr as *mut fz_string_t)) };
286        assert_eq!(content.to_str().unwrap(), "hello!");
287
288        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
289    }
290
291    #[test]
292    fn null_and_is_null() {
293        let mut fzstr = unsafe { fz_string_null() };
294        assert!(unsafe { fz_string_is_null(&fzstr as *const fz_string_t) });
295
296        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
297    }
298
299    #[test]
300    fn null_ptr_is_null() {
301        let mut fzstr = unsafe { fz_string_null() };
302        assert!(unsafe { fz_string_is_null(std::ptr::null()) });
303
304        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
305    }
306
307    #[test]
308    fn clone_invalid_utf8() {
309        let s = CString::new(INVALID_UTF8).unwrap();
310        let ptr = unsafe { s.as_ptr() };
311
312        let mut fzstr = unsafe { fz_string_clone(ptr) };
313        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
314
315        drop(s); // fzstr contains a clone of s, so deallocate
316
317        let mut len: usize = 0;
318        let ptr = unsafe {
319            fz_string_content_with_len(&mut fzstr as *mut fz_string_t, &mut len as *mut usize)
320        };
321        let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };
322        assert_eq!(slice, INVALID_UTF8);
323
324        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
325    }
326
327    #[test]
328    fn clone_with_len() {
329        let s = CString::new("ABCDEFGH").unwrap();
330        let ptr = unsafe { s.as_ptr() };
331
332        let mut fzstr = unsafe { fz_string_clone_with_len(ptr, 4) };
333        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
334
335        drop(s); // fzstr contains a clone of s, so deallocate
336
337        let content = unsafe { CStr::from_ptr(fz_string_content(&mut fzstr as *mut fz_string_t)) };
338        assert_eq!(content.to_str().unwrap(), "ABCD"); // only 4 bytes
339
340        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
341    }
342
343    #[test]
344    fn clone_with_len_invalid_utf8() {
345        let s = CString::new(INVALID_UTF8).unwrap();
346        let ptr = unsafe { s.as_ptr() };
347
348        let mut fzstr = unsafe { fz_string_clone_with_len(ptr, 4) };
349        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
350
351        drop(s); // fzstr contains a clone of s, so deallocate
352
353        let mut len: usize = 0;
354        let ptr = unsafe {
355            fz_string_content_with_len(&mut fzstr as *mut fz_string_t, &mut len as *mut usize)
356        };
357        let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };
358        assert_eq!(slice, &INVALID_UTF8[..4]); // only 4 bytes
359
360        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
361    }
362
363    // (fz_string_content's normal operation is tested above)
364
365    #[test]
366    fn content_nul_bytes() {
367        let s = String::from("hello \0 NUL byte");
368        let ptr = unsafe { s.as_ptr() } as *mut c_char;
369
370        let mut fzstr = unsafe { fz_string_clone_with_len(ptr, s.len()) };
371        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
372
373        let ptr = unsafe { fz_string_content(&mut fzstr as *mut fz_string_t) };
374
375        // could not return a string because of the embedded NUL byte
376        assert!(ptr.is_null());
377
378        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
379    }
380
381    #[test]
382    fn content_null_ptr() {
383        let ptr = unsafe { fz_string_content(std::ptr::null_mut()) };
384        assert!(ptr.is_null());
385    }
386
387    #[test]
388    fn content_with_len_nul_bytes() {
389        let s = String::from("hello \0 NUL byte");
390        let ptr = unsafe { s.as_ptr() } as *mut c_char;
391
392        let mut fzstr = unsafe { fz_string_clone_with_len(ptr, s.len()) };
393        assert!(unsafe { !fz_string_is_null(&fzstr as *const fz_string_t) });
394
395        let mut len: usize = 0;
396        let ptr = unsafe {
397            fz_string_content_with_len(&mut fzstr as *mut fz_string_t, &mut len as *mut usize)
398        };
399
400        let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };
401        let s = std::str::from_utf8(slice).unwrap();
402        assert_eq!(s, "hello \0 NUL byte");
403
404        unsafe { fz_string_free(&mut fzstr as *mut fz_string_t) };
405    }
406
407    #[test]
408    fn content_with_len_null_ptr() {
409        let mut len: usize = 9999;
410        let ptr =
411            unsafe { fz_string_content_with_len(std::ptr::null_mut(), &mut len as *mut usize) };
412        assert!(ptr.is_null());
413        assert_eq!(len, 0);
414    }
415
416    // (fz_string_free is tested above)
417}