Use a custom parser for commands
The removes the dependency for `nom` as commands are now parsed with its own tiny parser.
This commit is contained in:
parent
4b9186b10d
commit
4d39488c32
4 changed files with 89 additions and 109 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "webhookey"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
authors = ["finga <webhookey@onders.org>"]
|
||||
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"
|
||||
|
|
173
src/main.rs
173
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<String> {
|
||||
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<String> {
|
||||
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::<Vec<&str>>();
|
||||
|
||||
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<Vec<&str>> {
|
||||
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<String> {
|
||||
let parse: IResult<&str, Vec<&str>> = many0(alt((
|
||||
map_res(
|
||||
delimited(tag("{{"), take_until("}}"), tag("}}")),
|
||||
|param: &str| {
|
||||
let expr = param.trim().split(' ').collect::<Vec<&str>>();
|
||||
|
||||
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<File> {
|
||||
// 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]
|
||||
|
|
|
@ -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")]
|
||||
|
|
Loading…
Reference in a new issue