diff options
-rw-r--r-- | Cargo.lock | 18 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | LICENSE | 13 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | corgi.conf | 6 | ||||
-rw-r--r-- | corgi/src/caller.rs | 82 | ||||
-rw-r--r-- | corgi/src/main.rs | 7 | ||||
-rw-r--r-- | parrot_module/Cargo.toml | 10 | ||||
-rw-r--r-- | parrot_module/src/lib.rs | 57 |
9 files changed, 192 insertions, 5 deletions
diff --git a/Cargo.lock b/Cargo.lock index a813a85..1c55731 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "confindent" version = "2.2.1" -source = "git+https://github.com/gennyble/confindent?branch=v2#127f579c284131feb15f5deec45dde57a5f44284" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea618ded77af626818bde0f0802da7c20d47e38e23e37be40f6f807a76079e82" [[package]] name = "corgi" @@ -57,6 +58,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "libloading", "regex-lite", "tokio", ] @@ -199,6 +201,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -238,6 +250,10 @@ name = "parrot" version = "0.1.0" [[package]] +name = "parrot_module" +version = "0.1.0" + +[[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 0924370..8feb4b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["corgi", "parrot"] +members = ["corgi", "parrot", "parrot_module"] resolver = "3" # use this profile like this: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..57618e5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2025 gennyble + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md index d9d7661..3b94588 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,6 @@ Additionally, corgi will set environment variables for the HTTP request headers. They will be uppercased and hyphens replaced with underscores. Any environmental variable may be overridden if it is set in the -configuration file. +configuration file, except the `CONTENT_LENGTH` envar. [rfc]: https://datatracker.ietf.org/doc/html/rfc3875 \ No newline at end of file diff --git a/corgi.conf b/corgi.conf index 207acc7..ff1a416 100644 --- a/corgi.conf +++ b/corgi.conf @@ -1,6 +1,12 @@ Server Port 26744 +Script module-test + Path target/release/libparrot.dylib + Type object + Match + Regex /object + Script git-backend Path /usr/lib/git-core/git-http-backend Match diff --git a/corgi/src/caller.rs b/corgi/src/caller.rs index 1e1d138..b942849 100644 --- a/corgi/src/caller.rs +++ b/corgi/src/caller.rs @@ -1,4 +1,10 @@ -use std::{net::IpAddr, process::Stdio}; +use std::{ + ffi::{self, CString}, + net::IpAddr, + process::Stdio, + ptr, + str::FromStr, +}; use tokio::{io::AsyncWriteExt, process::Command}; @@ -156,3 +162,77 @@ pub struct CgiResponse { /// CGI response body pub body: Option<Vec<u8>>, } + +#[repr(C)] +struct ModuleRequest<'req> { + headers_len: ffi::c_ulong, + headers: &'req [[*const ffi::c_char; 2]], + body_len: ffi::c_ulong, + body: *const u8, +} + +#[repr(C)] +struct ModuleResponse { + status: ffi::c_ushort, + headers_len: ffi::c_ulong, + headers: &'static [[*const ffi::c_char; 2]], + body_len: ffi::c_ulong, + body: *const u8, +} + +type HandleFn = unsafe extern "C" fn(*const ModuleRequest) -> *const ModuleResponse; +type FreeFn = unsafe extern "C" fn(*const ModuleResponse); + +pub async fn call_and_parse_module(script: Script, req: HttpRequest) -> CgiResponse { + let env = req.build_kv(); + + let mut headers_owned = vec![]; + for (k, v) in env { + headers_owned.push([ + CString::from_str(&k).unwrap(), + CString::from_str(&v).unwrap(), + ]); + } + + let headers: Vec<[*const ffi::c_char; 2]> = + headers_owned.iter().map(|kvarr| [kvarr[0].as_ptr(), kvarr[1].as_ptr()]).collect(); + + let modreq = ModuleRequest { + headers_len: headers.len() as u64, + headers: &headers, + body_len: req.body.as_ref().map(|v| v.len()).unwrap_or(0) as u64, + body: req.body.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null()), + }; + + let mut cgi = CgiResponse { + status: 200, + headers: vec![], + body: None, + }; + + unsafe { + let lib = libloading::Library::new(script.filename).unwrap(); + let handle: libloading::Symbol<HandleFn> = lib.get(b"handle").unwrap(); + let free: libloading::Symbol<FreeFn> = lib.get(b"free").unwrap(); + + let response = handle((&modreq) as *const ModuleRequest); + let response_ref = response.as_ref().unwrap(); + + for idx in 0..response_ref.headers_len { + let kvarr = response_ref.headers[idx as usize]; + let k = ffi::CStr::from_ptr(kvarr[0]).to_string_lossy(); + let v = ffi::CStr::from_ptr(kvarr[1]).to_string_lossy(); + cgi.headers.push((k.as_bytes().to_vec(), v.as_bytes().to_vec())); + } + + let maybe_body: Option<Vec<u8>> = response_ref + .body + .as_ref() + .map(|b| std::slice::from_raw_parts(b, response_ref.body_len as usize).to_vec()); + cgi.body = maybe_body; + + free(response); + }; + + cgi +} diff --git a/corgi/src/main.rs b/corgi/src/main.rs index 0338d0e..cd3b67c 100644 --- a/corgi/src/main.rs +++ b/corgi/src/main.rs @@ -227,7 +227,12 @@ impl Svc { }; let start_cgi = Instant::now(); - let cgi_response = caller::call_and_parse_cgi(script.clone(), http_request).await; + let cgi_response = match script.kind { + ScriptKind::Executable => { + caller::call_and_parse_cgi(script.clone(), http_request).await + } + ScriptKind::Object => caller::call_and_parse_module(script.clone(), http_request).await, + }; let cgi_time = start_cgi.elapsed(); let status = StatusCode::from_u16(cgi_response.status).unwrap(); diff --git a/parrot_module/Cargo.toml b/parrot_module/Cargo.toml new file mode 100644 index 0000000..f4ced8e --- /dev/null +++ b/parrot_module/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "parrot_module" +version = "0.1.0" +edition = "2024" + +[lib] +name = "parrot" +crate-type = ["cdylib"] + +[dependencies] diff --git a/parrot_module/src/lib.rs b/parrot_module/src/lib.rs new file mode 100644 index 0000000..148f41d --- /dev/null +++ b/parrot_module/src/lib.rs @@ -0,0 +1,57 @@ +use std::ffi::{self, CStr, CString}; + +#[repr(C)] +struct ModuleRequest<'req> { + headers_len: ffi::c_ulong, + headers: &'req [[*const ffi::c_char; 2]], + body_len: ffi::c_ulong, + body: *const u8, +} + +#[repr(C)] +struct ModuleResponse { + status: ffi::c_ushort, + headers_len: ffi::c_ulong, + headers: &'static [[*const ffi::c_char; 2]], + body_len: ffi::c_ulong, + body: *const u8, +} + +const HEADERS: &'static [[*const ffi::c_char; 2]] = + &[[c"Content-Type".as_ptr(), c"text/plain".as_ptr()]]; + +#[unsafe(no_mangle)] +extern "C" fn handle(req: *const ModuleRequest) -> *const ModuleResponse { + let mut ret = String::new(); + + // unwrap is bad here + let reqref = unsafe { req.as_ref() }.unwrap(); + + for idx in 0..reqref.headers_len { + let kvarr = reqref.headers[idx as usize]; + let k = unsafe { CStr::from_ptr(kvarr[0]) }.to_string_lossy(); + let v = unsafe { CStr::from_ptr(kvarr[1]) }.to_string_lossy(); + ret.push_str(&format!("{k}: {v}\n")); + } + + let body = CString::new(ret).unwrap(); + + let resp = ModuleResponse { + status: 200, + headers_len: 1, + headers: HEADERS, + body_len: body.as_bytes().len() as u64, + body: body.into_raw() as *const u8, + }; + + let boxed = Box::new(resp); + + Box::<ModuleResponse>::into_raw(boxed) +} + +#[unsafe(no_mangle)] +extern "C" fn free(req: *const ModuleResponse) { + // from_raw what we need to here so that these get dropped + let boxed = unsafe { Box::from_raw(req as *mut ModuleResponse) }; + let body = unsafe { CString::from_raw(boxed.body as *mut i8) }; +} |