ngx/core/
string.rs

1#[cfg(all(not(feature = "std"), feature = "alloc"))]
2use alloc::{borrow::Cow, string::String};
3use core::cmp;
4use core::fmt;
5use core::str::{self, Utf8Error};
6#[cfg(feature = "std")]
7use std::{borrow::Cow, string::String};
8
9use crate::ffi::{ngx_str_t, u_char};
10
11/// Static string initializer for [`ngx_str_t`].
12///
13/// The resulting byte string is always nul-terminated (just like a C string).
14///
15/// [`ngx_str_t`]: https://nginx.org/en/docs/dev/development_guide.html#string_overview
16#[macro_export]
17macro_rules! ngx_string {
18    ($s:expr) => {{
19        $crate::ffi::ngx_str_t {
20            len: $s.len() as _,
21            data: concat!($s, "\0").as_ptr() as *mut u8,
22        }
23    }};
24}
25
26#[cfg(feature = "alloc")]
27pub use self::_alloc::NgxString;
28
29/// Representation of a borrowed [Nginx string].
30///
31/// [Nginx string]: https://nginx.org/en/docs/dev/development_guide.html#string_overview
32#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
33#[repr(transparent)]
34pub struct NgxStr([u_char]);
35
36impl NgxStr {
37    /// Create an [`NgxStr`] from an [`ngx_str_t`].
38    ///
39    /// [`ngx_str_t`]: https://nginx.org/en/docs/dev/development_guide.html#string_overview
40    ///
41    /// # Safety
42    ///
43    /// The caller has provided a valid `ngx_str_t` with a `data` pointer that points
44    /// to range of bytes of at least `len` bytes, whose content remains valid and doesn't
45    /// change for the lifetime of the returned `NgxStr`.
46    pub unsafe fn from_ngx_str<'a>(str: ngx_str_t) -> &'a NgxStr {
47        let bytes: &[u8] = str.as_bytes();
48        &*(bytes as *const [u8] as *const NgxStr)
49    }
50
51    /// Create an [NgxStr] from a borrowed byte slice.
52    #[inline]
53    pub fn from_bytes(bytes: &[u8]) -> &Self {
54        // SAFETY: An `NgxStr` is identical to a `[u8]` slice, given `u_char` is an alias for `u8`
55        unsafe { &*(bytes as *const [u8] as *const NgxStr) }
56    }
57
58    /// Create a mutable [NgxStr] from a borrowed byte slice.
59    #[inline]
60    pub fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self {
61        // SAFETY: An `NgxStr` is identical to a `[u8]` slice, given `u_char` is an alias for `u8`
62        unsafe { &mut *(bytes as *mut [u8] as *mut NgxStr) }
63    }
64
65    /// Access the [`NgxStr`] as a byte slice.
66    pub fn as_bytes(&self) -> &[u8] {
67        &self.0
68    }
69
70    /// Yields a `&str` slice if the [`NgxStr`] contains valid UTF-8.
71    pub fn to_str(&self) -> Result<&str, Utf8Error> {
72        str::from_utf8(self.as_bytes())
73    }
74
75    /// Converts an [`NgxStr`] into a [`Cow<str>`], replacing invalid UTF-8 sequences.
76    ///
77    /// See [`String::from_utf8_lossy`].
78    #[cfg(feature = "alloc")]
79    pub fn to_string_lossy(&self) -> Cow<'_, str> {
80        String::from_utf8_lossy(self.as_bytes())
81    }
82
83    /// Returns `true` if the [`NgxStr`] is empty, otherwise `false`.
84    pub fn is_empty(&self) -> bool {
85        self.0.is_empty()
86    }
87}
88
89impl AsRef<[u8]> for NgxStr {
90    #[inline]
91    fn as_ref(&self) -> &[u8] {
92        self.as_bytes()
93    }
94}
95
96impl AsMut<[u8]> for NgxStr {
97    #[inline]
98    fn as_mut(&mut self) -> &mut [u8] {
99        &mut self.0
100    }
101}
102
103impl fmt::Debug for NgxStr {
104    #[inline]
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        // XXX: Use debug_tuple() and feature(debug_closure_helpers) once it's stabilized
107        f.write_str("NgxStr(")?;
108        nginx_sys::detail::debug_bytes(f, &self.0)?;
109        f.write_str(")")
110    }
111}
112
113impl Default for &NgxStr {
114    fn default() -> Self {
115        NgxStr::from_bytes(&[])
116    }
117}
118
119impl fmt::Display for NgxStr {
120    #[inline]
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        nginx_sys::detail::display_bytes(f, &self.0)
123    }
124}
125
126macro_rules! impl_partial_ord_eq_from {
127    ($self:ty, $other:ty) => { impl_partial_ord_eq_from!($self, $other;); };
128
129    ($self:ty, $other:ty; $($args:tt)*) => {
130        impl<'a, $($args)*> From<$other> for &'a NgxStr {
131            #[inline]
132            fn from(other: $other) -> Self {
133                let other: &[u8] = other.as_ref();
134                NgxStr::from_bytes(other)
135            }
136        }
137
138        impl_partial_eq!($self, $other; $($args)*);
139        impl_partial_ord!($self, $other; $($args)*);
140    };
141}
142
143macro_rules! impl_partial_eq {
144    ($self:ty, $other:ty) => { impl_partial_eq!($self, $other;); };
145
146    ($self:ty, $other:ty; $($args:tt)*) => {
147        impl<'a, $($args)*> PartialEq<$other> for $self {
148            #[inline]
149            fn eq(&self, other: &$other) -> bool {
150                let other: &[u8] = other.as_ref();
151                PartialEq::eq(self.as_bytes(), other)
152            }
153        }
154
155        impl<'a, $($args)*> PartialEq<$self> for $other {
156            #[inline]
157            fn eq(&self, other: &$self) -> bool {
158                let this: &[u8] = self.as_ref();
159                PartialEq::eq(this, other.as_bytes())
160            }
161        }
162    };
163}
164
165macro_rules! impl_partial_ord {
166    ($self:ty, $other:ty) => { impl_partial_ord!($self, $other;); };
167
168    ($self:ty, $other:ty; $($args:tt)*) => {
169       impl<'a, $($args)*> PartialOrd<$other> for $self {
170            #[inline]
171            fn partial_cmp(&self, other: &$other) -> Option<cmp::Ordering> {
172                let other: &[u8] = other.as_ref();
173                PartialOrd::partial_cmp(self.as_bytes(), other)
174            }
175        }
176
177        impl<'a, $($args)*> PartialOrd<$self> for $other {
178            #[inline]
179            fn partial_cmp(&self, other: &$self) -> Option<cmp::Ordering> {
180                let this: &[u8] = self.as_ref();
181                PartialOrd::partial_cmp(this, other.as_bytes())
182            }
183        }
184    };
185}
186
187impl_partial_eq!(NgxStr, [u8]);
188impl_partial_eq!(NgxStr, [u8; N]; const N: usize);
189impl_partial_eq!(NgxStr, str);
190impl_partial_eq!(NgxStr, ngx_str_t);
191impl_partial_eq!(&'a NgxStr, ngx_str_t);
192impl_partial_ord!(NgxStr, [u8]);
193impl_partial_ord!(NgxStr, [u8; N]; const N: usize);
194impl_partial_ord!(NgxStr, str);
195impl_partial_ord!(NgxStr, ngx_str_t);
196impl_partial_ord!(&'a NgxStr, ngx_str_t);
197impl_partial_ord_eq_from!(NgxStr, &'a [u8]);
198impl_partial_ord_eq_from!(NgxStr, &'a [u8; N]; const N: usize);
199impl_partial_ord_eq_from!(NgxStr, &'a str);
200
201#[cfg(feature = "alloc")]
202mod _alloc {
203    use core::borrow::Borrow;
204    use core::hash;
205    use core::ops;
206    use core::ptr;
207
208    use super::*;
209
210    use crate::allocator::{self, Allocator};
211    use crate::collections::{TryReserveError, Vec};
212
213    /// Owned byte string type with Allocator support.
214    ///
215    /// Inspired by [bstr] and unstable [feature(bstr)], with two important differences:
216    ///  - Allocator always have to be specified,
217    ///  - any allocating methods are failible and require explicit handling of the result.
218    ///
219    ///  [bstr]: https://docs.rs/bstr/latest/bstr/
220    ///  [feature(bstr)]: https://github.com/rust-lang/rust/issues/134915
221    #[derive(Clone)]
222    #[repr(transparent)]
223    pub struct NgxString<A>(Vec<u8, A>)
224    where
225        A: Allocator + Clone;
226
227    impl<A> NgxString<A>
228    where
229        A: Allocator + Clone,
230    {
231        /// Constructs a new, empty `NgxString<A>`.
232        ///
233        /// No allocations will be made until data is added to the string.
234        pub fn new_in(alloc: A) -> Self {
235            Self(Vec::new_in(alloc))
236        }
237
238        /// Tries to construct a new `NgxString<A>` from a byte slice.
239        #[inline]
240        pub fn try_from_bytes_in(
241            bytes: impl AsRef<[u8]>,
242            alloc: A,
243        ) -> Result<Self, TryReserveError> {
244            let mut this = Self::new_in(alloc);
245            this.try_reserve_exact(bytes.as_ref().len())?;
246            this.0.extend_from_slice(bytes.as_ref());
247            Ok(this)
248        }
249
250        /// Returns a reference to the underlying allocator
251        #[inline]
252        pub fn allocator(&self) -> &A {
253            self.0.allocator()
254        }
255
256        /// Returns this `NgxString`'s capacity, in bytes.
257        #[inline]
258        pub fn capacity(&self) -> usize {
259            self.0.capacity()
260        }
261
262        /// Returns `true` if this `NgxString` has a length of zero, and `false` otherwise.
263        #[inline]
264        pub fn is_empty(&self) -> bool {
265            self.0.is_empty()
266        }
267
268        /// Return this `NgxString`'s length, in bytes.
269        #[inline]
270        pub fn len(&self) -> usize {
271            self.0.len()
272        }
273
274        /// Appends bytes if there is sufficient spare capacity.
275        ///
276        /// Returns the number of remaining bytes on overflow.
277        #[inline]
278        pub fn append_within_capacity(&mut self, other: impl AsRef<[u8]>) -> Result<(), usize> {
279            let other = other.as_ref();
280            if self.0.len() == self.0.capacity() {
281                return Err(other.len());
282            }
283
284            let n = cmp::min(self.0.capacity() - self.0.len(), other.len());
285            unsafe {
286                // SAFETY:
287                //  - self.0 has at least n writable bytes allocated past self.0.len(),
288                //  - other has at least n bytes available for reading.
289                //  - self.0 internal buffer will be initialized until len + n after this operation
290                //  - other is not borrowed from `self`
291                let p = self.0.as_mut_ptr().add(self.0.len());
292                ptr::copy_nonoverlapping(other.as_ptr(), p, n);
293                self.0.set_len(self.0.len() + n);
294            }
295
296            match other.len() - n {
297                0 => Ok(()),
298                x => Err(x),
299            }
300        }
301
302        /// Tries to append the bytes to the `NgxString`.
303        #[inline]
304        pub fn try_append(&mut self, other: impl AsRef<[u8]>) -> Result<(), TryReserveError> {
305            let other = other.as_ref();
306            self.0.try_reserve_exact(other.len())?;
307            self.0.extend_from_slice(other);
308            Ok(())
309        }
310
311        /// Tries to reserve capacity for at least `additional` more bytes.
312        #[inline]
313        pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
314            self.0.try_reserve(additional)
315        }
316
317        /// Tries to reserve the minimum capacity for at least `additional` more bytes.
318        #[inline]
319        pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
320            self.0.try_reserve_exact(additional)
321        }
322
323        #[inline]
324        pub(crate) fn as_bytes(&self) -> &[u8] {
325            &self.0
326        }
327
328        #[inline]
329        pub(crate) fn as_bytes_mut(&mut self) -> &mut [u8] {
330            &mut self.0
331        }
332
333        #[inline]
334        pub(crate) fn as_ngx_str(&self) -> &NgxStr {
335            NgxStr::from_bytes(self.0.as_slice())
336        }
337
338        #[inline]
339        pub(crate) fn as_ngx_str_mut(&mut self) -> &mut NgxStr {
340            NgxStr::from_bytes_mut(self.0.as_mut_slice())
341        }
342
343        /// Creates NgxString directly from a pointer, a capacity, a length and an allocator.
344        ///
345        /// # Safety
346        ///
347        /// See [Vec::from_raw_parts_in]
348        #[inline]
349        pub unsafe fn from_raw_parts(
350            ptr: *mut u8,
351            length: usize,
352            capacity: usize,
353            alloc: A,
354        ) -> Self {
355            Self(Vec::from_raw_parts_in(ptr, length, capacity, alloc))
356        }
357
358        /// Splits the NgxString into its raw components.
359        ///
360        /// The caller becomes responsible for the memory previously managed by this NgxString.
361        #[inline]
362        pub fn into_raw_parts(self) -> (*mut u8, usize, usize, A) {
363            self.0.into_raw_parts_with_alloc()
364        }
365    }
366
367    impl<A> AsRef<NgxStr> for NgxString<A>
368    where
369        A: Allocator + Clone,
370    {
371        fn as_ref(&self) -> &NgxStr {
372            self.as_ngx_str()
373        }
374    }
375
376    impl<A> AsMut<NgxStr> for NgxString<A>
377    where
378        A: Allocator + Clone,
379    {
380        fn as_mut(&mut self) -> &mut NgxStr {
381            self.as_ngx_str_mut()
382        }
383    }
384
385    impl<A> AsRef<[u8]> for NgxString<A>
386    where
387        A: Allocator + Clone,
388    {
389        #[inline]
390        fn as_ref(&self) -> &[u8] {
391            self.as_bytes()
392        }
393    }
394
395    impl<A> AsMut<[u8]> for NgxString<A>
396    where
397        A: Allocator + Clone,
398    {
399        #[inline]
400        fn as_mut(&mut self) -> &mut [u8] {
401            self.as_bytes_mut()
402        }
403    }
404
405    impl<A> Borrow<NgxStr> for NgxString<A>
406    where
407        A: Allocator + Clone,
408    {
409        fn borrow(&self) -> &NgxStr {
410            self.as_ngx_str()
411        }
412    }
413
414    impl<A> Borrow<[u8]> for NgxString<A>
415    where
416        A: Allocator + Clone,
417    {
418        fn borrow(&self) -> &[u8] {
419            self.0.as_slice()
420        }
421    }
422
423    impl<A> fmt::Debug for NgxString<A>
424    where
425        A: Allocator + Clone,
426    {
427        #[inline]
428        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429            // XXX: Use debug_tuple() and feature(debug_closure_helpers) once it's stabilized
430            f.write_str("NgxString(")?;
431            nginx_sys::detail::debug_bytes(f, &self.0)?;
432            f.write_str(")")
433        }
434    }
435
436    impl<A> ops::Deref for NgxString<A>
437    where
438        A: Allocator + Clone,
439    {
440        type Target = NgxStr;
441
442        fn deref(&self) -> &Self::Target {
443            self.as_ngx_str()
444        }
445    }
446
447    impl<A> ops::DerefMut for NgxString<A>
448    where
449        A: Allocator + Clone,
450    {
451        fn deref_mut(&mut self) -> &mut Self::Target {
452            self.as_ngx_str_mut()
453        }
454    }
455
456    impl<A> fmt::Display for NgxString<A>
457    where
458        A: Allocator + Clone,
459    {
460        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461            fmt::Display::fmt(self.as_ngx_str(), f)
462        }
463    }
464
465    impl<A> hash::Hash for NgxString<A>
466    where
467        A: Allocator + Clone,
468    {
469        fn hash<H: hash::Hasher>(&self, state: &mut H) {
470            self.0.hash(state);
471        }
472    }
473
474    // `NgxString`'s with different allocators should be comparable
475
476    impl<A1, A2> PartialEq<NgxString<A2>> for NgxString<A1>
477    where
478        A1: Allocator + Clone,
479        A2: Allocator + Clone,
480    {
481        fn eq(&self, other: &NgxString<A2>) -> bool {
482            PartialEq::eq(self.as_bytes(), other.as_bytes())
483        }
484    }
485
486    impl<A> Eq for NgxString<A> where A: Allocator + Clone {}
487
488    impl<A1, A2> PartialOrd<NgxString<A2>> for NgxString<A1>
489    where
490        A1: Allocator + Clone,
491        A2: Allocator + Clone,
492    {
493        fn partial_cmp(&self, other: &NgxString<A2>) -> Option<cmp::Ordering> {
494            Some(Ord::cmp(self.as_bytes(), other.as_bytes()))
495        }
496    }
497
498    impl<A> Ord for NgxString<A>
499    where
500        A: Allocator + Clone,
501    {
502        fn cmp(&self, other: &Self) -> cmp::Ordering {
503            Ord::cmp(self.as_bytes(), other.as_bytes())
504        }
505    }
506
507    impl<OA: Allocator + Clone> allocator::TryCloneIn for NgxString<OA> {
508        type Target<A: Allocator + Clone> = NgxString<A>;
509
510        fn try_clone_in<A: Allocator + Clone>(
511            &self,
512            alloc: A,
513        ) -> Result<Self::Target<A>, allocator::AllocError> {
514            NgxString::try_from_bytes_in(self.as_bytes(), alloc).map_err(|_| allocator::AllocError)
515        }
516    }
517
518    impl<A> fmt::Write for NgxString<A>
519    where
520        A: Allocator + Clone,
521    {
522        fn write_str(&mut self, s: &str) -> fmt::Result {
523            self.append_within_capacity(s).map_err(|_| fmt::Error)
524        }
525    }
526
527    // Implement byte comparisons directly, leave the rest to Deref<Target = NgxStr>.
528
529    impl_partial_eq!(NgxString<A>, &'a [u8]; A: Allocator + Clone);
530    impl_partial_eq!(NgxString<A>, &'a [u8; N]; A: Allocator + Clone, const N: usize);
531    impl_partial_eq!(NgxString<A>, &'a NgxStr; A: Allocator + Clone);
532    impl_partial_eq!(NgxString<A>, ngx_str_t; A: Allocator + Clone);
533
534    impl_partial_ord!(NgxString<A>, &'a [u8]; A: Allocator + Clone);
535    impl_partial_ord!(NgxString<A>, &'a [u8; N]; A: Allocator + Clone, const N: usize);
536    impl_partial_ord!(NgxString<A>, &'a NgxStr; A: Allocator + Clone);
537    impl_partial_ord!(NgxString<A>, ngx_str_t; A: Allocator + Clone);
538
539    impl_partial_eq!(NgxStr, String);
540    impl_partial_eq!(&'a NgxStr, String);
541    impl_partial_ord!(NgxStr, String);
542    impl_partial_ord!(&'a NgxStr, String);
543    impl_partial_ord_eq_from!(NgxStr, &'a String);
544}
545
546#[cfg(test)]
547mod tests {
548    extern crate alloc;
549
550    use alloc::string::ToString;
551
552    use super::*;
553
554    #[test]
555    fn test_str_comparisons() {
556        let string = "test".to_string();
557        let ngx_string = ngx_str_t {
558            data: string.as_ptr().cast_mut(),
559            len: string.len(),
560        };
561        let ns: &NgxStr = string.as_bytes().into();
562
563        #[cfg(feature = "alloc")]
564        assert_eq!(string, ns);
565        assert_eq!(ngx_string, ns);
566        assert_eq!(string.as_bytes(), ns);
567        assert_eq!(string.as_str(), ns);
568        assert_eq!(b"test", ns);
569        assert_eq!("test", ns);
570
571        #[cfg(feature = "alloc")]
572        assert_eq!(ns, string);
573        assert_eq!(ns, ngx_string);
574        assert_eq!(ns, string.as_bytes());
575        assert_eq!(ns, string.as_str());
576        assert_eq!(ns, b"test");
577        assert_eq!(ns, "test");
578    }
579
580    #[test]
581    #[cfg(feature = "alloc")]
582    fn test_string_comparisons() {
583        use crate::allocator::Global;
584
585        let string = "test".to_string();
586        let ngx_string = ngx_str_t {
587            data: string.as_ptr().cast_mut(),
588            len: string.len(),
589        };
590        let borrowed: &NgxStr = string.as_bytes().into();
591        let owned = NgxString::try_from_bytes_in(&string, Global).unwrap();
592
593        assert_eq!(string.as_bytes(), owned);
594        assert_eq!(ngx_string, owned);
595        assert_eq!(borrowed, owned);
596        assert_eq!(b"test", owned);
597        assert_eq!(owned, string.as_bytes());
598        assert_eq!(owned, ngx_string);
599        assert_eq!(owned, borrowed);
600        assert_eq!(owned, b"test");
601
602        // String comparisons via Deref<Target = NgxStr>
603        assert_eq!(string, *owned);
604        assert_eq!(string.as_str(), *owned);
605        assert_eq!("test", *owned);
606        assert_eq!(*owned, string);
607        assert_eq!(*owned, string.as_str());
608        assert_eq!(*owned, "test");
609    }
610
611    #[test]
612    #[cfg(feature = "alloc")]
613    fn test_string_write() {
614        use core::fmt::Write;
615
616        use crate::allocator::Global;
617
618        let h = NgxStr::from_bytes(b"Hello");
619        let w = NgxStr::from_bytes(b"world");
620
621        let mut s = NgxString::new_in(Global);
622        s.try_reserve(16).expect("reserve");
623
624        // Remember ptr and len of internal buffer
625        let saved = (s.as_bytes().as_ptr(), s.capacity());
626
627        write!(s, "{h} {w}!").expect("write");
628
629        assert_eq!(s, b"Hello world!");
630        assert_eq!((s.as_bytes().as_ptr(), s.capacity()), saved);
631    }
632
633    #[test]
634    fn test_lifetimes() {
635        let a: &NgxStr = "Hello World!".into();
636
637        let s = "Hello World!".to_string();
638        let b: &NgxStr = s.as_bytes().into();
639
640        // The compiler should detect that s is borrowed and fail.
641        // drop(s); // ☢️
642
643        assert_eq!(a.0, b.0);
644    }
645}