From 4d39488c320f9f5a6d0585a917d24751d293cb79 Mon Sep 17 00:00:00 2001 From: finga Date: Wed, 17 Nov 2021 13:17:15 +0100 Subject: [PATCH] Use a custom parser for commands The removes the dependency for `nom` as commands are now parsed with its own tiny parser. --- Cargo.lock | 20 +----- Cargo.toml | 3 +- src/main.rs | 173 ++++++++++++++++++++++++------------------------ src/webhooks.rs | 2 - 4 files changed, 89 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96529cd..e7c10da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -738,12 +738,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "mio" version = "0.7.14" @@ -786,17 +780,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "nom" -version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" -dependencies = [ - "memchr", - "minimal-lexical", - "version_check", -] - [[package]] name = "ntapi" version = "0.3.6" @@ -1933,7 +1916,7 @@ dependencies = [ [[package]] name = "webhookey" -version = "0.1.3" +version = "0.1.5" dependencies = [ "anyhow", "clap", @@ -1943,7 +1926,6 @@ dependencies = [ "hmac", "ipnet", "log", - "nom", "regex", "rocket", "run_script", diff --git a/Cargo.toml b/Cargo.toml index 0b63cad..d49ab4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "webhookey" -version = "0.1.4" +version = "0.1.5" authors = ["finga "] edition = "2021" license = "GPL-3.0-or-later" @@ -20,7 +20,6 @@ dirs = "4.0" anyhow = "1.0" log = "0.4" env_logger = "0.9" -nom = "7" hmac = "0.11" sha2 = "0.9" hex = "0.4" diff --git a/src/main.rs b/src/main.rs index d0e7eb8..7b161c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,6 @@ use anyhow::{anyhow, bail, Result}; use clap::Parser; use hmac::{Hmac, Mac, NewMac}; use log::{debug, error, info, trace, warn}; -use nom::{ - branch::alt, - bytes::complete::{tag, take_until}, - combinator::map_res, - multi::many0, - sequence::delimited, - Finish, IResult, -}; use rocket::{ data::{FromData, ToByteUnit}, futures::TryFutureExt, @@ -84,32 +76,71 @@ impl Hook { request: &Request, data: &mut serde_json::Value, ) -> Result { - trace!("Replacing parameters for command of hook `{}`", hook_name); + debug!("Replacing parameters for command of hook `{}`", hook_name); - for parameter in self.get_parameter()? { - let parameter = parameter.trim(); + Hook::replace_parameters(&self.command, request.headers(), data) + } - if let Some(json_value) = data.pointer(parameter) { - *data.pointer_mut(parameter).ok_or_else(|| { - WebhookeyError::InvalidParameterPointer(parameter.to_string()) - })? = serde_json::Value::String(webhooks::get_string(json_value)?); + fn replace_parameters( + input: &str, + headers: &HeaderMap, + data: &serde_json::Value, + ) -> Result { + let mut command = String::new(); + let command_template = &mut input.chars(); + + while let Some(i) = command_template.next() { + if i == '{' { + if let Some('{') = command_template.next() { + let mut token = String::new(); + + while let Some(i) = command_template.next() { + if i == '}' { + if let Some('}') = command_template.next() { + let expr = token.trim().split(' ').collect::>(); + + let replaced = match expr.get(0) { + Some(&"header") => get_header_field( + headers, + expr.get(1).ok_or_else(|| { + anyhow!("Missing parameter for `header` expression") + })?, + )? + .to_string(), + Some(pointer) => webhooks::get_string( + data.pointer(pointer).ok_or_else(|| { + anyhow!( + "Could not find field refered to in parameter `{}`", + pointer + ) + })?, + )?, + None => bail!("Missing expression in variable `{}`", token), + }; + + command.push_str(&replaced); + + trace!("Replace `{}` with: {}", token, replaced); + + break; + } else { + command.push('}'); + command.push(i); + } + } else { + token.push(i); + } + } + } else { + command.push('{'); + command.push(i); + } + } else { + command.push(i); } } - replace_parameters(&self.command, request.headers(), data) - } - - fn get_parameter(&self) -> Result> { - let parse: IResult<&str, Vec<&str>> = many0(alt(( - delimited(tag("{{"), take_until("}}"), tag("}}")), - take_until("{{"), - )))(&self.command); - - let (_last, result) = parse - .finish() - .map_err(|e| anyhow!("Could not get parameters from command: {}", e))?; - - Ok(result) + Ok(command) } } @@ -214,55 +245,12 @@ fn validate_request(secret: &str, signature: &str, data: &[u8]) -> Result<()> { mac.verify(&raw_signature).map_err(|e| anyhow!("{}", e)) } -fn get_header_field<'a>(headers: &'a HeaderMap, param: &[&str]) -> Result<&'a str> { +fn get_header_field<'a>(headers: &'a HeaderMap, param: &str) -> Result<&'a str> { headers - .get_one( - param - .get(1) - .ok_or_else(|| anyhow!("Missing parameter for `header` expression"))?, - ) + .get_one(param) .ok_or_else(|| anyhow!("Could not extract event parameter from header")) } -fn get_value_from_pointer<'a>(data: &'a serde_json::Value, pointer: &'a str) -> Result<&'a str> { - let value = data - .pointer(pointer) - .ok_or_else(|| anyhow!("Could not get field from pointer {}", pointer))?; - - value - .as_str() - .ok_or_else(|| anyhow!("Could not convert value `{}` to string", value)) -} - -fn replace_parameters( - input: &str, - headers: &HeaderMap, - data: &serde_json::Value, -) -> Result { - let parse: IResult<&str, Vec<&str>> = many0(alt(( - map_res( - delimited(tag("{{"), take_until("}}"), tag("}}")), - |param: &str| { - let expr = param.trim().split(' ').collect::>(); - - match expr.get(0) { - Some(&"header") => get_header_field(headers, &expr), - Some(pointer) => get_value_from_pointer(data, pointer), - None => bail!("Missing expression in `{}`", input), - } - }, - ), - take_until("{{"), - )))(input); - - let (last, mut result) = parse - .finish() - .map_err(|e| anyhow!("Could not parse command: {}", e))?; - result.push(last); - - Ok(result.join("")) -} - fn get_config() -> Result { // Look for config in CWD.. if let Ok(config) = File::open("config.yml") { @@ -583,37 +571,40 @@ mod tests { headers.add_raw("X-Gitea-Event", "something"); assert_eq!( - replace_parameters("command", &headers, &serde_json::Value::Null).unwrap(), + Hook::replace_parameters("command", &headers, &serde_json::Value::Null).unwrap(), "command" ); assert_eq!( - replace_parameters(" command", &headers, &serde_json::Value::Null).unwrap(), + Hook::replace_parameters(" command", &headers, &serde_json::Value::Null).unwrap(), " command" ); assert_eq!( - replace_parameters("command ", &headers, &serde_json::Value::Null).unwrap(), + Hook::replace_parameters("command ", &headers, &serde_json::Value::Null).unwrap(), "command " ); assert_eq!( - replace_parameters(" command ", &headers, &serde_json::Value::Null).unwrap(), + Hook::replace_parameters(" command ", &headers, &serde_json::Value::Null) + .unwrap(), " command " ); assert_eq!( - replace_parameters("command command ", &headers, &serde_json::Value::Null).unwrap(), + Hook::replace_parameters("command command ", &headers, &serde_json::Value::Null) + .unwrap(), "command command " ); assert_eq!( - replace_parameters("{{ /foo }} command", &headers, &json!({ "foo": "bar" })).unwrap(), + Hook::replace_parameters("{{ /foo }} command", &headers, &json!({ "foo": "bar" })) + .unwrap(), "bar command" ); assert_eq!( - replace_parameters( + Hook::replace_parameters( " command {{ /foo }} ", &headers, &json!({ "foo": "bar" }) @@ -623,7 +614,7 @@ mod tests { ); assert_eq!( - replace_parameters( + Hook::replace_parameters( "{{ /foo }} command{{/field1/foo}}", &headers, &json!({ "foo": "bar", "field1": { "foo": "baz" } }) @@ -633,7 +624,7 @@ mod tests { ); assert_eq!( - replace_parameters( + Hook::replace_parameters( " command {{ /foo }} ", &headers, &json!({ "foo": "bar" }) @@ -643,7 +634,7 @@ mod tests { ); assert_eq!( - replace_parameters( + Hook::replace_parameters( " {{ /field1/foo }} command", &headers, &json!({ "field1": { "foo": "bar" } }) @@ -653,7 +644,7 @@ mod tests { ); assert_eq!( - replace_parameters( + Hook::replace_parameters( " {{ header X-Gitea-Event }} command", &headers, &json!({ "field1": { "foo": "bar" } }) @@ -663,7 +654,7 @@ mod tests { ); assert_eq!( - replace_parameters( + Hook::replace_parameters( " {{ header X-Gitea-Event }} {{ /field1/foo }} command", &headers, &json!({ "field1": { "foo": "bar" } }) @@ -671,6 +662,16 @@ mod tests { .unwrap(), " something bar command" ); + + assert_eq!( + Hook::replace_parameters( + " {{ header X-Gitea-Event }} {{ /field1/foo }} {{ /field1/bar }} {{ /field2/foo }} --command{{ /cmd }}", + &headers, + &json!({ "field1": { "foo": "bar", "bar": "baz" }, "field2": { "foo": "qux" }, "cmd": " else"}) + ) + .unwrap(), + " something bar baz qux --command else" + ); } #[rocket::async_test] diff --git a/src/webhooks.rs b/src/webhooks.rs index 85a52d1..efd9fb6 100644 --- a/src/webhooks.rs +++ b/src/webhooks.rs @@ -14,8 +14,6 @@ pub enum WebhookeyError { Unauthorized(IpAddr), #[error("Unmatched hook from `{0}`")] UnmatchedHook(IpAddr), - #[error("Could not find field refered to in parameter `{0}`")] - InvalidParameterPointer(String), #[error("Could not evaluate filter request")] InvalidFilter, #[error("IO Error")]