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}