Skip to main content

rsvg/
util.rs

1//! Miscellaneous utilities.
2
3use std::borrow::Cow;
4use std::ffi::CStr;
5use std::str;
6
7/// Converts a `char *` which is known to be valid UTF-8 into a `&str`
8///
9/// The usual `from_glib_none(s)` allocates an owned String.  The
10/// purpose of `utf8_cstr()` is to get a temporary string slice into a
11/// C string which is already known to be valid UTF-8; for example,
12/// as for strings which come from `libxml2`.
13///
14/// Safety: `s` must be a nul-terminated, valid UTF-8 string of bytes.
15pub unsafe fn utf8_cstr<'a>(s: *const libc::c_char) -> &'a str {
16    assert!(!s.is_null());
17
18    unsafe { str::from_utf8_unchecked(CStr::from_ptr(s).to_bytes()) }
19}
20
21/// Converts a `char *` which is known to be valid UTF-8 into an `Option<&str>`
22///
23/// NULL pointers get converted to None.
24///
25/// Safety: `s` must be null, or a nul-terminated, valid UTF-8 string of bytes.
26pub unsafe fn opt_utf8_cstr<'a>(s: *const libc::c_char) -> Option<&'a str> {
27    if s.is_null() {
28        None
29    } else {
30        unsafe { Some(utf8_cstr(s)) }
31    }
32}
33
34/// Gets a known-to-be valid UTF-8 string given start/end-exclusive pointers to its bounds.
35///
36/// Safety: `start` must be a valid pointer, and `end` must be the same for a zero-length string,
37/// or greater than `start`.  All the bytes between them must be valid UTF-8.
38pub unsafe fn utf8_cstr_bounds<'a>(
39    start: *const libc::c_char,
40    end: *const libc::c_char,
41) -> &'a str {
42    unsafe {
43        let len = end.offset_from(start);
44        assert!(len >= 0);
45
46        utf8_cstr_len(start, len as usize)
47    }
48}
49
50/// Gets a known-to-be valid UTF-8 string given a pointer to its start and a length.
51///
52/// Safety: `start` must be a valid pointer, and `len` bytes starting from it must be
53/// valid UTF-8.
54pub unsafe fn utf8_cstr_len<'a>(start: *const libc::c_char, len: usize) -> &'a str {
55    unsafe {
56        // Convert from libc::c_char to u8.  Why cast like this?  Because libc::c_char
57        // is of different signedness depending on the architecture (u8 on aarch64,
58        // i8 on x86_64).  If one just uses "start as *const u8", it triggers a
59        // trivial_casts warning.
60        #[allow(trivial_casts)]
61        let start = start as *const u8;
62        let value_slice = std::slice::from_raw_parts(start, len);
63
64        str::from_utf8_unchecked(value_slice)
65    }
66}
67
68/// Error-tolerant C string import
69pub unsafe fn cstr<'a>(s: *const libc::c_char) -> Cow<'a, str> {
70    if s.is_null() {
71        return Cow::Borrowed("(null)");
72    }
73    unsafe { CStr::from_ptr(s).to_string_lossy() }
74}
75
76pub fn clamp<T: PartialOrd>(val: T, low: T, high: T) -> T {
77    if val < low {
78        low
79    } else if val > high {
80        high
81    } else {
82        val
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[allow(trivial_casts)]
91    #[test]
92    fn utf8_cstr_works() {
93        unsafe {
94            let hello = b"hello\0".as_ptr() as *const libc::c_char;
95
96            assert_eq!(utf8_cstr(hello), "hello");
97        }
98    }
99
100    #[allow(trivial_casts)]
101    #[test]
102    fn opt_utf8_cstr_works() {
103        unsafe {
104            let hello = b"hello\0".as_ptr() as *const libc::c_char;
105
106            assert_eq!(opt_utf8_cstr(hello), Some("hello"));
107            assert_eq!(opt_utf8_cstr(std::ptr::null()), None);
108        }
109    }
110
111    #[allow(trivial_casts)]
112    #[test]
113    fn utf8_cstr_bounds_works() {
114        unsafe {
115            let hello: *const libc::c_char = b"hello\0" as *const _ as *const _;
116
117            assert_eq!(utf8_cstr_bounds(hello, hello.offset(5)), "hello");
118            assert_eq!(utf8_cstr_bounds(hello, hello), "");
119        }
120    }
121
122    #[allow(trivial_casts)]
123    #[test]
124    fn utf8_cstr_len_works() {
125        unsafe {
126            let hello: *const libc::c_char = b"hello\0" as *const _ as *const _;
127
128            assert_eq!(utf8_cstr_len(hello, 5), "hello");
129        }
130    }
131
132    #[allow(trivial_casts)]
133    #[test]
134    fn cstr_works() {
135        unsafe {
136            let hello: *const libc::c_char = b"hello\0" as *const _ as *const _;
137            let invalid_utf8: *const libc::c_char = b"hello\xff\0" as *const _ as *const _;
138
139            assert_eq!(cstr(hello).as_ref(), "hello");
140            assert_eq!(cstr(std::ptr::null()).as_ref(), "(null)");
141            assert_eq!(cstr(invalid_utf8).as_ref(), "hello\u{fffd}");
142        }
143    }
144}