From 5a5ff5e81db9e9457775fa6872de0f8c0cbc7811 Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Mon, 24 Jul 2023 16:32:14 -0700 Subject: [PATCH 01/10] doc: updated docker command to use named container --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fa677e4..c5a12bb 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,12 @@ We provide a multistage [Dockerfile](Dockerfile): docker buildx build --build-arg NGX_VERSION=1.23.3 -t ngx-rust . # start NGINX using [curl](examples/curl.conf) module example: - docker run --rm -d -p 8000:8000 ngx-rust nginx -c examples/curl.conf + docker run --name curl --rm -d -p 8000:8000 ngx-rust nginx -c examples/curl.conf - # test it - you should see 403 Forbidden + # Test it - you should see 403 Forbidden curl http://127.0.0.1:8000 -v -H "user-agent: curl" - - # test it - you should see 404 Not Found + # Test it - you should see 404 Not Found curl http://127.0.0.1:8000 -v -H "user-agent: foo" ## Usage From 57545686bd03ec4458821aabdd2868b406ec3a49 Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Mon, 24 Jul 2023 16:38:36 -0700 Subject: [PATCH 02/10] fix: fixed curl module nginx config example --- examples/curl.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/curl.conf b/examples/curl.conf index 603fc15..c0fdf7e 100644 --- a/examples/curl.conf +++ b/examples/curl.conf @@ -18,14 +18,14 @@ http { listen *:8000; server_name localhost; location / { - root html; + root /usr/share/nginx/html; index index.html index.htm; # libcurl module directive: curl on; } error_page 500 502 503 504 /50x.html; location = /50x.html { - root html; + root /usr/share/nginx/html; } } } From 1931ef22736911d61e0e68bf8aeeb7c1b776a5ec Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Mon, 24 Jul 2023 16:38:59 -0700 Subject: [PATCH 03/10] ci: address some clippy warnings --- nginx-sys/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nginx-sys/src/lib.rs b/nginx-sys/src/lib.rs index 9b4fcef..fc7bea7 100644 --- a/nginx-sys/src/lib.rs +++ b/nginx-sys/src/lib.rs @@ -29,6 +29,7 @@ //! ``` //! #![warn(missing_docs)] +#![allow(clippy::needless_doctest_main)] use std::fmt; use std::ptr::copy_nonoverlapping; @@ -66,7 +67,7 @@ pub use bindings::*; /// ```rust /// let pool: *mut ngx_pool_t = ...; // Obtain a pointer to the nginx memory pool /// let data: &str = "example"; // The string to convert -/// let ptr = str_to_uchar(pool, data); +/// let ptr = unsafe { str_to_uchar(pool, data) }; /// ``` pub unsafe fn str_to_uchar(pool: *mut ngx_pool_t, data: &str) -> *mut u_char { let ptr: *mut u_char = ngx_palloc(pool, data.len() as _) as _; @@ -197,6 +198,7 @@ pub unsafe fn add_to_ngx_table( if table.is_null() { return None; } + #[allow(clippy::not_unsafe_ptr_arg_deref)] table.as_mut().map(|table| { table.hash = 1; table.key.len = key.len() as _; From 6ef3eb495317433f0145b70caeca5bb453b5eccb Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Wed, 9 Aug 2023 07:18:11 -0700 Subject: [PATCH 04/10] feat: add cmp_ignore_case_utf8 and as_str for NgxStr --- src/core/string.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/core/string.rs b/src/core/string.rs index 55ee5a7..f14b5f5 100644 --- a/src/core/string.rs +++ b/src/core/string.rs @@ -1,6 +1,7 @@ use crate::ffi::*; use std::borrow::Cow; +use std::cmp::Ordering; use std::slice; use std::str::{self, Utf8Error}; @@ -56,6 +57,15 @@ impl NgxStr { &self.0 } + pub fn as_str(&self) -> &str { + // Safety: Converting the raw data to a string slice is unsafe, but as long + // as the lifetime of NgxStr is properly managed, this should be safe. + unsafe { + // let slice = std::slice::from_raw_parts(self.0.data, self.0.len); + std::str::from_utf8_unchecked(&self.0) + } + } + /// Yields a `&str` slice if the [`NgxStr`] contains valid UTF-8. pub fn to_str(&self) -> Result<&str, Utf8Error> { str::from_utf8(self.as_bytes()) @@ -72,6 +82,13 @@ impl NgxStr { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + // Compare the NgxStr with another NgxStr using case-insensitive UTF-8 comparison. + pub fn cmp_ignore_case_utf8(&self, other: &str) -> Ordering { + let self_slice = self.as_str().to_lowercase(); + let other_slice = other.to_lowercase(); + self_slice.cmp(&other_slice) + } } impl From<&[u8]> for &NgxStr { From 413aca45a51e306fb12144c87b90356bcf22a64c Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Mon, 21 Aug 2023 09:35:16 -0700 Subject: [PATCH 05/10] feat: is_internal method for request --- examples/curl.rs | 30 +++++++++++++++++++++--------- src/http/request.rs | 4 ++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/examples/curl.rs b/examples/curl.rs index d0bbb43..49ae183 100644 --- a/examples/curl.rs +++ b/examples/curl.rs @@ -4,9 +4,9 @@ use ngx::ffi::{ ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_MODULE, NGX_RS_HTTP_LOC_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, }; -use ngx::http::MergeConfigError; -use ngx::{core, core::Status, http, http::HTTPModule}; -use ngx::{http_request_handler, ngx_log_debug_http, ngx_modules, ngx_null_command, ngx_string}; +use ngx::{core, core::Status, http, http::HTTPModule, http::MergeConfigError}; +use ngx::{http_request_handler, ngx_log_debug, ngx_modules, ngx_null_command, ngx_string}; + use std::os::raw::{c_char, c_void}; struct Module; @@ -104,18 +104,30 @@ impl http::Merge for ModuleConfig { } http_request_handler!(curl_access_handler, |request: &mut http::Request| { + // we can check if a request is internal and disable handler + let log = request.log(); + ngx_log_debug!(log, "is internal: {}", request.is_internal()); + + if request.is_internal() { + return core::Status::NGX_DECLINED; + } + // get location config let co = unsafe { request.get_module_loc_conf::(&ngx_http_curl_module) }; let co = co.expect("module config is none"); - ngx_log_debug_http!(request, "curl module enabled: {}", co.enable); - + // check if module is enabled based on the location config + ngx_log_debug!(log, "curl module enabled: {}", co.enable); + if request.is_internal() { + return core::Status::NGX_DECLINED; + } match co.enable { true => { - if request.user_agent().as_bytes().starts_with(b"curl") { - http::HTTPStatus::FORBIDDEN.into() - } else { - core::Status::NGX_DECLINED + if let Some(ua) = request.user_agent() { + if ua.as_bytes().starts_with(b"curl") { + return http::HTTPStatus::FORBIDDEN.into(); + } } + return core::Status::NGX_DECLINED; } false => core::Status::NGX_DECLINED, } diff --git a/src/http/request.rs b/src/http/request.rs index f4efab8..9f2d276 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -109,6 +109,10 @@ impl Request { std::ptr::eq(self, main) } + pub fn is_internal(&self) -> bool { + self.0.internal() != 0 + } + /// Request pool. pub fn pool(&self) -> Pool { // SAFETY: This request is allocated from `pool`, thus must be a valid pool. From 3490d39da47a3264c06e7a30108fe105174c8d64 Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Mon, 21 Aug 2023 11:43:00 -0700 Subject: [PATCH 06/10] feat!: owned header iterator --- examples/awssig.rs | 23 +++++--- src/http/headers.rs | 137 ++++++++++++++++++++++++++++++++++++++++++++ src/http/mod.rs | 2 + src/http/request.rs | 71 ++--------------------- 4 files changed, 159 insertions(+), 74 deletions(-) create mode 100644 src/http/headers.rs diff --git a/examples/awssig.rs b/examples/awssig.rs index e5aa70d..3c866cd 100644 --- a/examples/awssig.rs +++ b/examples/awssig.rs @@ -302,12 +302,13 @@ http_request_handler!(awssigv4_header_handler, |request: &mut Request| { // Iterate over requests headers_in and copy into HeaderMap // Copy only headers that will be used to sign the request let mut headers = HeaderMap::new(); - for (name, value) in request.headers_in_iterator() { - match name.to_lowercase().as_str() { - "host" => { - headers.insert(http::header::HOST, value.parse().unwrap()); + for h in request.headers_in_iterator() { + match h.lowercase_key() { + Some("host") => { + headers.insert(http::header::HOST, h.value().parse().unwrap()); } - &_ => {} + Some(&_) => {} + None => {} }; } headers.insert("X-Amz-Date", datetime_now.parse().unwrap()); @@ -334,11 +335,15 @@ http_request_handler!(awssigv4_header_handler, |request: &mut Request| { request.add_header_in("X-Amz-Date", datetime_now.as_str()); // done signing, let's print values we have in request.headers_out, request.headers_in - for (name, value) in request.headers_out_iterator() { - ngx_log_debug_http!(request, "headers_out {}: {}", name, value); + for h in request.headers_out_iterator() { + ngx_log_debug_http!(request, "headers_out {}", h); } - for (name, value) in request.headers_in_iterator() { - ngx_log_debug_http!(request, "headers_in {}: {}", name, value); + for mut h in request.headers_in_iterator() { + ngx_log_debug_http!(request, "headers_in {}", h); + if h.lowercase_key() == Some("foo") { + let new_val = "value + ".to_owned() + h.value(); + h.set_value(&new_val); + } } core::Status::NGX_OK diff --git a/src/http/headers.rs b/src/http/headers.rs new file mode 100644 index 0000000..9fa9aef --- /dev/null +++ b/src/http/headers.rs @@ -0,0 +1,137 @@ +use crate::ffi::{ + ngx_http_headers_in_t, ngx_http_headers_out_t, ngx_list_part_t, ngx_list_t, ngx_pool_t, ngx_table_elt_t, + ngx_uint_t, u_char, +}; +use crate::ngx_string; +use std::ffi::CString; +use std::fmt; +use std::ptr; + +pub struct Header(*mut ngx_table_elt_t, *mut ngx_pool_t); + +impl Header { + pub fn set_value(&mut self, value: &str) { + // we can use pool to allocate and then copy + unsafe { self.0.as_mut() }.map(|table| { + table.value.len = value.len() as _; + table.value.data = crate::ffi::str_to_uchar(self.1, value); + }); + + // Alternative way is using CString and transfer ownership. + // unsafe { self.0.as_mut() }.map(|table| { + // let c_value = CString::new(value).unwrap(); + // // table.value.len = c_value.as_bytes().len(); + // table.value.len = c_value.len(); + // table.value.data = c_value.into_raw() as *mut u_char; + // }); + } + + pub fn set_key(&mut self, key: &str) { + // we can use pool to allocate and then copy + unsafe { self.0.as_mut() }.map(|table| { + table.key.len = key.len() as _; + table.key.data = crate::ffi::str_to_uchar(self.1, key); + table.lowcase_key = crate::ffi::str_to_uchar(self.1, key.to_lowercase().as_str()); + }); + } + + pub fn set_hash(&mut self, hash: ngx_uint_t) { + unsafe { self.0.as_mut() }.map(|table| { + table.hash = hash; + }); + } + + pub fn value(&self) -> &str { + unsafe { + std::str::from_utf8(std::slice::from_raw_parts( + unsafe { *self.0 }.value.data, + unsafe { *self.0 }.value.len as usize, + )) + .unwrap() + } + } + + pub fn key(&self) -> &str { + unsafe { + std::str::from_utf8(std::slice::from_raw_parts( + (*self.0).key.data, + (*self.0).key.len as usize, + )) + .unwrap() + } + } + pub fn lowercase_key(&self) -> Option<&str> { + unsafe { + let byte_slice = std::slice::from_raw_parts_mut(unsafe { *self.0 }.lowcase_key, unsafe { *self.0 }.key.len); + if let Ok(utf8_str) = std::str::from_utf8(byte_slice) { + Some(utf8_str) + } else { + // not a valid UTF-8, return None + None + } + } + } + + pub fn hash(&self) -> ngx_uint_t { + unsafe { *self.0 }.hash + } +} + +impl fmt::Display for Header { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.key(), self.value()) + } +} + +/// Iterator for `ngx_list_t` types. +/// +/// Implementes the std::iter::Iterator trait. +pub struct HeadersIterator { + done: bool, + part: *const ngx_list_part_t, + h: *mut ngx_table_elt_t, + i: ngx_uint_t, + pool: *mut ngx_pool_t, +} + +impl HeadersIterator { + pub unsafe fn new(list: *const ngx_list_t) -> Self { + let part: *const ngx_list_part_t = &(*list).part; + let p = (*list).pool; + Self { + done: false, + part, + h: (*part).elts as *mut ngx_table_elt_t, + i: 0, + pool: p, + } + } +} + +impl Iterator for HeadersIterator { + type Item = Header; + + fn next(&mut self) -> Option { + unsafe { + if self.done { + None + } else { + if self.i >= (*self.part).nelts { + if (*self.part).next.is_null() { + self.done = true; + return None; + } + + // loop back + self.part = (*self.part).next; + self.h = (*self.part).elts as *mut ngx_table_elt_t; + self.i = 0; + } + + let header: *mut ngx_table_elt_t = self.h.add(self.i); + self.i += 1; + Some(Header(header, self.pool)) + } + } + } +} diff --git a/src/http/mod.rs b/src/http/mod.rs index 024ad1f..51db96e 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,10 +1,12 @@ mod conf; +mod headers; mod module; mod request; mod status; mod upstream; pub use conf::*; +pub use headers::*; pub use module::*; pub use request::*; pub use status::*; diff --git a/src/http/request.rs b/src/http/request.rs index 9f2d276..6b0091e 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -8,6 +8,8 @@ use std::os::raw::c_void; use std::error::Error; use std::str::FromStr; +use super::HeadersIterator; + /// Define a static request handler. /// /// Handlers are expected to take a single [`Request`] argument and return a [`Status`]. @@ -362,14 +364,14 @@ impl Request { /// Iterate over headers_in /// each header item is (String, String) (copied) - pub fn headers_in_iterator(&self) -> NgxListIterator { - unsafe { list_iterator(&self.0.headers_in.headers) } + pub fn headers_in_iterator(&self) -> HeadersIterator { + unsafe { HeadersIterator::new(&self.0.headers_in.headers) } } /// Iterate over headers_out /// each header item is (String, String) (copied) - pub fn headers_out_iterator(&self) -> NgxListIterator { - unsafe { list_iterator(&self.0.headers_out.headers) } + pub fn headers_out_iterator(&self) -> HeadersIterator { + unsafe { HeadersIterator::new(&self.0.headers_out.headers) } } } @@ -383,67 +385,6 @@ impl fmt::Debug for Request { } } -/// Iterator for `ngx_list_t` types. -/// -/// Implementes the std::iter::Iterator trait. -pub struct NgxListIterator { - done: bool, - part: *const ngx_list_part_t, - h: *const ngx_table_elt_t, - i: ngx_uint_t, -} - -// create new http request iterator -/// # Safety -/// -/// The caller has provided a valid `ngx_str_t` which can be dereferenced validly. -pub unsafe fn list_iterator(list: *const ngx_list_t) -> NgxListIterator { - let part: *const ngx_list_part_t = &(*list).part; - - NgxListIterator { - done: false, - part, - h: (*part).elts as *const ngx_table_elt_t, - i: 0, - } -} - -// iterator for ngx_list_t -impl Iterator for NgxListIterator { - // type Item = (&str,&str); - // TODO: try to use str instead of string - // something like pub struct Header(ngx_table_elt_t); - // then header would have key and value - - type Item = (String, String); - - fn next(&mut self) -> Option { - unsafe { - if self.done { - None - } else { - if self.i >= (*self.part).nelts { - if (*self.part).next.is_null() { - self.done = true; - return None; - } - - // loop back - self.part = (*self.part).next; - self.h = (*self.part).elts as *mut ngx_table_elt_t; - self.i = 0; - } - - let header: *const ngx_table_elt_t = self.h.add(self.i); - let header_name: ngx_str_t = (*header).key; - let header_value: ngx_str_t = (*header).value; - self.i += 1; - Some((header_name.to_string(), header_value.to_string())) - } - } - } -} - /// A possible error value when converting `Method` pub struct InvalidMethod { _priv: (), From 8b5e172c59411adcfb869dc242d5034959f1dcc3 Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Wed, 23 Aug 2023 09:30:19 -0700 Subject: [PATCH 07/10] update default zlib version to 1.3 --- nginx-sys/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx-sys/build.rs b/nginx-sys/build.rs index 37536a6..9abbbe9 100644 --- a/nginx-sys/build.rs +++ b/nginx-sys/build.rs @@ -15,7 +15,7 @@ use tar::Archive; use which::which; /// The default version of zlib to use if the `ZLIB_VERSION` environment variable is not present -const ZLIB_DEFAULT_VERSION: &str = "1.2.13"; +const ZLIB_DEFAULT_VERSION: &str = "1.3"; const ZLIB_GPG_SERVER_AND_KEY_ID: (&str, &str) = ("keyserver.ubuntu.com", "783FCD8E58BCAFBA"); const ZLIB_DOWNLOAD_URL_PREFIX: &str = "https://www.zlib.net"; /// The default version of pcre2 to use if the `PCRE2_VERSION` environment variable is not present From 7809674bdc34f8e44f5652b6dedb0c5497c93338 Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Fri, 25 Aug 2023 11:24:01 -0700 Subject: [PATCH 08/10] feat!: support NGX_SRC_DIR and NGX_CONFIGURE_ARGS via env vars Allows providing NGX_SRC_DIR, NGX_CONFIGURE_ARGS via env variables. This enables to run bingen against provided NGINX source code and use non-default arguments for configure script --- README.md | 28 +++-- examples/README.md | 8 +- nginx-sys/build.rs | 243 +++++++++++++++++++++++++++----------------- src/core/string.rs | 37 ++++++- src/http/headers.rs | 59 +++++------ src/http/request.rs | 1 + 6 files changed, 232 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index c5a12bb..3fa0baa 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Project status -This project is still a work in progress and not production ready. +This project is still a work in progress and not production-ready. # Description @@ -14,15 +14,21 @@ dynamic modules completely in Rust. In short, this SDK allows writing NGINX modules using the Rust language. +It contains the following Rust crates: + * [nginx-sys](./nginx-sys) - allows to use ngx_* C functions via FFI when implementing modules. The `-sys` is used to follow a [naming convention](https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages) to link with a C library. + * [ngx](./src) - it an opinionated SDK to make it a bit easer to use [nginx-sys](./nginx-sys) crate. Implements `macro_rules`, provides a way to build a dynamic module without any C code (see `ngx_modules!` macro_rule). + ## Build NGINX modules can be built against a particular version of NGINX. The following environment variables can be used to specify a particular version of NGINX or an NGINX dependency: -* `ZLIB_VERSION` (default 1.2.13) - +* `ZLIB_VERSION` (default 1.3) * `PCRE2_VERSION` (default 10.42) * `OPENSSL_VERSION` (default 3.0.7) * `NGX_VERSION` (default 1.23.3) - NGINX OSS version * `NGX_DEBUG` (default to false)- if set to true, then will compile NGINX `--with-debug` option +* `NGX_SRC_DIR` (default not set) - When the value is set, then use this NGINX source code folder to build bindings from +* `NGX_CONFIGURE_ARGS` (default not set) - When the value is set, then run the NGINX configure script with For example, this is how you would compile the [examples](examples) using a specific version of NGINX and enabling debugging: @@ -30,7 +36,7 @@ debugging: NGX_DEBUG=true NGX_VERSION=1.23.0 cargo build --package=examples --examples --release ``` -To build Linux-only modules, use the "linux" feature: +To build Linux-only modules, use the `linux` feature: ``` cargo build --package=examples --examples --features=linux --release ``` @@ -43,13 +49,13 @@ the SDK. You can start NGINX directly from this directory if you want to test th ### Mac OS dependencies -In order to use the optional GNU make build process on MacOS, you will need to install additional tools. This can be +To use the optional GNU build process on MacOS, you will need to install additional tools. This can be done via [homebrew](https://brew.sh/) with the following command: ``` brew install make openssl grep ``` -Additionally, you may need to set up LLVM and clang. Typically, this is done as follows: +Additionally, you may need to set up LLVM and Clang. Typically, this is done as follows: ``` # make sure xcode tools are installed @@ -64,16 +70,16 @@ See the [Dockerfile](Dockerfile) for dependencies as an example of required pack ### Build example -Example modules are available in [examples](examples) folder. You can use `cargo build --package=examples --examples` to build these examples. After building, you can find the `.so` or `.dylib` in the `target/debug` folder. Add `--features=linux` to build linux specific modules. **NOTE**: adding the "linux" feature on MacOS will cause a build failure. +Example modules are available in [examples](examples) folder. You can use `cargo build --package=examples --examples` to build these examples. After building, you can find the `.so` or `.dylib` in the `target/debug` folder. Add `--features=linux` to build Linux-specific modules. **NOTE**: adding the `linux` feature on MacOS will cause a build failure. -For example (all examples plus linux specific): +For example (all examples plus Linux specific): `cargo build --packages=examples --examples --features=linux` ### Docker We provide a multistage [Dockerfile](Dockerfile): - # build all dynamic modules examples and specify NGINX version to use + # Build all dynamic module examples and specify the NGINX version to use docker buildx build --build-arg NGX_VERSION=1.23.3 -t ngx-rust . # start NGINX using [curl](examples/curl.conf) module example: @@ -123,15 +129,15 @@ http { ``` ## Support -This SDK is currently unstable. Right now, our primary goal is collect feedback and stabilize it be before -offering support. Feel free [contributing](CONTRIBUTING.md) by creating issues, PRs, or github discussions. +This SDK is currently unstable. Right now, our primary goal is to collect feedback and stabilize it +before offering support. Feel free to [contribute](CONTRIBUTING.md) by creating issues, PRs, or GitHub discussions. Currently, the only supported platforms are: * Darwin (Mac OSX) * Linux platform ## Roadmap -If you have ideas for releases in the future, please suggest them in the github discussions. +If you have ideas for releases in the future, please suggest them in the GitHub discussions. ## Contributing diff --git a/examples/README.md b/examples/README.md index c0eb5b5..0d8fa5d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,9 +11,9 @@ # Examples -This crate provides a couple of example using [ngx](https://crates.io/crates/ngx) crate: +This crate provides a couple of examples using [ngx](https://crates.io/crates/ngx)](https://crates.io/crates/ngx) crate: -- [awssig.rs](./awssig.rs) - An example of NGINX dynamic module that can sign GET request using AWS Signature v4. +- [awssig.rs](./awssig.rs) - An example of an NGINX dynamic module that can sign GET requests using AWS Signature v4. - [curl](./curl.rs) - An example of the Access Phase NGINX dynamic module that blocks HTTP requests if `user-agent` header starts with `curl`. - [httporigdst](./httporigdst.rs) - A dynamic module recovers the original IP address and port number of the destination packet. - [upstream](./upstream.rs) - A dynamic module demonstrating the setup code to write an upstream filter or load balancer. @@ -29,7 +29,7 @@ cargo build --package=examples --examples This module demonstrates how to create a minimal dynamic module with `http_request_handler`, that checks for User-Agent headers and returns status code 403 if UA starts with `curl`, if a module is disabled then uses `core::Status::NGX_DECLINED` to indicate the operation is rejected, for example, because it is disabled in the configuration (`curl off`). Additionally, it demonstrates how to write a defective parser. -An example of nginx configuration file that uses that module can be found at [curl.conf](./curl.conf). +An example of an Nginx configuration file that uses that module can be found at [curl.conf](./curl.conf). How to build and run in a [Docker](../Dockerfile) container curl example: ``` @@ -51,7 +51,7 @@ curl http://127.0.0.1:8000 -v -H "user-agent: foo" This module uses [NGX_HTTP_PRECONTENT_PHASE](https://nginx.org/en/docs/dev/development_guide.html#http_phases) and provides examples, of how to use external dependency and manipulate HTTP headers before sending client requests upstream. -An example of nginx configuration file that uses that module can be found at [awssig.conf](./awssig.conf). +An example of an Nginx configuration file that uses that module can be found at [awssig.conf](./awssig.conf). ## HTTPORIGDST - NGINX Destination IP recovery module for HTTP diff --git a/nginx-sys/build.rs b/nginx-sys/build.rs index 9abbbe9..72d4d4c 100644 --- a/nginx-sys/build.rs +++ b/nginx-sys/build.rs @@ -5,9 +5,9 @@ use duct::cmd; use flate2::read::GzDecoder; use std::error::Error as StdError; use std::ffi::OsString; -use std::fs::{read_to_string, File}; +use std::fs::File; +use std::io::Error as IoError; use std::io::ErrorKind::NotFound; -use std::io::{Error as IoError, Write}; use std::path::{Path, PathBuf}; use std::process::Output; use std::{env, thread}; @@ -39,14 +39,13 @@ const NGX_DEFAULT_VERSION: &str = "1.23.3"; const NGX_GPG_SERVER_AND_KEY_ID: (&str, &str) = ("keyserver.ubuntu.com", "A0EA981B66B0D967"); const NGX_DOWNLOAD_URL_PREFIX: &str = "https://nginx.org/download"; /// If you are adding another dependency, you will need to add the server/public key tuple below. -const ALL_SERVERS_AND_PUBLIC_KEY_IDS: [(&str, &str); 4] = [ +const ALL_DEVPS_SERVERS_AND_PUBLIC_KEY_IDS: [(&str, &str); 3] = [ ZLIB_GPG_SERVER_AND_KEY_ID, PCRE2_GPG_SERVER_AND_KEY_ID, OPENSSL_GPG_SERVER_AND_KEY_IDS, - NGX_GPG_SERVER_AND_KEY_ID, ]; -/// List of configure switches specifying the modules to build nginx with -const NGX_BASE_MODULES: [&str; 20] = [ +/// List of configure switches specifying the modules to build NGINX with +const NGX_BASE_MODULES: [&str; 19] = [ "--with-compat", "--with-http_addition_module", "--with-http_auth_request_module", @@ -57,7 +56,6 @@ const NGX_BASE_MODULES: [&str; 20] = [ "--with-http_realip_module", "--with-http_secure_link_module", "--with-http_slice_module", - "--with-http_slice_module", "--with-http_ssl_module", "--with-http_stub_status_module", "--with-http_sub_module", @@ -69,18 +67,22 @@ const NGX_BASE_MODULES: [&str; 20] = [ "--with-threads", ]; /// Additional configuration flags to use when building on Linux. -const NGX_LINUX_ADDITIONAL_OPTS: [&str; 3] = [ +const NGX_LINUX_ADDITIONAL_OPTS: [&str; 1] = [ "--with-file-aio", - "--with-cc-opt=-g -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC", - "--with-ld-opt=-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie", + // TODO: do we really need, ngx-rust is not intended to compile nginx, we need only header files + // "--with-cc-opt='-g,-fstack-protector-strong,-Wformat,-Werror=format-security,-Wp,-D_FORTIFY_SOURCE=2,-fPIC'", + // "--with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,--as-needed,-pie'", ]; -const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 9] = [ +/// List of env vars that trigger builds.rs to re-run (rerun-if-env-changed) +const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 11] = [ "DEBUG", "OUT_DIR", "ZLIB_VERSION", "PCRE2_VERSION", "OPENSSL_VERSION", "NGX_VERSION", + "NGX_SRC_DIR", + "NGX_CONFIGURE_ARGS", "CARGO_CFG_TARGET_OS", "CARGO_MANIFEST_DIR", "CARGO_TARGET_TMPDIR", @@ -89,14 +91,14 @@ const ENV_VARS_TRIGGERING_RECOMPILE: [&str; 9] = [ /// Function invoked when `cargo build` is executed. /// This function will download NGINX and all supporting dependencies, verify their integrity, /// extract them, execute autoconf `configure` for NGINX, compile NGINX and finally install -/// NGINX in a subdirectory with the project. +/// NGINX in a subdirectory with the project or use provided NGNINX source path fn main() -> Result<(), Box> { - // Create .cache directory - let cache_dir = make_cache_dir()?; - // Import GPG keys used to verify dependency tarballs - import_gpg_keys(&cache_dir)?; - // Configure and Compile NGINX - let (_nginx_install_dir, nginx_src_dir) = compile_nginx()?; + let src_dir = nginx_src_dir()?; + + configure_nginx(&src_dir)?; + + println!("cargo:rerun-if-changed={}/objs/Makefile", src_dir.display()); + // Hint cargo to rebuild if any of the these environment variables values change // because they will trigger a recompilation of NGINX with different parameters for var in ENV_VARS_TRIGGERING_RECOMPILE { @@ -105,7 +107,7 @@ fn main() -> Result<(), Box> { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=wrapper.h"); // Read autoconf generated makefile for NGINX and generate Rust bindings based on its includes - generate_binding(nginx_src_dir); + generate_binding(src_dir); Ok(()) } @@ -117,12 +119,16 @@ fn generate_binding(nginx_source_dir: PathBuf) { .map(|path| format!("-I{}", path.to_string_lossy())) .collect(); + println!("bindgen clang_args: {:?}", clang_args); let bindings = bindgen::Builder::default() // Bindings will not compile on Linux without block listing this item // It is worth investigating why this is .blocklist_item("IPPORT_RESERVED") // The input header we would like to generate bindings for. .header("wrapper.h") + .allowlist_type("ngx_.*") + .allowlist_function("ngx_.*") + .allowlist_var("ngx_.*|NGX_.*|nginx_.*") .clang_args(clang_args) .layout_tests(false) .generate() @@ -190,12 +196,11 @@ fn nginx_archive_url() -> String { /// Returns a list of tuples containing the URL to a tarball archive and the GPG signature used /// to validate the integrity of the tarball. -fn all_archives() -> Vec<(String, String)> { +fn all_dep_archives() -> Vec<(String, String)> { vec![ (zlib_archive_url(), format!("{}.asc", zlib_archive_url())), (pcre2_archive_url(), format!("{}.sig", pcre2_archive_url())), (openssl_archive_url(), format!("{}.asc", openssl_archive_url())), - (nginx_archive_url(), format!("{}.asc", nginx_archive_url())), ] } @@ -211,6 +216,39 @@ fn source_output_dir(cache_dir: &Path) -> PathBuf { .join(format!("{}-{}", env::consts::OS, env::consts::ARCH)) }) } +/// Returns path to NGINX source code +/// If env NGX_SRC_DIR var is set use it, otherwise download NGINX OSS from the Internet +fn nginx_src_dir() -> Result> { + let nginx_src_dir = match env::var("NGX_SRC_DIR") { + Err(_) => { + // Create .cache directory + let cache_dir = make_cache_dir()?; + // Import GPG keys used to verify dependency tarballs + import_gpg_keys(&cache_dir, &[NGX_GPG_SERVER_AND_KEY_ID; 1])?; + let extract_output_base_dir = source_output_dir(&cache_dir); + if !extract_output_base_dir.exists() { + std::fs::create_dir_all(&extract_output_base_dir)?; + } + // download nginx from the Internet and extract it to the cache folder + let archive_url = nginx_archive_url(); + let signature_url = format!("{}.asc", archive_url); + let archive_path = get_archive(&cache_dir, &archive_url, &signature_url)?; + let (_, output_dir) = extract_archive(&archive_path, &extract_output_base_dir)?; + output_dir + } + Ok(v) => PathBuf::from(v), + }; + Ok(nginx_src_dir) +} + +/// Returns Path to the dependency or panics is not found +fn find_dependency_path<'a>(sources: &'a [(String, PathBuf)], name: &str) -> &'a Path { + sources + .iter() + .find(|(n, _)| n == name) + .map(|(_, p)| p.as_path()) + .unwrap_or_else(|| panic!("Unable to find dependency [{name}] path")) +} #[allow(clippy::ptr_arg)] /// Returns the path to install NGINX to @@ -220,9 +258,9 @@ fn nginx_install_dir(base_dir: &PathBuf) -> PathBuf { base_dir.join("nginx").join(nginx_version).join(platform) } -/// Imports all of the required GPG keys into the `.cache/.gnupu` directory in order to +/// Imports GPG keys into the `.cache/.gnupu` directory in order to /// validate the integrity of the downloaded tarballs. -fn import_gpg_keys(cache_dir: &Path) -> Result<(), Box> { +fn import_gpg_keys(cache_dir: &Path, keys: &[(&str, &str)]) -> Result<(), Box> { if let Some(gpg) = gpg_path() { // We do not want to mess with the default gpg data for the running user, // so we store all gpg data with our cache directory. @@ -231,7 +269,7 @@ fn import_gpg_keys(cache_dir: &Path) -> Result<(), Box> { std::fs::create_dir_all(&gnupghome)?; } - let keys_to_import = ALL_SERVERS_AND_PUBLIC_KEY_IDS.iter().filter(|(_, key_id)| { + let keys_to_import = keys.iter().filter(|(_, key_id)| { let key_id_record_file = gnupghome.join(format!("{key_id}.key")); !key_id_record_file.exists() }); @@ -413,14 +451,16 @@ fn extract_archive( Ok((dependency_name, archive_output_dir)) } -/// Extract all of the tarballs into subdirectories within the source base directory. -fn extract_all_archives(cache_dir: &Path) -> Result, Box> { - let archives = all_archives(); +/// Extract dependencies of the tarballs into subdirectories within the source base directory. +fn extract_all_dep_archives(cache_dir: &Path) -> Result, Box> { + let archives = all_dep_archives(); let mut sources = Vec::new(); let extract_output_base_dir = source_output_dir(cache_dir); if !extract_output_base_dir.exists() { std::fs::create_dir_all(&extract_output_base_dir)?; } + + import_gpg_keys(cache_dir, &ALL_DEVPS_SERVERS_AND_PUBLIC_KEY_IDS)?; for (archive_url, signature_url) in archives { let archive_path = get_archive(cache_dir, &archive_url, &signature_url)?; let (name, output_dir) = extract_archive(&archive_path, &extract_output_base_dir)?; @@ -430,57 +470,62 @@ fn extract_all_archives(cache_dir: &Path) -> Result, Box< Ok(sources) } -/// Invoke external processes to run autoconf `configure` to generate a makefile for NGINX and -/// then run `make install`. -fn compile_nginx() -> Result<(PathBuf, PathBuf), Box> { - fn find_dependency_path<'a>(sources: &'a [(String, PathBuf)], name: &str) -> &'a PathBuf { - sources - .iter() - .find(|(n, _)| n == name) - .map(|(_, p)| p) - .unwrap_or_else(|| panic!("Unable to find dependency [{name}] path")) - } - let cache_dir = make_cache_dir()?; - let nginx_install_dir = nginx_install_dir(&cache_dir); - let sources = extract_all_archives(&cache_dir)?; - let zlib_src_dir = find_dependency_path(&sources, "zlib"); - let openssl_src_dir = find_dependency_path(&sources, "openssl"); - let pcre2_src_dir = find_dependency_path(&sources, "pcre2"); - let nginx_src_dir = find_dependency_path(&sources, "nginx"); - let nginx_configure_flags = nginx_configure_flags(&nginx_install_dir, zlib_src_dir, openssl_src_dir, pcre2_src_dir); - let nginx_binary_exists = nginx_install_dir.join("sbin").join("nginx").exists(); - let autoconf_makefile_exists = nginx_src_dir.join("Makefile").exists(); - // We find out how NGINX was configured last time, so that we can compare it to what - // we are going to configure it to this time. If there are no changes, then we can assume - // that we do not need to reconfigure and rebuild NGINX. - let build_info_path = nginx_src_dir.join("last-build-info"); - let current_build_info = build_info(&nginx_configure_flags); - let build_info_no_change = if build_info_path.exists() { - read_to_string(&build_info_path).map_or(false, |s| s == current_build_info) - } else { - false +/// Executes `configure` script and `make build` for NGINX if wasn't ran previously +/// Reads env NGX_CONFIGURE_ARGS var or uses predefnied options and dependecies +fn configure_nginx(nginx_src_dir: &PathBuf) -> Result<(), Box> { + let flags = match env::var("NGX_CONFIGURE_ARGS") { + Ok(v) => v, + Err(_) => { + let cache_dir = make_cache_dir()?; + let nginx_install_dir = nginx_install_dir(&cache_dir); + let sources = extract_all_dep_archives(&cache_dir)?; + let zlib_src_dir = find_dependency_path(&sources, "zlib"); + let openssl_src_dir = find_dependency_path(&sources, "openssl"); + let pcre2_src_dir = find_dependency_path(&sources, "pcre2"); + let args = nginx_configure_flags(&nginx_install_dir, zlib_src_dir, openssl_src_dir, pcre2_src_dir); + args.join(" ") + } }; - println!("NGINX already installed: {nginx_binary_exists}"); + let autoconf_makefile_exists = autoconf_exists(nginx_src_dir); + // NGINX binary file is in objs folder after `make build` + let nginx_binary_exists = nginx_src_dir.join("objs").join("nginx").exists(); + println!("NGINX autoconf makefile already created: {autoconf_makefile_exists}"); - println!("NGINX build info changed: {}", !build_info_no_change); - - if !nginx_binary_exists || !autoconf_makefile_exists || !build_info_no_change { - std::fs::create_dir_all(&nginx_install_dir)?; - configure(nginx_configure_flags, nginx_src_dir)?; - make(nginx_src_dir, "install")?; - let mut output = File::create(build_info_path)?; - // Store the configure flags of the last successful build - output.write_all(current_build_info.as_bytes())?; + println!("NGINX source folder: {:?}", nginx_src_dir); + println!("NGINX already built: {nginx_binary_exists}"); + println!("NGINX configure flags: {flags}"); + + // TODO: a problem: NGX_SRC_DIR was provided and user already ran configure/build + // but did not provide NGX_CONFIGURE_ARGS and the flags used are different with our default. + // what do to? + if !autoconf_makefile_exists { + run_configure_nginx(nginx_src_dir, &flags)?; } - Ok((nginx_install_dir, nginx_src_dir.to_owned())) + + // TODO: a problem why we need to ran build - openssl needs to be configured if it is used, + // potentially openssl src path can be whatever NGX_CONFIGURE_ARGS or defaults to the OS + // but when `configure` is ran for nginx it doesn't configure openssl. openssl is configured only when + // running make build. if make build wasn't run then openssl is not configured and rust bingen can + // 'Unable to generate bindings fails like: ClangDiagnostic(".cache/src/macos-x86_64/nginx-1.23.3/src/event/ngx_event_openssl.h:17:10: fatal error: 'openssl/ssl.h' file not found\n")', + // this is because objs/Makefile contains + // `-I/".cache/src/macos-x86_64/openssl-3.0.7/.openssl/include`, where `.openssl/include` is result of configuring openssl + // so let's check and ran make build at least once + if !nginx_binary_exists { + // at least once let's run make + // when NGX_CONFIGURE_ARGS provided don't install it as we hmay not trust this + if env::var("NGX_CONFIGURE_ARGS").is_ok() { + make(nginx_src_dir, "build")?; + } else { + make(nginx_src_dir, "install")?; + } + } + Ok(()) } -/// Returns the options in which NGINX was built with -fn build_info(nginx_configure_flags: &[String]) -> String { - // Flags should contain strings pointing to OS/platform as well as dependency versions, - // so if any of that changes, it can trigger a rebuild - nginx_configure_flags.join(" ") +/// check if nginx was autoconfigured already by checking Makefile +fn autoconf_exists(nginx_src_dir: &Path) -> bool { + nginx_src_dir.join("Makefile").exists() } /// Generate the flags to use with autoconf `configure` for NGINX based on the downloaded @@ -500,14 +545,13 @@ fn nginx_configure_flags( ) } let modules = || -> Vec { - let mut modules = vec![ - format_source_path("--with-zlib", zlib_src_dir), - format_source_path("--with-pcre", pcre2_src_dir), - format_source_path("--with-openssl", openssl_src_dir), - ]; + let mut modules = Vec::new(); for module in NGX_BASE_MODULES { modules.push(module.to_string()); } + modules.push(format_source_path("--with-zlib", zlib_src_dir)); + modules.push(format_source_path("--with-pcre", pcre2_src_dir)); + modules.push(format_source_path("--with-openssl", openssl_src_dir)); modules }; let mut nginx_opts = vec![format_source_path("--prefix", nginx_install_dir)]; @@ -523,28 +567,39 @@ fn nginx_configure_flags( for flag in modules() { nginx_opts.push(flag); } - nginx_opts } /// Run external process invoking autoconf `configure` for NGINX. -fn configure(nginx_configure_flags: Vec, nginx_src_dir: &Path) -> std::io::Result { - let flags = nginx_configure_flags - .iter() - .map(OsString::from) - .collect::>(); - let configure_executable = nginx_src_dir.join("configure"); +fn run_configure_nginx(nginx_src_dir: &Path, flags: &str) -> std::io::Result { + let mut configure_executable = nginx_src_dir.join("auto").join("configure"); if !configure_executable.exists() { - panic!( - "Unable to find NGINX configure script at: {}", - configure_executable.to_string_lossy() - ); + println!("checking NGINX configure on the top level..."); + // in some cases configure is located at the top level (gzip sources download from the nginx.org) + configure_executable = nginx_src_dir.join("configure"); + + if !configure_executable.exists() { + panic!( + "Unable to find NGINX configure script at: {}", + configure_executable.to_string_lossy() + ); + } } println!( - "Running NGINX configure script with flags: {:?}", - nginx_configure_flags.join(" ") + "NGINX configure script was found: {}", + configure_executable.to_string_lossy() ); - duct::cmd(configure_executable, flags) + // FIXME: it might cause an issue incorectly splitting argumens + // if it contains `--with-cc-opt='-g -fstack-protector-strong'` + // then it would split in ["--with-cc-opt='-g", "-fstack-protector-strong"] which is not correct, + // there is no such argument `-fstack-protector-strong` for nginx's configure script + // instead probably best is to use CFLAGS env or other way.. + let args = flags + .split_ascii_whitespace() + .map(OsString::from) + .collect::>(); + + duct::cmd(&configure_executable, args) .dir(nginx_src_dir) .stderr_to_stdout() .run() @@ -560,7 +615,7 @@ fn make(nginx_src_dir: &Path, arg: &str) -> std::io::Result { _ => Err(IoError::new(NotFound, "Unable to find make in path (gmake or make)")), }?; - // Level of concurrency to use when building nginx - cargo nicely provides this information + // Level of concurrency to use when building NGINX - cargo nicely provides this information let num_jobs = match env::var("NUM_JOBS") { Ok(s) => s.parse::().ok(), Err(_) => thread::available_parallelism().ok().map(|n| n.get()), @@ -577,8 +632,8 @@ fn make(nginx_src_dir: &Path, arg: &str) -> std::io::Result { } /// Reads through the makefile generated by autoconf and finds all of the includes -/// used to compile nginx. This is used to generate the correct bindings for the -/// nginx source code. +/// used to compile NGINX. This is used to generate the correct bindings for the +/// NGINX source code. fn parse_includes_from_makefile(nginx_autoconf_makefile_path: &PathBuf) -> Vec { fn extract_include_part(line: &str) -> &str { line.strip_suffix('\\').map_or(line, |s| s.trim()) diff --git a/src/core/string.rs b/src/core/string.rs index f14b5f5..bf40a4b 100644 --- a/src/core/string.rs +++ b/src/core/string.rs @@ -52,11 +52,42 @@ impl NgxStr { slice::from_raw_parts(str.data, str.len).into() } - /// Access the [`NgxStr`] as a byte slice. + /// Converts the [`NgxStr`] to a byte slice. + /// + /// # Examples + /// + /// ``` + /// use ngx::core::NgxStr; + /// use ngx::ngx_string; + /// + /// let ngx_str = unsafe { NgxStr::from_ngx_str(ngx_string!("hello")) }; + /// let bytes = ngx_str.as_bytes(); + /// assert_eq!(bytes, &[104, 101, 108, 108, 111]); // "hello" in ASCII + /// ``` pub fn as_bytes(&self) -> &[u8] { &self.0 } - + /// Converts the Nginx string reference to a Rust string slice (`&str`). + /// + /// # Safety + /// + /// This operation is marked as `unsafe` because it involves converting raw bytes to a + /// string slice. The safety of this operation depends on the validity of the underlying + /// bytes and the proper lifetime management of `NgxStr`. + /// + /// It's important to ensure that the bytes in the `NgxStr` are valid UTF-8 and that the + /// `NgxStr` instance is not used after its underlying data is deallocated. + /// + /// # Examples + /// + /// ``` + /// use ngx::core::NgxStr; + /// use ngx::ngx_string; + /// + /// let ngx_str = unsafe { NgxStr::from_ngx_str(ngx_string!("hello")) }; + /// let rust_str: &str = ngx_str.as_str(); + /// assert_eq!(rust_str, "hello"); + /// ``` pub fn as_str(&self) -> &str { // Safety: Converting the raw data to a string slice is unsafe, but as long // as the lifetime of NgxStr is properly managed, this should be safe. @@ -83,7 +114,7 @@ impl NgxStr { self.0.is_empty() } - // Compare the NgxStr with another NgxStr using case-insensitive UTF-8 comparison. + /// Compare the NgxStr with another &str using case-insensitive UTF-8 comparison. pub fn cmp_ignore_case_utf8(&self, other: &str) -> Ordering { let self_slice = self.as_str().to_lowercase(); let other_slice = other.to_lowercase(); diff --git a/src/http/headers.rs b/src/http/headers.rs index 9fa9aef..c461fda 100644 --- a/src/http/headers.rs +++ b/src/http/headers.rs @@ -1,21 +1,18 @@ -use crate::ffi::{ - ngx_http_headers_in_t, ngx_http_headers_out_t, ngx_list_part_t, ngx_list_t, ngx_pool_t, ngx_table_elt_t, - ngx_uint_t, u_char, -}; -use crate::ngx_string; -use std::ffi::CString; +use crate::ffi::{ngx_list_part_t, ngx_list_t, ngx_pool_t, ngx_table_elt_t, ngx_uint_t}; + use std::fmt; -use std::ptr; +/// Represents an HTTP header. pub struct Header(*mut ngx_table_elt_t, *mut ngx_pool_t); impl Header { + /// Set the header value. pub fn set_value(&mut self, value: &str) { // we can use pool to allocate and then copy - unsafe { self.0.as_mut() }.map(|table| { + if let Some(table) = unsafe { self.0.as_mut() } { table.value.len = value.len() as _; - table.value.data = crate::ffi::str_to_uchar(self.1, value); - }); + table.value.data = unsafe { crate::ffi::str_to_uchar(self.1, value) }; + }; // Alternative way is using CString and transfer ownership. // unsafe { self.0.as_mut() }.map(|table| { @@ -26,43 +23,36 @@ impl Header { // }); } + /// Set the header name. pub fn set_key(&mut self, key: &str) { - // we can use pool to allocate and then copy - unsafe { self.0.as_mut() }.map(|table| { + if let Some(table) = unsafe { self.0.as_mut() } { table.key.len = key.len() as _; - table.key.data = crate::ffi::str_to_uchar(self.1, key); - table.lowcase_key = crate::ffi::str_to_uchar(self.1, key.to_lowercase().as_str()); - }); + table.key.data = unsafe { crate::ffi::str_to_uchar(self.1, key) }; + table.lowcase_key = unsafe { crate::ffi::str_to_uchar(self.1, key.to_lowercase().as_str()) }; + }; } + /// Set hash. 0 means header would be ignored by NGINX pub fn set_hash(&mut self, hash: ngx_uint_t) { - unsafe { self.0.as_mut() }.map(|table| { + if let Some(table) = unsafe { self.0.as_mut() } { table.hash = hash; - }); + } } + /// Returns value of the header. pub fn value(&self) -> &str { - unsafe { - std::str::from_utf8(std::slice::from_raw_parts( - unsafe { *self.0 }.value.data, - unsafe { *self.0 }.value.len as usize, - )) - .unwrap() - } + unsafe { std::str::from_utf8(std::slice::from_raw_parts((*self.0).value.data, (*self.0).value.len)).unwrap() } } + /// Returns name of the header. pub fn key(&self) -> &str { - unsafe { - std::str::from_utf8(std::slice::from_raw_parts( - (*self.0).key.data, - (*self.0).key.len as usize, - )) - .unwrap() - } + unsafe { std::str::from_utf8(std::slice::from_raw_parts((*self.0).key.data, (*self.0).key.len)).unwrap() } } + + /// Returns lowercases header name pub fn lowercase_key(&self) -> Option<&str> { unsafe { - let byte_slice = std::slice::from_raw_parts_mut(unsafe { *self.0 }.lowcase_key, unsafe { *self.0 }.key.len); + let byte_slice = std::slice::from_raw_parts_mut((*self.0).lowcase_key, (*self.0).key.len); if let Ok(utf8_str) = std::str::from_utf8(byte_slice) { Some(utf8_str) } else { @@ -72,6 +62,7 @@ impl Header { } } + /// Returns hash of the header pub fn hash(&self) -> ngx_uint_t { unsafe { *self.0 }.hash } @@ -95,6 +86,10 @@ pub struct HeadersIterator { } impl HeadersIterator { + /// Create a new [`HeadersIterator`] from an [`*const ngx_list_t`] + /// # Safety + /// + /// The caller has provided a valid non-null pointer to a valid `ngx_list_t` pub unsafe fn new(list: *const ngx_list_t) -> Self { let part: *const ngx_list_part_t = &(*list).part; let p = (*list).pool; diff --git a/src/http/request.rs b/src/http/request.rs index 6b0091e..c924e3f 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -111,6 +111,7 @@ impl Request { std::ptr::eq(self, main) } + /// Returns `true` if request is internal pub fn is_internal(&self) -> bool { self.0.internal() != 0 } From cc4a5ba9047b83c3609f3d1e402c8e87b6c9081b Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Fri, 25 Aug 2023 11:26:26 -0700 Subject: [PATCH 09/10] chore: run cargo clean insted of rm -fr target --- GNUmakefile | 1 - 1 file changed, 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index 3089bdd..a01a8d9 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -41,7 +41,6 @@ help: .PHONY: clean clean: clean-cache; $(info $(M) cleaning...) @ ## Cleanup everything - $Q rm -rf $(CURDIR)/target $Q $(CARGO) clean .PHONY: clean-cache From 8ceb8b6990235223aea14967c62bd5db42ed44e5 Mon Sep 17 00:00:00 2001 From: Maxim Ivanitskiy Date: Wed, 23 Aug 2023 20:36:04 -0700 Subject: [PATCH 10/10] ci: Bump nginx-sys to 0.3.0 and ngx to 0.5.0 --- Cargo.lock | 18 ++++++++++++------ Cargo.toml | 4 ++-- nginx-sys/Cargo.toml | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8276a90..6a97cd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,11 +45,11 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bindgen" -version = "0.65.1" +version = "0.66.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" dependencies = [ - "bitflags", + "bitflags 2.4.0", "cexpr", "clang-sys", "lazy_static", @@ -72,6 +72,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "block-buffer" version = "0.9.0" @@ -444,7 +450,7 @@ dependencies = [ [[package]] name = "nginx-sys" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bindgen", "duct", @@ -456,7 +462,7 @@ dependencies = [ [[package]] name = "ngx" -version = "0.4.0-beta" +version = "0.5.0" dependencies = [ "nginx-sys", ] @@ -558,7 +564,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6721749..e13b541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "ngx" -version = "0.4.0-beta" +version = "0.5.0" edition = "2021" autoexamples = false categories = ["api-bindings", "network-programming"] @@ -19,7 +19,7 @@ keywords = ["nginx", "module", "sys"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nginx-sys = { path = "nginx-sys", version = "0.2"} +nginx-sys = { path = "nginx-sys", version = "0.3"} [badges] maintenance = { status = "experimental" } diff --git a/nginx-sys/Cargo.toml b/nginx-sys/Cargo.toml index ec4df4b..e26a8d7 100644 --- a/nginx-sys/Cargo.toml +++ b/nginx-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nginx-sys" -version = "0.2.0" +version = "0.3.0" edition = "2021" categories = ["external-ffi-bindings"] description = "FFI bindings to NGINX" @@ -15,7 +15,7 @@ crate-type = ["staticlib", "rlib"] [dependencies] [build-dependencies] -bindgen = "0.65.0" +bindgen = "0.66.0" which = "4.4.0" duct = "0.13.6" ureq = { version = "2.6.2", features = ["tls"] }