nginx_sys/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![no_std]
4
5mod event;
6mod queue;
7
8use core::fmt;
9use core::mem::offset_of;
10use core::ptr::{self, copy_nonoverlapping};
11use core::slice;
12
13#[doc(hidden)]
14mod bindings {
15    #![allow(missing_docs)]
16    #![allow(non_upper_case_globals)]
17    #![allow(non_camel_case_types)]
18    #![allow(non_snake_case)]
19    #![allow(dead_code)]
20    #![allow(clippy::all)]
21    #![allow(improper_ctypes)]
22    #![allow(rustdoc::broken_intra_doc_links)]
23    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
24}
25#[doc(no_inline)]
26pub use bindings::*;
27
28pub use event::*;
29pub use queue::*;
30
31/// The offset of the `main_conf` field in the `ngx_http_conf_ctx_t` struct.
32///
33/// This is used to access the main configuration context for an HTTP module.
34pub const NGX_HTTP_MAIN_CONF_OFFSET: usize = offset_of!(ngx_http_conf_ctx_t, main_conf);
35
36/// The offset of the `srv_conf` field in the `ngx_http_conf_ctx_t` struct.
37///
38/// This is used to access the server configuration context for an HTTP module.
39pub const NGX_HTTP_SRV_CONF_OFFSET: usize = offset_of!(ngx_http_conf_ctx_t, srv_conf);
40
41/// The offset of the `loc_conf` field in the `ngx_http_conf_ctx_t` struct.
42///
43/// This is used to access the location configuration context for an HTTP module.
44pub const NGX_HTTP_LOC_CONF_OFFSET: usize = offset_of!(ngx_http_conf_ctx_t, loc_conf);
45
46/// Convert a byte slice to a raw pointer (`*mut u_char`) allocated in the given nginx memory pool.
47///
48/// # Safety
49///
50/// The caller must provide a valid pointer to the memory pool.
51pub unsafe fn bytes_to_uchar(pool: *mut ngx_pool_t, data: &[u8]) -> Option<*mut u_char> {
52    let ptr: *mut u_char = ngx_pnalloc(pool, data.len()) as _;
53    if ptr.is_null() {
54        return None;
55    }
56    copy_nonoverlapping(data.as_ptr(), ptr, data.len());
57    Some(ptr)
58}
59
60/// Convert a string slice (`&str`) to a raw pointer (`*mut u_char`) allocated in the given nginx memory pool.
61///
62/// # Arguments
63///
64/// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`).
65/// * `data` - The string slice to convert to a raw pointer.
66///
67/// # Safety
68/// This function is marked as unsafe because it involves raw pointer manipulation and direct memory allocation using `ngx_pnalloc`.
69///
70/// # Returns
71/// A raw pointer (`*mut u_char`) to the allocated memory containing the converted string data.
72///
73/// # Example
74/// ```rust,ignore
75/// let pool: *mut ngx_pool_t = ...; // Obtain a pointer to the nginx memory pool
76/// let data: &str = "example"; // The string to convert
77/// let ptr = str_to_uchar(pool, data);
78/// ```
79pub unsafe fn str_to_uchar(pool: *mut ngx_pool_t, data: &str) -> *mut u_char {
80    let ptr: *mut u_char = ngx_pnalloc(pool, data.len()) as _;
81    debug_assert!(!ptr.is_null());
82    copy_nonoverlapping(data.as_ptr(), ptr, data.len());
83    ptr
84}
85
86impl ngx_str_t {
87    /// Returns the contents of this `ngx_str_t` as a byte slice.
88    ///
89    /// The returned slice will **not** contain the optional nul terminator that `ngx_str_t.data`
90    /// may have.
91    #[inline]
92    pub fn as_bytes(&self) -> &[u8] {
93        if self.is_empty() {
94            &[]
95        } else {
96            // SAFETY: `ngx_str_t` with non-zero len must contain a valid correctly aligned pointer
97            unsafe { slice::from_raw_parts(self.data, self.len) }
98        }
99    }
100
101    /// Returns the contents of this `ngx_str_t` as a mutable byte slice.
102    #[inline]
103    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
104        if self.is_empty() {
105            &mut []
106        } else {
107            // SAFETY: `ngx_str_t` with non-zero len must contain a valid correctly aligned pointer
108            unsafe { slice::from_raw_parts_mut(self.data, self.len) }
109        }
110    }
111
112    /// Returns `true` if the string has a length of 0.
113    #[inline]
114    pub fn is_empty(&self) -> bool {
115        self.len == 0
116    }
117
118    /// Convert the nginx string to a string slice (`&str`).
119    ///
120    /// # Panics
121    /// This function panics if the `ngx_str_t` is not valid UTF-8.
122    ///
123    /// # Returns
124    /// A string slice (`&str`) representing the nginx string.
125    pub fn to_str(&self) -> &str {
126        core::str::from_utf8(self.as_bytes()).unwrap()
127    }
128
129    /// Creates an empty `ngx_str_t` instance.
130    ///
131    /// This method replaces the `ngx_null_string` C macro.
132    pub const fn empty() -> Self {
133        ngx_str_t {
134            len: 0,
135            data: ptr::null_mut(),
136        }
137    }
138
139    /// Create an `ngx_str_t` instance from a byte slice.
140    ///
141    /// # Safety
142    ///
143    /// The caller must provide a valid pointer to a memory pool.
144    pub unsafe fn from_bytes(pool: *mut ngx_pool_t, src: &[u8]) -> Option<Self> {
145        bytes_to_uchar(pool, src).map(|data| Self { data, len: src.len() })
146    }
147
148    /// Create an `ngx_str_t` instance from a string slice (`&str`).
149    ///
150    /// # Arguments
151    ///
152    /// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`).
153    /// * `data` - The string slice from which to create the nginx string.
154    ///
155    /// # Safety
156    /// This function is marked as unsafe because it accepts a raw pointer argument. There is no
157    /// way to know if `pool` is pointing to valid memory. The caller must provide a valid pool to
158    /// avoid indeterminate behavior.
159    ///
160    /// # Returns
161    /// An `ngx_str_t` instance representing the given string slice.
162    pub unsafe fn from_str(pool: *mut ngx_pool_t, data: &str) -> Self {
163        ngx_str_t {
164            data: str_to_uchar(pool, data),
165            len: data.len(),
166        }
167    }
168}
169
170impl Default for ngx_str_t {
171    fn default() -> Self {
172        Self::empty()
173    }
174}
175
176impl From<ngx_str_t> for &[u8] {
177    fn from(s: ngx_str_t) -> Self {
178        if s.len == 0 || s.data.is_null() {
179            return Default::default();
180        }
181        unsafe { slice::from_raw_parts(s.data, s.len) }
182    }
183}
184
185impl fmt::Display for ngx_str_t {
186    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187        // The implementation is similar to an inlined `String::from_utf8_lossy`, with two
188        // important differences:
189        //
190        //  - it writes directly to the Formatter instead of allocating a temporary String
191        //  - invalid sequences are represented as escaped individual bytes
192        for chunk in self.as_bytes().utf8_chunks() {
193            f.write_str(chunk.valid())?;
194            for byte in chunk.invalid() {
195                f.write_str("\\x")?;
196                fmt::LowerHex::fmt(byte, f)?;
197            }
198        }
199        Ok(())
200    }
201}
202
203impl TryFrom<ngx_str_t> for &str {
204    type Error = core::str::Utf8Error;
205
206    fn try_from(s: ngx_str_t) -> Result<Self, Self::Error> {
207        core::str::from_utf8(s.into())
208    }
209}
210
211impl ngx_command_t {
212    /// Creates a new empty [`ngx_command_t`] instance.
213    ///
214    /// This method replaces the `ngx_null_command` C macro. This is typically used to terminate an
215    /// array of configuration directives.
216    ///
217    /// [`ngx_command_t`]: https://nginx.org/en/docs/dev/development_guide.html#config_directives
218    pub const fn empty() -> Self {
219        Self {
220            name: ngx_str_t::empty(),
221            type_: 0,
222            set: None,
223            conf: 0,
224            offset: 0,
225            post: ptr::null_mut(),
226        }
227    }
228}
229
230impl ngx_module_t {
231    /// Create a new `ngx_module_t` instance with default values.
232    pub const fn default() -> Self {
233        Self {
234            ctx_index: ngx_uint_t::MAX,
235            index: ngx_uint_t::MAX,
236            name: ptr::null_mut(),
237            spare0: 0,
238            spare1: 0,
239            version: nginx_version as ngx_uint_t,
240            signature: NGX_RS_MODULE_SIGNATURE.as_ptr(),
241            ctx: ptr::null_mut(),
242            commands: ptr::null_mut(),
243            type_: 0,
244            init_master: None,
245            init_module: None,
246            init_process: None,
247            init_thread: None,
248            exit_thread: None,
249            exit_process: None,
250            exit_master: None,
251            spare_hook0: 0,
252            spare_hook1: 0,
253            spare_hook2: 0,
254            spare_hook3: 0,
255            spare_hook4: 0,
256            spare_hook5: 0,
257            spare_hook6: 0,
258            spare_hook7: 0,
259        }
260    }
261}
262
263/// Add a key-value pair to an nginx table entry (`ngx_table_elt_t`) in the given nginx memory pool.
264///
265/// # Arguments
266///
267/// * `table` - A pointer to the nginx table entry (`ngx_table_elt_t`) to modify.
268/// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`) for memory allocation.
269/// * `key` - The key string to add to the table entry.
270/// * `value` - The value string to add to the table entry.
271///
272/// # Safety
273/// This function is marked as unsafe because it involves raw pointer manipulation and direct memory allocation using `str_to_uchar`.
274///
275/// # Returns
276/// An `Option<()>` representing the result of the operation. `Some(())` indicates success, while `None` indicates a null table pointer.
277///
278/// # Example
279/// ```rust
280/// # use nginx_sys::*;
281/// # unsafe fn example(pool: *mut ngx_pool_t, headers: *mut ngx_list_t) {
282/// // Obtain a pointer to the nginx table entry
283/// let table: *mut ngx_table_elt_t = ngx_list_push(headers).cast();
284/// assert!(!table.is_null());
285/// let key: &str = "key"; // The key to add
286/// let value: &str = "value"; // The value to add
287/// let result = add_to_ngx_table(table, pool, key, value);
288/// # }
289/// ```
290pub unsafe fn add_to_ngx_table(
291    table: *mut ngx_table_elt_t,
292    pool: *mut ngx_pool_t,
293    key: impl AsRef<[u8]>,
294    value: impl AsRef<[u8]>,
295) -> Option<()> {
296    if let Some(table) = table.as_mut() {
297        let key = key.as_ref();
298        table.key = ngx_str_t::from_bytes(pool, key)?;
299        table.value = ngx_str_t::from_bytes(pool, value.as_ref())?;
300        table.lowcase_key = ngx_pnalloc(pool, table.key.len).cast();
301        if table.lowcase_key.is_null() {
302            return None;
303        }
304        table.hash = ngx_hash_strlow(table.lowcase_key, table.key.data, table.key.len);
305        return Some(());
306    }
307    None
308}
309
310#[cfg(test)]
311mod tests {
312    extern crate alloc;
313    use alloc::string::ToString;
314
315    use super::*;
316
317    #[test]
318    fn ngx_str_display() {
319        let pairs: &[(&[u8], &str)] = &[
320            (b"", ""),
321            (b"Ferris the \xf0\x9f\xa6\x80", "Ferris the 🦀"),
322            (b"\xF0\x90\x80", "\\xf0\\x90\\x80"),
323            (b"\xF0\x90\x80Hello World", "\\xf0\\x90\\x80Hello World"),
324            (b"Hello \xF0\x90\x80World", "Hello \\xf0\\x90\\x80World"),
325            (b"Hello World\xF0\x90\x80", "Hello World\\xf0\\x90\\x80"),
326        ];
327
328        for (bytes, expected) in pairs {
329            let str = ngx_str_t {
330                data: bytes.as_ptr().cast_mut(),
331                len: bytes.len(),
332            };
333            assert_eq!(str.to_string(), *expected);
334        }
335    }
336}