From 3a04f2e198e045316bcb1d5272e58c1881cc3d0c Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 19 Jul 2025 19:59:38 -0700 Subject: [PATCH 1/2] feat(sys): add ngx_str_t::strip_prefix/suffix methods The former is particularly useful when parsing the configuration to avoid creation of an intermediate reference. The latter added for sake of completeness. --- nginx-sys/src/string.rs | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/nginx-sys/src/string.rs b/nginx-sys/src/string.rs index b316430..c386fb6 100644 --- a/nginx-sys/src/string.rs +++ b/nginx-sys/src/string.rs @@ -93,6 +93,66 @@ impl ngx_str_t { len: data.len(), } } + + /// Divides one `ngx_str_t` into two at an index. + /// + /// # Safety + /// + /// The results will reference the original string; be wary of the ownership and lifetime. + pub fn split_at(&self, mid: usize) -> Option<(ngx_str_t, ngx_str_t)> { + if mid > self.len { + return None; + } + + Some(( + ngx_str_t { + data: self.data, + len: mid, + }, + ngx_str_t { + data: unsafe { self.data.add(mid) }, + len: self.len - mid, + }, + )) + } + + /// Returns an `ngx_str_t` with the prefix removed. + /// + /// If the string starts with the byte sequence `prefix`, returns the substring after the + /// prefix, wrapped in `Some`. The resulting substring can be empty. + /// + /// # Safety + /// + /// The result will reference the original string; be wary of the ownership and lifetime. + /// + /// The method is not marked as `unsafe` as everything it does is possible via safe interfaces. + pub fn strip_prefix(&self, prefix: impl AsRef<[u8]>) -> Option { + let prefix = prefix.as_ref(); + if self.as_bytes().starts_with(prefix) { + self.split_at(prefix.len()).map(|x| x.1) + } else { + None + } + } + + /// Returns an `ngx_str_t` with the suffix removed. + /// + /// If the string ends with the byte sequence `suffix`, returns the substring before the + /// suffix, wrapped in `Some`. The resulting substring can be empty. + /// + /// # Safety + /// + /// The result will reference the original string; be wary of the ownership and lifetime. + /// + /// The method is not marked as `unsafe` as everything it does is possible via safe interfaces. + pub fn strip_suffix(&self, suffix: impl AsRef<[u8]>) -> Option { + let suffix = suffix.as_ref(); + if self.as_bytes().ends_with(suffix) { + self.split_at(self.len - suffix.len()).map(|x| x.0) + } else { + None + } + } } impl AsRef<[u8]> for ngx_str_t { @@ -162,3 +222,31 @@ impl TryFrom for &str { str::from_utf8(s.into()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ngx_str_prefix() { + let s = "key=value"; + let s = ngx_str_t { + data: s.as_ptr().cast_mut(), + len: s.len(), + }; + + assert_eq!( + s.strip_prefix("key=").as_ref().map(ngx_str_t::as_bytes), + Some("value".as_bytes()) + ); + + assert_eq!(s.strip_prefix("test"), None); + + assert_eq!( + s.strip_suffix("value").as_ref().map(ngx_str_t::as_bytes), + Some("key=".as_bytes()) + ); + + assert_eq!(s.strip_suffix("test"), None); + } +} From c1315daff1c02ce11670faafc7b5ae9df2d604b6 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 19 Jul 2025 20:11:21 -0700 Subject: [PATCH 2/2] feat(sys): add ngx_array_t::as_slice method --- examples/async.rs | 3 +-- examples/awssig.rs | 16 +++++++-------- examples/curl.rs | 4 ++-- examples/shared_dict.rs | 8 +++----- examples/upstream.rs | 10 ++++------ nginx-sys/src/lib.rs | 44 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 62 insertions(+), 23 deletions(-) diff --git a/examples/async.rs b/examples/async.rs index f46f20e..c158d02 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -1,6 +1,5 @@ use std::ffi::{c_char, c_void}; use std::ptr::{addr_of, addr_of_mut}; -use std::slice; use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use std::sync::{Arc, OnceLock}; use std::time::Instant; @@ -205,7 +204,7 @@ extern "C" fn ngx_http_async_commands_set_enable( ) -> *mut c_char { unsafe { let conf = &mut *(conf as *mut ModuleConfig); - let args = slice::from_raw_parts((*(*cf).args).elts as *mut ngx_str_t, (*(*cf).args).nelts); + let args: &[ngx_str_t] = (*(*cf).args).as_slice(); let val = args[1].to_str(); // set default value optionally diff --git a/examples/awssig.rs b/examples/awssig.rs index 7a93e10..5b903dc 100644 --- a/examples/awssig.rs +++ b/examples/awssig.rs @@ -175,8 +175,8 @@ extern "C" fn ngx_http_awssigv4_commands_set_enable( ) -> *mut c_char { unsafe { let conf = &mut *(conf as *mut ModuleConfig); - let args = (*(*cf).args).elts as *mut ngx_str_t; - let val = (*args.add(1)).to_str(); + let args: &[ngx_str_t] = (*(*cf).args).as_slice(); + let val = args[1].to_str(); // set default value optionally conf.enable = false; @@ -198,8 +198,8 @@ extern "C" fn ngx_http_awssigv4_commands_set_access_key( ) -> *mut c_char { unsafe { let conf = &mut *(conf as *mut ModuleConfig); - let args = (*(*cf).args).elts as *mut ngx_str_t; - conf.access_key = (*args.add(1)).to_string(); + let args: &[ngx_str_t] = (*(*cf).args).as_slice(); + conf.access_key = args[1].to_string(); }; ngx::core::NGX_CONF_OK @@ -212,8 +212,8 @@ extern "C" fn ngx_http_awssigv4_commands_set_secret_key( ) -> *mut c_char { unsafe { let conf = &mut *(conf as *mut ModuleConfig); - let args = (*(*cf).args).elts as *mut ngx_str_t; - conf.secret_key = (*args.add(1)).to_string(); + let args: &[ngx_str_t] = (*(*cf).args).as_slice(); + conf.secret_key = args[1].to_string(); }; ngx::core::NGX_CONF_OK @@ -226,8 +226,8 @@ extern "C" fn ngx_http_awssigv4_commands_set_s3_bucket( ) -> *mut c_char { unsafe { let conf = &mut *(conf as *mut ModuleConfig); - let args = (*(*cf).args).elts as *mut ngx_str_t; - conf.s3_bucket = (*args.add(1)).to_string(); + let args: &[ngx_str_t] = (*(*cf).args).as_slice(); + conf.s3_bucket = args[1].to_string(); if conf.s3_bucket.len() == 1 { println!("Validation failed"); return ngx::core::NGX_CONF_ERROR; diff --git a/examples/curl.rs b/examples/curl.rs index da7c9da..74b4613 100644 --- a/examples/curl.rs +++ b/examples/curl.rs @@ -117,9 +117,9 @@ extern "C" fn ngx_http_curl_commands_set_enable( ) -> *mut c_char { unsafe { let conf = &mut *(conf as *mut ModuleConfig); - let args = (*(*cf).args).elts as *mut ngx_str_t; + let args: &[ngx_str_t] = (*(*cf).args).as_slice(); - let val = (*args.add(1)).to_str(); + let val = args[1].to_str(); // set default value optionally conf.enable = false; diff --git a/examples/shared_dict.rs b/examples/shared_dict.rs index 0c02e0f..7b332bb 100644 --- a/examples/shared_dict.rs +++ b/examples/shared_dict.rs @@ -1,6 +1,6 @@ #![no_std] use ::core::ffi::{c_char, c_void}; -use ::core::{mem, ptr, slice}; +use ::core::{mem, ptr}; use nginx_sys::{ ngx_command_t, ngx_conf_t, ngx_http_add_variable, ngx_http_compile_complex_value_t, @@ -127,8 +127,7 @@ extern "C" fn ngx_http_shared_dict_add_zone( // - `cf.args` is guaranteed to be a pointer to an array with 3 elements (NGX_CONF_TAKE2). // - The pointers are well-aligned by construction method (`ngx_palloc`). debug_assert!(!cf.args.is_null() && unsafe { (*cf.args).nelts >= 3 }); - let args = - unsafe { slice::from_raw_parts_mut((*cf.args).elts as *mut ngx_str_t, (*cf.args).nelts) }; + let args = unsafe { (*cf.args).as_slice_mut() }; let name: ngx_str_t = args[1]; let size = unsafe { ngx_parse_size(&mut args[2]) }; @@ -210,8 +209,7 @@ extern "C" fn ngx_http_shared_dict_add_variable( // - `cf.args` is guaranteed to be a pointer to an array with 3 elements (NGX_CONF_TAKE2). // - The pointers are well-aligned by construction method (`ngx_palloc`). debug_assert!(!cf.args.is_null() && unsafe { (*cf.args).nelts >= 3 }); - let args = - unsafe { slice::from_raw_parts_mut((*cf.args).elts as *mut ngx_str_t, (*cf.args).nelts) }; + let args = unsafe { (*cf.args).as_slice_mut() }; let mut ccv: ngx_http_compile_complex_value_t = unsafe { mem::zeroed() }; ccv.cf = cf; diff --git a/examples/upstream.rs b/examples/upstream.rs index 7517ece..5680458 100644 --- a/examples/upstream.rs +++ b/examples/upstream.rs @@ -8,7 +8,6 @@ */ use std::ffi::{c_char, c_void}; use std::mem; -use std::slice; use ngx::core::{Pool, Status}; use ngx::ffi::{ @@ -267,19 +266,18 @@ unsafe extern "C" fn ngx_http_upstream_commands_set_custom( // SAFETY: this function is called with non-NULL cf always let cf = &mut *cf; ngx_log_debug_mask!(DebugMask::Http, cf.log, "CUSTOM UPSTREAM module init"); + let args: &[ngx_str_t] = (*cf.args).as_slice(); let ccf = &mut (*(conf as *mut SrvConfig)); - if (*cf.args).nelts == 2 { - let value: &[ngx_str_t] = - slice::from_raw_parts((*cf.args).elts as *const ngx_str_t, (*cf.args).nelts); - let n = ngx_atoi(value[1].data, value[1].len); + if let Some(value) = args.get(1) { + let n = ngx_atoi(value.data, value.len); if n == (NGX_ERROR as isize) || n == 0 { ngx_conf_log_error!( NGX_LOG_EMERG, cf, "invalid value \"{}\" in \"{}\" directive", - value[1], + value, &(*cmd).name ); return ngx::core::NGX_CONF_ERROR; diff --git a/nginx-sys/src/lib.rs b/nginx-sys/src/lib.rs index d80dbe4..4d40e2b 100644 --- a/nginx-sys/src/lib.rs +++ b/nginx-sys/src/lib.rs @@ -46,6 +46,50 @@ pub const NGX_ALIGNMENT: usize = NGX_RS_ALIGNMENT; // requirements. const _: () = assert!(core::mem::align_of::() <= NGX_ALIGNMENT); +impl ngx_array_t { + /// Returns the contents of this array as a slice of `T`. + /// + /// # Safety + /// + /// The array must be a valid, initialized array containing elements of type T or compatible in + /// layout with T (e.g. `#[repr(transparent)]` wrappers). + pub unsafe fn as_slice(&self) -> &[T] { + debug_assert_eq!( + core::mem::size_of::(), + self.size, + "ngx_array_t::as_slice(): element size mismatch" + ); + if self.nelts == 0 { + &[] + } else { + // SAFETY: in a valid array, `elts` is a valid well-aligned pointer to at least `nelts` + // elements of size `size` + core::slice::from_raw_parts(self.elts.cast(), self.nelts) + } + } + + /// Returns the contents of this array as a mutable slice of `T`. + /// + /// # Safety + /// + /// The array must be a valid, initialized array containing elements of type T or compatible in + /// layout with T (e.g. `#[repr(transparent)]` wrappers). + pub unsafe fn as_slice_mut(&mut self) -> &mut [T] { + debug_assert_eq!( + core::mem::size_of::(), + self.size, + "ngx_array_t::as_slice_mut(): element size mismatch" + ); + if self.nelts == 0 { + &mut [] + } else { + // SAFETY: in a valid array, `elts` is a valid well-aligned pointer to at least `nelts` + // elements of size `size` + core::slice::from_raw_parts_mut(self.elts.cast(), self.nelts) + } + } +} + impl ngx_command_t { /// Creates a new empty [`ngx_command_t`] instance. ///