Add JSON parsing from executables
This commit is contained in:
parent
b1cdce7c85
commit
ce21a63f16
5 changed files with 52 additions and 15 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -431,6 +431,7 @@ dependencies = [
|
|||
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_yaml 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simple_logger 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -15,6 +15,7 @@ actix-rt = "1.1"
|
|||
dirs = "3.0"
|
||||
serde = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "1.0"
|
||||
handlebars = "3.5"
|
||||
hotwatch = "0.4"
|
||||
percent-encoding = "2.1"
|
||||
|
|
|
@ -17,11 +17,15 @@ default_route: "g"
|
|||
# defined route is used.
|
||||
#
|
||||
# You may provide an (absolute, recommended) path to an executable file to out-
|
||||
# source route resolution to a program. The program will receive one argument
|
||||
# only, which is the entire string provided from the user after matching the
|
||||
# route. It is up to the out-sourced program to parse the arguments and to
|
||||
# interpret those arguments. These programs should print one line to standard
|
||||
# out, which should be a fully resolved URL to lead the user to.
|
||||
# source route resolution to a program. The program will receive the arguments
|
||||
# as space-separated words, without any shell parsing.
|
||||
#
|
||||
# These programs must return a JSON object with either one of the following
|
||||
# key-value pairs:
|
||||
# - "redirect": "some-path-to-redirect-to.com"
|
||||
# - "body": The actual body to return.
|
||||
# For example, to return a page that only prints out `3`, the function should
|
||||
# return `{"redirect": "3"}`.
|
||||
#
|
||||
# These programs must be developed defensively, as they accept arbitrary user
|
||||
# input. Improper handling of user input can easily lead to anywhere from simple
|
||||
|
|
|
@ -12,6 +12,7 @@ pub enum BunBunError {
|
|||
InvalidConfigPath(std::path::PathBuf, std::io::Error),
|
||||
ConfigTooLarge(u64),
|
||||
ZeroByteConfig,
|
||||
JsonParse(serde_json::Error),
|
||||
}
|
||||
|
||||
impl Error for BunBunError {}
|
||||
|
@ -29,7 +30,8 @@ impl fmt::Display for BunBunError {
|
|||
write!(f, "Failed to access {:?}: {}", path, reason)
|
||||
}
|
||||
Self::ConfigTooLarge(size) => write!(f, "The config file was too large ({} bytes)! Pass in --large-config to bypass this check.", size),
|
||||
Self::ZeroByteConfig => write!(f, "The config provided reported a size of 0 bytes. Please check your config path!")
|
||||
Self::ZeroByteConfig => write!(f, "The config provided reported a size of 0 bytes. Please check your config path!"),
|
||||
Self::JsonParse(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,3 +52,4 @@ from_error!(std::io::Error, Io);
|
|||
from_error!(serde_yaml::Error, Parse);
|
||||
from_error!(hotwatch::Error, Watch);
|
||||
from_error!(log::SetLoggerError, LoggerInit);
|
||||
from_error!(serde_json::Error, JsonParse);
|
||||
|
|
|
@ -101,18 +101,18 @@ pub async fn hop(
|
|||
route_type: RouteType::External,
|
||||
path,
|
||||
..
|
||||
} => Ok(path.to_owned().into_bytes()),
|
||||
} => Ok(HopAction::Redirect(path.clone())),
|
||||
};
|
||||
|
||||
match resolved_template {
|
||||
Ok(path) => HttpResponse::Found()
|
||||
Ok(HopAction::Redirect(path)) => HttpResponse::Found()
|
||||
.header(
|
||||
header::LOCATION,
|
||||
req
|
||||
.app_data::<Handlebars>()
|
||||
.unwrap()
|
||||
.render_template(
|
||||
std::str::from_utf8(&path).unwrap(),
|
||||
std::str::from_utf8(path.as_bytes()).unwrap(),
|
||||
&template_args::query(
|
||||
utf8_percent_encode(&args, FRAGMENT_ENCODE_SET).to_string(),
|
||||
),
|
||||
|
@ -120,6 +120,7 @@ pub async fn hop(
|
|||
.unwrap(),
|
||||
)
|
||||
.finish(),
|
||||
Ok(HopAction::Body(body)) => HttpResponse::Ok().body(body),
|
||||
Err(e) => {
|
||||
error!("Failed to redirect user for {}: {}", path, e);
|
||||
HttpResponse::InternalServerError().body("Something went wrong :(\n")
|
||||
|
@ -203,15 +204,24 @@ fn check_route(route: &Route, arg_count: usize) -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum HopAction {
|
||||
Redirect(String),
|
||||
Body(String),
|
||||
}
|
||||
|
||||
/// Runs the executable with the user's input as a single argument. Returns Ok
|
||||
/// so long as the executable was successfully executed. Returns an Error if the
|
||||
/// file doesn't exist or bunbun did not have permission to read and execute the
|
||||
/// file.
|
||||
fn resolve_path(path: PathBuf, args: &str) -> Result<Vec<u8>, BunBunError> {
|
||||
let output = Command::new(path.canonicalize()?).arg(args).output()?;
|
||||
fn resolve_path(path: PathBuf, args: &str) -> Result<HopAction, BunBunError> {
|
||||
let output = Command::new(path.canonicalize()?)
|
||||
.args(args.split(" "))
|
||||
.output()?;
|
||||
|
||||
if output.status.success() {
|
||||
Ok(output.stdout)
|
||||
Ok(serde_json::from_slice(&output.stdout[..])?)
|
||||
} else {
|
||||
error!(
|
||||
"Program exit code for {} was not 0! Dumping standard error!",
|
||||
|
@ -359,7 +369,7 @@ mod check_route {
|
|||
|
||||
#[cfg(test)]
|
||||
mod resolve_path {
|
||||
use super::resolve_path;
|
||||
use super::{resolve_path, HopAction};
|
||||
use std::env::current_dir;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -370,7 +380,9 @@ mod resolve_path {
|
|||
|
||||
#[test]
|
||||
fn valid_path_returns_ok() {
|
||||
assert!(resolve_path(PathBuf::from("/bin/echo"), "hello").is_ok());
|
||||
assert!(
|
||||
resolve_path(PathBuf::from("/bin/echo"), r#"{"body": "a"}"#).is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -379,7 +391,7 @@ mod resolve_path {
|
|||
let nest_level = current_dir().unwrap().ancestors().count() - 1;
|
||||
let mut rel_path = PathBuf::from("../".repeat(nest_level));
|
||||
rel_path.push("./bin/echo");
|
||||
assert!(resolve_path(rel_path, "hello").is_ok());
|
||||
assert!(resolve_path(rel_path, r#"{"body": "a"}"#).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -399,4 +411,20 @@ mod resolve_path {
|
|||
// cat-ing a folder always returns exit code 1
|
||||
assert!(resolve_path(PathBuf::from("/bin/cat"), "/").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_body() {
|
||||
assert_eq!(
|
||||
resolve_path(PathBuf::from("/bin/echo"), r#"{"body": "a"}"#).unwrap(),
|
||||
HopAction::Body("a".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_redirect() {
|
||||
assert_eq!(
|
||||
resolve_path(PathBuf::from("/bin/echo"), r#"{"redirect": "a"}"#).unwrap(),
|
||||
HopAction::Redirect("a".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue