diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 44 | ||||
-rw-r--r-- | src/config/helpers.rs | 44 | ||||
-rw-r--r-- | src/main.rs | 3 |
3 files changed, 67 insertions, 24 deletions
diff --git a/src/config.rs b/src/config.rs index d3fc75b..570994c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,8 +4,8 @@ mod helpers; use std::{ - collections::HashMap, fs::File, io::Read, os::unix::fs::PermissionsExt, path::Path, - time::Duration, + collections::HashMap, ffi::OsString, fs::File, io::Read, os::unix::fs::PermissionsExt, + path::Path, time::Duration, }; use anyhow::bail; @@ -66,9 +66,7 @@ fn default_level_filter() -> LevelFilter { #[derive(Debug, PartialEq, Clone)] pub struct Program { - // TODO: Figure out a way to allow arbitrary unix paths (arbitrary - // non-unicode) without base64 - pub command: Box<[String]>, + pub command: Box<[OsString]>, pub timeout: Option<Duration>, } @@ -128,19 +126,25 @@ pub fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Config> { mod tests { use super::*; use rumqttc::QoS; - use std::time::Duration; + use std::{ffi::OsStr, os::unix::ffi::OsStrExt, time::Duration}; impl Program { - fn new(command: Vec<&str>) -> Self { + fn new(command: Vec<&[u8]>) -> Self { Program { - command: command.into_iter().map(str::to_string).collect(), + command: command + .into_iter() + .map(|s| OsStr::from_bytes(s).to_owned()) + .collect(), timeout: None, } } - fn new_with_timeout(command: Vec<&str>, timeout: Duration) -> Self { + fn new_with_timeout(command: Vec<&[u8]>, timeout: Duration) -> Self { Program { - command: command.into_iter().map(str::to_string).collect(), + command: command + .into_iter() + .map(|s| OsStr::from_bytes(s).to_owned()) + .collect(), timeout: Some(timeout), } } @@ -170,7 +174,7 @@ mod tests { { command = ["/bin/program3", "arg"]}, ], qos = "exactly-once" } "topic/seq" = [ - ["/bin/program4", "arg"], + ["/bin/program4", "arg", { b64 = "//9hYmP//w==" }], { command = ["/bin/program5"], timeout = 1.2 }, ] "#; @@ -196,9 +200,9 @@ mod tests { assert_eq!( route_map.programs, vec![ - Program::new(vec!["/bin/program1"]), - Program::new(vec!["/bin/program2", "arg"]), - Program::new(vec!["/bin/program3", "arg"]), + Program::new(vec![b"/bin/program1"]), + Program::new(vec![b"/bin/program2", b"arg"]), + Program::new(vec![b"/bin/program3", b"arg"]), ] .into() ); @@ -208,8 +212,8 @@ mod tests { assert_eq!( route_seq.programs, vec![ - Program::new(vec!["/bin/program4", "arg"]), - Program::new_with_timeout(vec!["/bin/program5"], Duration::from_secs_f64(1.2)), + Program::new(vec![b"/bin/program4", b"arg", b"\xff\xffabc\xff\xff"]), + Program::new_with_timeout(vec![b"/bin/program5"], Duration::from_secs_f64(1.2)), ] .into() ); @@ -243,8 +247,8 @@ mod tests { assert_eq!( route.programs, vec![ - Program::new(vec!["/foo/bar"]), - Program::new(vec!["/baz/qux", "arg"]) + Program::new(vec![b"/foo/bar"]), + Program::new(vec![b"/baz/qux", b"arg"]) ] .into() ); @@ -264,14 +268,14 @@ mod tests { let route_with_qos = config.routes.get("topic/with_qos").unwrap(); assert_eq!( route_with_qos.programs, - vec![Program::new(vec!["/foo/bar", "arg"])].into() + vec![Program::new(vec![b"/foo/bar", b"arg"])].into() ); assert_eq!(route_with_qos.qos, Some(QoS::AtLeastOnce)); let route_without_qos = config.routes.get("topic/without_qos").unwrap(); assert_eq!( route_without_qos.programs, - vec![Program::new(vec!["/baz/qux"])].into() + vec![Program::new(vec![b"/baz/qux"])].into() ); assert_eq!(route_without_qos.qos, None); } diff --git a/src/config/helpers.rs b/src/config/helpers.rs index ddf29ad..edf4bd5 100644 --- a/src/config/helpers.rs +++ b/src/config/helpers.rs @@ -1,8 +1,13 @@ // SPDX-FileCopyrightText: 2025 Tomasz Kramkowski <tomasz@kramkow.ski> // SPDX-License-Identifier: GPL-3.0-or-later -use std::time::Duration; +use std::{ + ffi::{OsStr, OsString}, + os::unix::ffi::OsStrExt, + time::Duration, +}; +use base64::{engine::general_purpose::STANDARD, Engine as _}; use rumqttc::QoS; use serde::{de, Deserialize, Deserializer}; @@ -79,12 +84,44 @@ where Ok(helper.map(|Helper(external)| external)) } +pub fn deserialize_box_slice_os_string<'de, D>(deserializer: D) -> Result<Box<[OsString]>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum Untagged { + String(String), + Base64 { b64: String }, + } + + impl TryInto<OsString> for Untagged { + type Error = String; + fn try_into(self) -> Result<OsString, Self::Error> { + match self { + Untagged::String(s) => Ok(s.into()), + Untagged::Base64 { b64 } => match STANDARD.decode(&b64) { + Err(_) => Err(b64), + Ok(b) => Ok(OsStr::from_bytes(&b).to_owned()), + }, + } + } + } + + Vec::<Untagged>::deserialize(deserializer)? + .into_iter() + .map(TryInto::<OsString>::try_into) + .collect::<Result<_, _>>() + .map_err(|e| de::Error::invalid_value(de::Unexpected::Str(&e), &"valid Base64")) +} + impl<'de> Deserialize<'de> for super::Program { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { #[derive(Deserialize)] #[serde(remote = "super::Program")] struct Helper { - command: Box<[String]>, + #[serde(deserialize_with = "deserialize_box_slice_os_string")] + command: Box<[OsString]>, #[serde(default, deserialize_with = "deserialize_timeout_opt")] timeout: Option<Duration>, } @@ -92,7 +129,8 @@ impl<'de> Deserialize<'de> for super::Program { #[derive(Deserialize)] #[serde(untagged)] enum Untagged { - Short(Box<[String]>), + #[serde(deserialize_with = "deserialize_box_slice_os_string")] + Short(Box<[OsString]>), #[serde(with = "Helper")] Full(super::Program), } diff --git a/src/main.rs b/src/main.rs index c2a3797..2d20c94 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later use std::{ + ffi::OsString, os::unix::process::ExitStatusExt, path::PathBuf, process::{ExitStatus, Stdio}, @@ -18,7 +19,7 @@ mod mqtt; const PROGRAM: &str = "mqttr"; -async fn run(program: &[String], message: &Publish) -> anyhow::Result<ExitStatus> { +async fn run(program: &[OsString], message: &Publish) -> anyhow::Result<ExitStatus> { debug!("Starting program {program:?} for message {message:?}"); let mut command = Command::new(&program[0]); command |