1
//! Miscellaneous utilities.
2

            
3
use std::borrow::Cow;
4
use std::ffi::CStr;
5
use 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.
15
4238927
pub unsafe fn utf8_cstr<'a>(s: *const libc::c_char) -> &'a str {
16
4238927
    assert!(!s.is_null());
17

            
18
4238927
    str::from_utf8_unchecked(CStr::from_ptr(s).to_bytes())
19
4238927
}
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.
26
4267647
pub unsafe fn opt_utf8_cstr<'a>(s: *const libc::c_char) -> Option<&'a str> {
27
4267647
    if s.is_null() {
28
2162378
        None
29
    } else {
30
2105269
        Some(utf8_cstr(s))
31
    }
32
4267647
}
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.
38
89129
pub unsafe fn utf8_cstr_bounds<'a>(
39
    start: *const libc::c_char,
40
    end: *const libc::c_char,
41
) -> &'a str {
42
89129
    let len = end.offset_from(start);
43
89129
    assert!(len >= 0);
44

            
45
89129
    utf8_cstr_len(start, len as usize)
46
89129
}
47

            
48
/// Gets a known-to-be valid UTF-8 string given a pointer to its start and a length.
49
///
50
/// Safety: `start` must be a valid pointer, and `len` bytes starting from it must be
51
/// valid UTF-8.
52
1123324
pub unsafe fn utf8_cstr_len<'a>(start: *const libc::c_char, len: usize) -> &'a str {
53
    // Convert from libc::c_char to u8.  Why transmute?  Because libc::c_char
54
    // is of different signedness depending on the architecture (u8 on aarch64,
55
    // i8 on x86_64).  If one just uses "start as *const u8", it triggers a
56
    // trivial_casts warning.
57
    #[allow(trivial_casts)]
58
1123324
    let start = start as *const u8;
59
1123324
    let value_slice = std::slice::from_raw_parts(start, len);
60

            
61
1123324
    str::from_utf8_unchecked(value_slice)
62
1123324
}
63

            
64
/// Error-tolerant C string import
65
15
pub unsafe fn cstr<'a>(s: *const libc::c_char) -> Cow<'a, str> {
66
15
    if s.is_null() {
67
7
        return Cow::Borrowed("(null)");
68
    }
69
8
    CStr::from_ptr(s).to_string_lossy()
70
15
}
71

            
72
9689651
pub fn clamp<T: PartialOrd>(val: T, low: T, high: T) -> T {
73
9689651
    if val < low {
74
703533
        low
75
8937074
    } else if val > high {
76
319338
        high
77
    } else {
78
8666780
        val
79
    }
80
9689651
}
81

            
82
#[cfg(test)]
83
mod tests {
84
    use super::*;
85

            
86
    #[allow(trivial_casts)]
87
    #[test]
88
2
    fn utf8_cstr_works() {
89
        unsafe {
90
1
            let hello = b"hello\0".as_ptr() as *const libc::c_char;
91

            
92
1
            assert_eq!(utf8_cstr(hello), "hello");
93
        }
94
2
    }
95

            
96
    #[allow(trivial_casts)]
97
    #[test]
98
2
    fn opt_utf8_cstr_works() {
99
        unsafe {
100
1
            let hello = b"hello\0".as_ptr() as *const libc::c_char;
101

            
102
1
            assert_eq!(opt_utf8_cstr(hello), Some("hello"));
103
1
            assert_eq!(opt_utf8_cstr(std::ptr::null()), None);
104
        }
105
2
    }
106

            
107
    #[allow(trivial_casts)]
108
    #[test]
109
2
    fn utf8_cstr_bounds_works() {
110
        unsafe {
111
1
            let hello: *const libc::c_char = b"hello\0" as *const _ as *const _;
112

            
113
1
            assert_eq!(utf8_cstr_bounds(hello, hello.offset(5)), "hello");
114
1
            assert_eq!(utf8_cstr_bounds(hello, hello), "");
115
        }
116
2
    }
117

            
118
    #[allow(trivial_casts)]
119
    #[test]
120
2
    fn utf8_cstr_len_works() {
121
        unsafe {
122
1
            let hello: *const libc::c_char = b"hello\0" as *const _ as *const _;
123

            
124
1
            assert_eq!(utf8_cstr_len(hello, 5), "hello");
125
        }
126
2
    }
127

            
128
    #[allow(trivial_casts)]
129
    #[test]
130
2
    fn cstr_works() {
131
        unsafe {
132
1
            let hello: *const libc::c_char = b"hello\0" as *const _ as *const _;
133
1
            let invalid_utf8: *const libc::c_char = b"hello\xff\0" as *const _ as *const _;
134

            
135
2
            assert_eq!(cstr(hello).as_ref(), "hello");
136
2
            assert_eq!(cstr(std::ptr::null()).as_ref(), "(null)");
137
2
            assert_eq!(cstr(invalid_utf8).as_ref(), "hello\u{fffd}");
138
        }
139
2
    }
140
}