Skip to content
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
21 changes: 20 additions & 1 deletion src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,29 @@ extern "C" fn cmd_issuer_set_state_path(

issuer.state_path = ptr::null_mut();

if cf.args().get(1).map(ngx_str_t::as_bytes) == Some(b"off") {
// NGX_CONF_TAKE1 ensures that args contains 2 elements
let mut path = cf.args()[1];

if path.as_bytes() == b"off" {
return NGX_CONF_OK;
}

// We need to add our prefix before we pass the path to ngx_conf_set_path_slot,
// because otherwise it will be resolved with cycle->prefix.
if let Some(p) = issuer::NGX_ACME_STATE_PREFIX {
let mut p = ngx_str_t {
data: p.as_ptr().cast_mut(),
len: p.len(),
};

// ngx_get_full_name does not modify input buffers.
if !Status(unsafe { nginx_sys::ngx_get_full_name(cf.pool, &mut p, &mut path) }).is_ok() {
return NGX_CONF_ERROR;
}

cf.args_mut()[1] = path;
}

unsafe { nginx_sys::ngx_conf_set_path_slot(cf, cmd, ptr::from_mut(issuer).cast()) }
}

Expand Down
11 changes: 11 additions & 0 deletions src/conf/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use ngx::ngx_conf_log_error;

pub trait NgxConfExt {
fn args(&self) -> &[ngx_str_t];
fn args_mut(&mut self) -> &mut [ngx_str_t];
fn error(&self, dir: impl AsRef<[u8]>, err: &dyn StdError) -> *mut c_char;
fn pool(&self) -> ngx::core::Pool;
}
Expand All @@ -23,6 +24,16 @@ impl NgxConfExt for ngx_conf_t {
unsafe { self.args.as_ref().map(|x| x.as_slice()).unwrap_or_default() }
}

fn args_mut(&mut self) -> &mut [ngx_str_t] {
// SAFETY: we know that cf.args is an array of ngx_str_t
unsafe {
self.args
.as_mut()
.map(|x| x.as_slice_mut())
.unwrap_or_default()
}
}

fn error(&self, dir: impl AsRef<[u8]>, err: &dyn StdError) -> *mut c_char {
// ngx_conf_log_error does not modify the `cf` itself, and the log is mutable due to being a
// pointer.
Expand Down
36 changes: 32 additions & 4 deletions src/conf/issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use crate::state::issuer::{IssuerContext, IssuerState};
use crate::time::{Time, TimeRange};

pub const ACCOUNT_URL_FILE: &str = "account.url";
pub const NGX_ACME_STATE_PREFIX: Option<&str> = get_state_prefix();

const ACCOUNT_KEY_FILE: &str = "account.key";
const NGX_ACME_DEFAULT_RESOLVER_TIMEOUT: ngx_msec_t = 30000;
Expand Down Expand Up @@ -344,12 +345,10 @@ fn default_state_path(cf: &mut ngx_conf_t, name: &ngx_str_t) -> Result<ngx_str_t
let mut path = Vec::new_in(cf.pool());
let reserve = "acme_".len() + name.len + 1;

if let Some(p) = core::option_env!("NGX_ACME_STATE_PREFIX") {
let p = p.trim_end_matches('/');
path.try_reserve_exact(p.len() + reserve + 1)
if let Some(p) = NGX_ACME_STATE_PREFIX {
path.try_reserve_exact(p.len() + reserve)
.map_err(|_| AllocError)?;
path.extend(p.as_bytes());
path.push(b'/');
}

path.try_reserve_exact(reserve).map_err(|_| AllocError)?;
Expand Down Expand Up @@ -450,3 +449,32 @@ impl StateDir {
Ok(cert)
}
}

/// Returns NGX_ACME_STATE_PREFIX value with a trailing '/'.
const fn get_state_prefix() -> Option<&'static str> {
const fn trim_trailing_slashes(x: &str) -> &str {
let mut bytes = x.as_bytes();
while let [rest @ .., last] = bytes {
if *last == b'/' {
bytes = rest;
} else {
break;
}
}
// SAFETY: the transform above cannot produce an invalid UTF-8 sequence.
unsafe { core::str::from_utf8_unchecked(bytes) }
}

const RAW_PREFIX: Option<&str> = core::option_env!("NGX_ACME_STATE_PREFIX");
if RAW_PREFIX.is_none() {
return None;
}

// Strip all the trailing slashes from the path.
const TRIMMED_PREFIX: &str = match RAW_PREFIX {
Some(x) => trim_trailing_slashes(x),
None => "", // unreachable
Copy link

Copilot AI Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The unreachable branch in line 476 could be replaced with unreachable!() macro to make the intent explicit and provide better compile-time guarantees. This would help prevent future logic errors if the const evaluation changes.

Suggested change
None => "", // unreachable
None => unreachable!(),

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VAL_0, and VAL_1 seems a bit odd

diff --git a/src/conf/issuer.rs b/src/conf/issuer.rs
index ccd8807..086d4ce 100644
--- a/src/conf/issuer.rs
+++ b/src/conf/issuer.rs
@@ -465,16 +465,16 @@ const fn get_state_prefix() -> Option<&'static str> {
         unsafe { core::str::from_utf8_unchecked(bytes) }
     }
 
-    const VAL_0: Option<&str> = core::option_env!("NGX_ACME_STATE_PREFIX");
-    if VAL_0.is_none() {
+    const RAW_ENV_PREFIX: Option<&str> = core::option_env!("NGX_ACME_STATE_PREFIX");
+    if RAW_ENV_PREFIX.is_none() {
         return None;
     }
 
     // Strip all the trailing slashes from the path.
-    const VAL_1: &str = match VAL_0 {
+    const TRIMMED_ENV_PREFIX: &str = match RAW_ENV_PREFIX {
         Some(x) => trim_trailing_slashes(x),
         None => "", // unreachable
     };
 
-    Some(constcat::concat!(VAL_1, "/"))
+    Some(constcat::concat!(TRIMMED_ENV_PREFIX, "/"))
 }

};

Some(constcat::concat!(TRIMMED_PREFIX, "/"))
}