diff --git a/Cargo.lock b/Cargo.lock index 25ee386..a69aae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,7 +456,7 @@ dependencies = [ [[package]] name = "ngx" -version = "0.3.0-beta" +version = "0.4.0-beta" dependencies = [ "nginx-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 6eac1e0..6fed388 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "ngx" -version = "0.3.0-beta" +version = "0.4.0-beta" edition = "2021" autoexamples = false categories = ["api-bindings", "network-programming"] diff --git a/nginx-sys/src/lib.rs b/nginx-sys/src/lib.rs index ad1c5d4..cdfd38d 100644 --- a/nginx-sys/src/lib.rs +++ b/nginx-sys/src/lib.rs @@ -70,11 +70,9 @@ pub use bindings::*; /// let data: &str = "example"; // The string to convert /// let ptr = str_to_uchar(pool, data); /// ``` -pub fn str_to_uchar(pool: *mut ngx_pool_t, data: &str) -> *mut u_char { +pub unsafe fn str_to_uchar(pool: *mut ngx_pool_t, data: &str) -> *mut u_char { let ptr: *mut u_char = unsafe { ngx_palloc(pool, data.len() as _) as _ }; - unsafe { - copy_nonoverlapping(data.as_ptr(), ptr, data.len()); - } + copy_nonoverlapping(data.as_ptr(), ptr, data.len()); ptr } @@ -94,14 +92,6 @@ impl ngx_str_t { } } - /// Convert the nginx string to a `String` by copying its contents. - /// - /// # Returns - /// A new `String` containing the contents of the nginx string. - pub fn to_string(&self) -> String { - return String::from(self.to_str()); - } - /// Create an `ngx_str_t` instance from a `String`. /// /// # Arguments @@ -109,9 +99,13 @@ impl ngx_str_t { /// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`). /// * `data` - The `String` from which to create the nginx string. /// + /// # Safety + /// This function is marked as unsafe because it passes a raw pointer (pool) to another unsafe + /// function which allocates a buffer from the pool. + /// /// # Returns /// An `ngx_str_t` instance representing the given `String`. - pub fn from_string(pool: *mut ngx_pool_t, data: String) -> Self { + pub unsafe fn from_string(pool: *mut ngx_pool_t, data: String) -> Self { ngx_str_t { data: str_to_uchar(pool, data.as_str()), len: data.len() as _, @@ -125,9 +119,13 @@ impl ngx_str_t { /// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`). /// * `data` - The string slice from which to create the nginx string. /// + /// # Safety + /// This function is marked as unsafe because it passes a raw pointer (pool) to another unsafe + /// function which allocates a buffer from the pool. + /// /// # Returns /// An `ngx_str_t` instance representing the given string slice. - pub fn from_str(pool: *mut ngx_pool_t, data: &str) -> Self { + pub unsafe fn from_str(pool: *mut ngx_pool_t, data: &str) -> Self { ngx_str_t { data: str_to_uchar(pool, data), len: data.len() as _, @@ -190,11 +188,16 @@ impl TryFrom for &str { /// let value: &str = "value"; // The value to add /// let result = add_to_ngx_table(table, pool, key, value); /// ``` -pub fn add_to_ngx_table(table: *mut ngx_table_elt_t, pool: *mut ngx_pool_t, key: &str, value: &str) -> Option<()> { +pub unsafe fn add_to_ngx_table( + table: *mut ngx_table_elt_t, + pool: *mut ngx_pool_t, + key: &str, + value: &str, +) -> Option<()> { if table.is_null() { return None; } - unsafe { table.as_mut() }.map(|table| { + table.as_mut().map(|table| { table.hash = 1; table.key.len = key.len() as _; table.key.data = str_to_uchar(pool, key); diff --git a/src/core/event.rs b/src/core/event.rs new file mode 100644 index 0000000..fd68e77 --- /dev/null +++ b/src/core/event.rs @@ -0,0 +1,116 @@ +use crate::ffi::*; +use crate::ngx_log_debug_mask; + +/// Wrapper struct for an `ngx_event_t` pointer, provides an interface into timer methods. +#[repr(transparent)] +pub struct Event(pub ngx_event_t); + +impl Event { + #[inline] + fn ident(&self) -> i32 { + let conn = self.0.data as *const ngx_connection_t; + unsafe { (*conn).fd } + } + + /// Adds a timer to this event. Argument `timer` is in milliseconds. + pub fn add_timer(&mut self, timer_msec: ngx_msec_t) { + let key: ngx_msec_int_t = unsafe { ngx_current_msec as isize + timer_msec as isize }; + if self.0.timer_set() != 0 { + /* FROM NGX: + * Use a previous timer value if difference between it and a new + * value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows + * to minimize the rbtree operations for fast connections. + */ + let diff = key - self.0.timer.key as ngx_msec_int_t; + if diff.abs() < NGX_TIMER_LAZY_DELAY as isize { + ngx_log_debug_mask!( + NGX_LOG_DEBUG_EVENT, + self.0.log, + "event time: {}, old: {:?}, new: {:?}", + self.ident(), + self.0.timer.key, + key + ); + return; + } + + self.del_timer(); + } + + self.0.timer.key = key as ngx_msec_t; + ngx_log_debug_mask!( + NGX_LOG_DEBUG_EVENT, + self.0.log, + "event time: {}, old: {:?}, new: {:?}", + self.ident(), + self.0.timer.key, + key + ); + unsafe { + ngx_rbtree_insert(&mut ngx_event_timer_rbtree as *mut _, &mut self.0.timer as *mut _); + } + + self.0.set_timer_set(1); + } + + /// Deletes an associated timer from this event. + pub fn del_timer(&mut self) { + ngx_log_debug_mask!( + NGX_LOG_DEBUG_EVENT, + self.0.log, + "event timer del: {}:{:?}", + self.ident(), + self.0.timer.key + ); + unsafe { + ngx_rbtree_delete(&mut ngx_event_timer_rbtree as *mut _, &mut self.0.timer as *mut _); + } + + self.0.set_timer_set(0); + } + + /// Add event to processing queue. Translated from ngx_post_event macro. + /// + /// # Safety + /// This function is marked unsafe because it dereferences a raw pointer. The pointer (queue) + /// MUST NOT be null to satisfy its contract, will panic with null input. + /// + /// # Panics + /// Panics if the given queue is null. + pub unsafe fn post_to_queue(&mut self, queue: *mut ngx_queue_t) { + assert!(!queue.is_null(), "queue is empty"); + if self.0.posted() == 0 { + self.0.set_posted(1); + // translated from ngx_queue_insert_tail macro + self.0.queue.prev = (*queue).prev; + (*self.0.queue.prev).next = &self.0.queue as *const _ as *mut _; + self.0.queue.next = queue; + (*queue).prev = &self.0.queue as *const _ as *mut _; + } + } + + /// new_for_request creates an new Event (ngx_event_t) from the Request pool. + /// + /// # Safety + /// This function is marked as unsafe because it involves dereferencing a raw pointer memory + /// allocation from the underlying Nginx pool allocator. + /// + /// # Returns + /// An `Option<&mut Event>` representing the result of the allocation. `Some(&mut Event)` + /// indicates successful allocation, while `None` indicates a null Event. + pub unsafe fn new_for_request(req: &mut crate::http::Request) -> Option<&mut Event> { + Some(&mut *(req.pool().alloc(std::mem::size_of::()) as *mut Event)) + } +} + +impl From<*mut ngx_event_t> for &mut Event { + fn from(evt: *mut ngx_event_t) -> Self { + unsafe { &mut *evt.cast::() } + } +} + +impl From<&mut Event> for *mut ngx_event_t { + fn from(val: &mut Event) -> Self { + &mut val.0 as *mut ngx_event_t + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index a91a496..b2571ed 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,9 +1,11 @@ mod buffer; +mod event; mod pool; mod status; mod string; pub use buffer::*; +pub use event::*; pub use pool::*; pub use status::*; pub use string::*; diff --git a/src/http/flags.rs b/src/http/flags.rs new file mode 100644 index 0000000..9928353 --- /dev/null +++ b/src/http/flags.rs @@ -0,0 +1,187 @@ +use crate::ffi::ngx_uint_t; +use std::{ + mem, + ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, +}; + +/// SubrequestFlags is a bitmask for NGINX subrequest control. +/// Refer to https://nginx.org/en/docs/dev/development_guide.html#http_subrequests for more +/// details. +/// +/// The following flags are available: +/// None: Zero value of the subrequest flag. +/// InMemory - Output is not sent to the client, but rather stored in memory. The flag only affects subrequests which are processed by one of the proxying modules. After a subrequest is finalized its output is available in r->out of type ngx_buf_t. +/// Waited - The subrequest's done flag is set even if the subrequest is not active when it is finalized. This subrequest flag is used by the SSI filter. +/// Clone - The subrequest is created as a clone of its parent. It is started at the same location and proceeds from the same phase as the parent request. +/// Background - The subrequest operates in the background (useful for background cache updates), this type of subrequest does not block any other subrequests or the main request. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u32)] +pub enum SubrequestFlags { + /// None: Zero value of the subrequest flag. + None = 0, + // unused = 1 ngx_http_request.h:65 + /// InMemory - Output is not sent to the client, but rather stored in memory. The flag only affects subrequests which are processed by one of the proxying modules. After a subrequest is finalized its output is available in r->out of type ngx_buf_t. + InMemory = 2, + /// Waited - The subrequest's done flag is set even if the subrequest is not active when it is finalized. This subrequest flag is used by the SSI filter. + Waited = 4, + /// Clone - The subrequest is created as a clone of its parent. It is started at the same location and proceeds from the same phase as the parent request. + Clone = 8, + /// Background - The subrequest operates in the background (useful for background cache updates), this type of subrequest does not block any other subrequests or the main request. + Background = 16, +} + +impl BitAnd for SubrequestFlags { + type Output = Self; + + #[inline] + fn bitand(self, rhs: Self) -> Self { + unsafe { mem::transmute(self as u32 & rhs as u32) } + } +} + +impl BitAndAssign for SubrequestFlags { + #[inline] + fn bitand_assign(&mut self, rhs: Self) { + *self = *self & rhs; + } +} + +impl BitOr for SubrequestFlags { + type Output = Self; + + #[inline] + fn bitor(self, rhs: Self) -> Self { + unsafe { mem::transmute(self as u32 | rhs as u32) } + } +} + +impl BitOrAssign for SubrequestFlags { + #[inline] + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; + } +} + +impl BitXor for SubrequestFlags { + type Output = Self; + + #[inline] + fn bitxor(self, rhs: Self) -> Self { + unsafe { std::mem::transmute(self as u32 ^ rhs as u32) } + } +} + +impl BitXorAssign for SubrequestFlags { + #[inline] + fn bitxor_assign(&mut self, rhs: Self) { + *self = *self ^ rhs; + } +} + +impl Not for SubrequestFlags { + type Output = Self; + + #[inline] + fn not(self) -> Self { + unsafe { std::mem::transmute(!(self as u32)) } + } +} + +impl From for u32 { + #[inline] + fn from(flags: SubrequestFlags) -> Self { + flags as u32 + } +} + +impl From for ngx_uint_t { + #[inline] + fn from(flags: SubrequestFlags) -> Self { + flags as ngx_uint_t + } +} + +impl SubrequestFlags { + /// Tests if flag(s) are set on the SubrequestFlags. + #[inline] + pub fn has_flag(&self, flag: SubrequestFlags) -> bool { + (*self as u32 & flag as u32) != 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_and() { + let mut flag: SubrequestFlags = SubrequestFlags::Background; + assert_eq!(flag.has_flag(SubrequestFlags::Background), true); + + let test_flag = flag & SubrequestFlags::InMemory; + assert_eq!(test_flag, SubrequestFlags::None); + assert_eq!(test_flag.has_flag(SubrequestFlags::Background), false); + assert_eq!(test_flag.has_flag(SubrequestFlags::InMemory), false); + + flag &= SubrequestFlags::Clone; + assert_eq!(flag, SubrequestFlags::None); + assert_eq!(flag.has_flag(SubrequestFlags::Clone), false); + assert_eq!(flag.has_flag(SubrequestFlags::Background), false); + } + + #[test] + fn test_or() { + let mut flag: SubrequestFlags = SubrequestFlags::Background; + assert_eq!(flag.has_flag(SubrequestFlags::Background), true); + + let test_flag = flag | SubrequestFlags::InMemory; + assert_eq!(test_flag as u32, 18); + assert_eq!(test_flag.has_flag(SubrequestFlags::Background), true); + assert_eq!(test_flag.has_flag(SubrequestFlags::InMemory), true); + assert_eq!(test_flag.has_flag(SubrequestFlags::Clone), false); + + flag |= SubrequestFlags::Clone; + assert_eq!(flag as u32, 24); + assert_eq!(flag.has_flag(SubrequestFlags::Background), true); + assert_eq!(flag.has_flag(SubrequestFlags::Clone), true); + assert_eq!(flag.has_flag(SubrequestFlags::InMemory), false); + } + + #[test] + fn test_xor() { + let mut flag: SubrequestFlags = SubrequestFlags::Background | SubrequestFlags::InMemory; + assert_eq!(flag as u32, 18); + + let test_flag = flag ^ SubrequestFlags::Background; + assert_eq!(test_flag, SubrequestFlags::InMemory); + assert_eq!(test_flag.has_flag(SubrequestFlags::Background), false); + assert_eq!(test_flag.has_flag(SubrequestFlags::Clone), false); + assert_eq!(test_flag.has_flag(SubrequestFlags::InMemory), true); + + flag ^= SubrequestFlags::Background; + assert_eq!(flag, SubrequestFlags::InMemory); + assert_eq!(flag.has_flag(SubrequestFlags::Background), false); + assert_eq!(flag.has_flag(SubrequestFlags::Clone), false); + assert_eq!(flag.has_flag(SubrequestFlags::InMemory), true); + + flag ^= SubrequestFlags::Clone; + assert_eq!(flag as u32, 10); + assert_eq!(flag.has_flag(SubrequestFlags::Background), false); + assert_eq!(flag.has_flag(SubrequestFlags::Clone), true); + assert_eq!(flag.has_flag(SubrequestFlags::InMemory), true); + } + + #[test] + fn test_not() { + let flag: SubrequestFlags = SubrequestFlags::Background | SubrequestFlags::InMemory; + assert_eq!(flag as u32, 18); + + let test_flag: SubrequestFlags = flag & !SubrequestFlags::Background; + assert_eq!(test_flag, SubrequestFlags::InMemory); + + assert_eq!(!SubrequestFlags::InMemory as i32, -3); + assert_eq!(!SubrequestFlags::Waited as i32, -5); + assert_eq!(!SubrequestFlags::Clone as i32, -9); + assert_eq!(!SubrequestFlags::Background as i32, -17); + } +} diff --git a/src/http/mod.rs b/src/http/mod.rs index 230ce0b..6e23a0a 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,9 +1,11 @@ mod conf; +mod flags; mod module; mod request; mod status; pub use conf::*; +pub use flags::*; pub use module::*; pub use request::*; pub use status::*; diff --git a/src/http/request.rs b/src/http/request.rs index 88607fd..3d5aca2 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -1,5 +1,6 @@ use crate::core::*; use crate::ffi::*; +use crate::http::flags::SubrequestFlags; use crate::http::status::*; use crate::ngx_null_string; use std::fmt; @@ -191,14 +192,30 @@ impl Request { self.0.headers_out.status = status.into(); } + /// Get HTTP status of response. + pub fn get_status(&self) -> HTTPStatus { + HTTPStatus(self.0.headers_out.status) + } + + /// Add one to the request's current cycle count. + pub fn increment_cycle_count(&mut self) { + self.0.set_count(self.0.count() + 1); + } + + /// Add header key and value to the input headers object. + /// + /// See https://nginx.org/en/docs/dev/development_guide.html#http_request `headers_in`. pub fn add_header_in(&mut self, key: &str, value: &str) -> Option<()> { let table: *mut ngx_table_elt_t = unsafe { ngx_list_push(&mut self.0.headers_in.headers) as _ }; - add_to_ngx_table(table, self.0.pool, key, value) + unsafe { add_to_ngx_table(table, self.0.pool, key, value) } } + /// Add header key and value to the output headers object. + /// + /// See https://nginx.org/en/docs/dev/development_guide.html#http_request `headers_out`. pub fn add_header_out(&mut self, key: &str, value: &str) -> Option<()> { let table: *mut ngx_table_elt_t = unsafe { ngx_list_push(&mut self.0.headers_out.headers) as _ }; - add_to_ngx_table(table, self.0.pool, key, value) + unsafe { add_to_ngx_table(table, self.0.pool, key, value) } } /// Set response body [Content-Length]. @@ -247,36 +264,83 @@ impl Request { unsafe { Status(ngx_http_output_filter(&mut self.0, body)) } } - /// Perform internal redirect to a location - pub fn internal_redirect(&self, location: &str) -> Status { - assert!(!location.is_empty(), "uri location is empty"); - let uri_ptr = &mut ngx_str_t::from_str(self.0.pool, location) as *mut _; - - // FIXME: check status of ngx_http_named_location or ngx_http_internal_redirect + /// Utility method to perform an internal redirect without args (query parameters) or named + /// location. + /// For full control methods see `ngx_internal_redirect` and `ngx_named_location`. + /// + /// # Safety + /// + /// This method invokes unsafe methods. + pub unsafe fn internal_redirect(&self, location: &str) -> Status { if location.starts_with('@') { - unsafe { - ngx_http_named_location((self as *const Request as *mut Request).cast(), uri_ptr); - } + self.ngx_named_location(location) } else { - unsafe { - ngx_http_internal_redirect( - (self as *const Request as *mut Request).cast(), - uri_ptr, - std::ptr::null_mut(), - ); - } + self.ngx_internal_redirect(location, "") } - Status::NGX_DONE + } + + /// Invoke ngx_internal_redirect to perform an internal redirect to a location. + /// + /// # Safety + /// + /// This method calls into unsafe functions on the stack and dereferences raw pointers to + /// interface with NGINX API primitives. + pub unsafe fn ngx_internal_redirect(&self, location: &str, args: &str) -> Status { + assert!(!location.is_empty(), "uri location is empty"); + let uri_ptr = &mut ngx_str_t::from_str(self.0.pool, location) as *mut _; + let args_ptr = if !args.is_empty() { + &mut ngx_str_t::from_str(self.0.pool, location) as *mut _ + } else { + std::ptr::null_mut() + }; + + Status(ngx_http_internal_redirect( + (self as *const Request as *mut Request).cast(), + uri_ptr, + args_ptr, + )) + } + + /// Invoke ngx_named_location to perform an internal redirect to a named location. + /// + /// # Safety + /// + /// This method calls into unsafe functions on the stack and dereferences raw pointers to + /// interface with NGINX API primitives. + pub unsafe fn ngx_named_location(&self, location: &str) -> Status { + assert!(!location.is_empty(), "uri location is empty"); + assert!(location.starts_with('@'), "named location must start with @"); + let uri_ptr = &mut ngx_str_t::from_str(self.0.pool, location) as *mut _; + + Status(ngx_http_named_location( + (self as *const Request as *mut Request).cast(), + uri_ptr, + )) + } + + /// How many subrequests are available to make in this request, + /// will return NGX_HTTP_MAX_SUBREQUESTS for a parent request. + pub fn subrequests_available(&self) -> u32 { + // 1 is subtracted because this function was caught returning 1 extra + // The return value should be (50, 0), with the parent request returning + // NGX_HTTP_MAX_SUBREQUESTS. + // See http://nginx.org/en/docs/dev/development_guide.html#http_subrequests + // for more information + self.0.subrequests() - 1 } /// Send a subrequest pub fn subrequest( &self, uri: &str, + args: &str, + flags: SubrequestFlags, module: &ngx_module_t, + data: Option<*mut c_void>, post_callback: unsafe extern "C" fn(*mut ngx_http_request_t, *mut c_void, ngx_int_t) -> ngx_int_t, ) -> Status { - let uri_ptr = &mut ngx_str_t::from_str(self.0.pool, uri) as *mut _; + let uri_ptr = unsafe { &mut ngx_str_t::from_str(self.0.pool, uri) as *mut _ }; + let args_ptr = unsafe { &mut ngx_str_t::from_str(self.0.pool, args) as *mut _ }; // ------------- // allocate memory and set values for ngx_http_post_subrequest_t let sub_ptr = self.pool().alloc(std::mem::size_of::()); @@ -285,7 +349,12 @@ impl Request { let post_subreq = sub_ptr as *const ngx_http_post_subrequest_t as *mut ngx_http_post_subrequest_t; unsafe { (*post_subreq).handler = Some(post_callback); - (*post_subreq).data = self.get_module_ctx_ptr(module); // WARN: safety! ensure that ctx is already set + if let Some(datum) = data { + (*post_subreq).data = datum; + } else { + // WARN: safety! ensure that ctx is already set + (*post_subreq).data = self.get_module_ctx_ptr(module); + } } // ------------- @@ -294,26 +363,13 @@ impl Request { ngx_http_subrequest( (self as *const Request as *mut Request).cast(), uri_ptr, - std::ptr::null_mut(), + args_ptr, &mut psr as *mut _, sub_ptr as *mut _, - NGX_HTTP_SUBREQUEST_WAITED as _, + flags.into(), ) }; - // previously call of ngx_http_subrequest() would ensure that the pointer is not null anymore - let mut sr = unsafe { &mut *psr }; - - /* - * allocate fake request body to avoid attempts to read it and to make - * sure real body file (if already read) won't be closed by upstream - */ - sr.request_body = self.pool().alloc(std::mem::size_of::()) as *mut _; - - if sr.request_body.is_null() { - return Status::NGX_ERROR; - } - sr.set_header_only(1 as _); Status(r) } diff --git a/src/log.rs b/src/log.rs index 37d4ac4..7ad7626 100644 --- a/src/log.rs +++ b/src/log.rs @@ -27,3 +27,18 @@ macro_rules! ngx_log_debug_http { $crate::ngx_log_debug!(log, $($arg)*); } } + +#[macro_export] +macro_rules! ngx_log_debug_mask { + ( $mask:expr, $log:expr, $($arg:tt)* ) => { + let log_level = unsafe { (*$log).log_level }; + if log_level & $mask as usize != 0 { + let level = $mask as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + } +}