Skip to content

feat: provide a few convenience methods for ngx_str_t and ngx_array_t #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions examples/async.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions examples/awssig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions examples/curl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 3 additions & 5 deletions examples/shared_dict.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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]) };
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 4 additions & 6 deletions examples/upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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;
Expand Down
44 changes: 44 additions & 0 deletions nginx-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,50 @@ pub const NGX_ALIGNMENT: usize = NGX_RS_ALIGNMENT;
// requirements.
const _: () = assert!(core::mem::align_of::<ngx_str_t>() <= 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<T>(&self) -> &[T] {
debug_assert_eq!(
core::mem::size_of::<T>(),
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<T>(&mut self) -> &mut [T] {
debug_assert_eq!(
core::mem::size_of::<T>(),
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.
///
Expand Down
88 changes: 88 additions & 0 deletions nginx-sys/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ngx_str_t> {
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<ngx_str_t> {
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 {
Expand Down Expand Up @@ -162,3 +222,31 @@ impl TryFrom<ngx_str_t> 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);
}
}
Loading