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
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…
Add table
Add a link
Reference in a new issue