nginx_sys/
string.rs

1use core::cmp;
2use core::fmt;
3use core::hash;
4use core::ptr;
5use core::slice;
6use core::str;
7
8use crate::bindings::{ngx_pool_t, ngx_str_t};
9use crate::detail;
10
11impl ngx_str_t {
12    /// Returns the contents of this `ngx_str_t` as a byte slice.
13    ///
14    /// The returned slice will **not** contain the optional nul terminator that `ngx_str_t.data`
15    /// may have.
16    #[inline]
17    pub fn as_bytes(&self) -> &[u8] {
18        if self.is_empty() {
19            &[]
20        } else {
21            // SAFETY: `ngx_str_t` with non-zero len must contain a valid correctly aligned pointer
22            unsafe { slice::from_raw_parts(self.data, self.len) }
23        }
24    }
25
26    /// Returns the contents of this `ngx_str_t` as a mutable byte slice.
27    #[inline]
28    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
29        if self.is_empty() {
30            &mut []
31        } else {
32            // SAFETY: `ngx_str_t` with non-zero len must contain a valid correctly aligned pointer
33            unsafe { slice::from_raw_parts_mut(self.data, self.len) }
34        }
35    }
36
37    /// Returns `true` if the string has a length of 0.
38    #[inline]
39    pub fn is_empty(&self) -> bool {
40        self.len == 0
41    }
42
43    /// Returns the contents of this `ngx_str_t` a string slice (`&str`) if
44    /// the contents are utf-8 encoded.
45    ///
46    /// # Returns
47    /// A string slice (`&str`) representing the nginx string, or else a
48    /// Utf8Error.
49    pub fn to_str(&self) -> Result<&str, str::Utf8Error> {
50        str::from_utf8(self.as_bytes())
51    }
52
53    /// Creates an empty `ngx_str_t` instance.
54    ///
55    /// This method replaces the `ngx_null_string` C macro.
56    pub const fn empty() -> Self {
57        ngx_str_t {
58            len: 0,
59            data: ptr::null_mut(),
60        }
61    }
62
63    /// Create an `ngx_str_t` instance from a byte slice.
64    ///
65    /// # Safety
66    ///
67    /// The caller must provide a valid pointer to a memory pool.
68    pub unsafe fn from_bytes(pool: *mut ngx_pool_t, src: &[u8]) -> Option<Self> {
69        detail::bytes_to_uchar(pool, src).map(|data| Self {
70            data,
71            len: src.len(),
72        })
73    }
74
75    /// Create an `ngx_str_t` instance from a string slice (`&str`).
76    ///
77    /// # Arguments
78    ///
79    /// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`).
80    /// * `data` - The string slice from which to create the nginx string.
81    ///
82    /// # Safety
83    /// This function is marked as unsafe because it accepts a raw pointer argument. There is no
84    /// way to know if `pool` is pointing to valid memory. The caller must provide a valid pool to
85    /// avoid indeterminate behavior.
86    ///
87    /// # Returns
88    /// An `ngx_str_t` instance representing the given string slice.
89    pub unsafe fn from_str(pool: *mut ngx_pool_t, data: &str) -> Self {
90        ngx_str_t {
91            data: detail::str_to_uchar(pool, data),
92            len: data.len(),
93        }
94    }
95
96    /// Divides one `ngx_str_t` into two at an index.
97    ///
98    /// # Safety
99    ///
100    /// The results will reference the original string; be wary of the ownership and lifetime.
101    pub fn split_at(&self, mid: usize) -> Option<(ngx_str_t, ngx_str_t)> {
102        if mid > self.len {
103            return None;
104        }
105
106        Some((
107            ngx_str_t {
108                data: self.data,
109                len: mid,
110            },
111            ngx_str_t {
112                data: unsafe { self.data.add(mid) },
113                len: self.len - mid,
114            },
115        ))
116    }
117
118    /// Returns an `ngx_str_t` with the prefix removed.
119    ///
120    /// If the string starts with the byte sequence `prefix`, returns the substring after the
121    /// prefix, wrapped in `Some`. The resulting substring can be empty.
122    ///
123    /// # Safety
124    ///
125    /// The result will reference the original string; be wary of the ownership and lifetime.
126    ///
127    /// The method is not marked as `unsafe` as everything it does is possible via safe interfaces.
128    pub fn strip_prefix(&self, prefix: impl AsRef<[u8]>) -> Option<ngx_str_t> {
129        let prefix = prefix.as_ref();
130        if self.as_bytes().starts_with(prefix) {
131            self.split_at(prefix.len()).map(|x| x.1)
132        } else {
133            None
134        }
135    }
136
137    /// Returns an `ngx_str_t` with the suffix removed.
138    ///
139    /// If the string ends with the byte sequence `suffix`, returns the substring before the
140    /// suffix, wrapped in `Some`. The resulting substring can be empty.
141    ///
142    /// # Safety
143    ///
144    /// The result will reference the original string; be wary of the ownership and lifetime.
145    ///
146    /// The method is not marked as `unsafe` as everything it does is possible via safe interfaces.
147    pub fn strip_suffix(&self, suffix: impl AsRef<[u8]>) -> Option<ngx_str_t> {
148        let suffix = suffix.as_ref();
149        if self.as_bytes().ends_with(suffix) {
150            self.split_at(self.len - suffix.len()).map(|x| x.0)
151        } else {
152            None
153        }
154    }
155}
156
157impl AsRef<[u8]> for ngx_str_t {
158    fn as_ref(&self) -> &[u8] {
159        self.as_bytes()
160    }
161}
162
163impl AsMut<[u8]> for ngx_str_t {
164    fn as_mut(&mut self) -> &mut [u8] {
165        self.as_bytes_mut()
166    }
167}
168
169impl Default for ngx_str_t {
170    fn default() -> Self {
171        Self::empty()
172    }
173}
174
175impl fmt::Display for ngx_str_t {
176    #[inline]
177    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178        detail::display_bytes(f, self.as_bytes())
179    }
180}
181
182impl From<ngx_str_t> for &[u8] {
183    fn from(s: ngx_str_t) -> Self {
184        if s.len == 0 || s.data.is_null() {
185            return Default::default();
186        }
187        unsafe { slice::from_raw_parts(s.data, s.len) }
188    }
189}
190
191impl hash::Hash for ngx_str_t {
192    fn hash<H: hash::Hasher>(&self, state: &mut H) {
193        self.as_bytes().hash(state)
194    }
195}
196
197impl PartialEq for ngx_str_t {
198    fn eq(&self, other: &Self) -> bool {
199        PartialEq::eq(self.as_bytes(), other.as_bytes())
200    }
201}
202
203impl Eq for ngx_str_t {}
204
205impl PartialOrd<Self> for ngx_str_t {
206    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
207        Some(self.cmp(other))
208    }
209}
210
211impl Ord for ngx_str_t {
212    fn cmp(&self, other: &Self) -> cmp::Ordering {
213        Ord::cmp(self.as_bytes(), other.as_bytes())
214    }
215}
216
217impl TryFrom<ngx_str_t> for &str {
218    type Error = str::Utf8Error;
219
220    fn try_from(s: ngx_str_t) -> Result<Self, Self::Error> {
221        str::from_utf8(s.into())
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn ngx_str_prefix() {
231        let s = "key=value";
232        let s = ngx_str_t {
233            data: s.as_ptr().cast_mut(),
234            len: s.len(),
235        };
236
237        assert_eq!(
238            s.strip_prefix("key=").as_ref().map(ngx_str_t::as_bytes),
239            Some("value".as_bytes())
240        );
241
242        assert_eq!(s.strip_prefix("test"), None);
243
244        assert_eq!(
245            s.strip_suffix("value").as_ref().map(ngx_str_t::as_bytes),
246            Some("key=".as_bytes())
247        );
248
249        assert_eq!(s.strip_suffix("test"), None);
250    }
251}