|
@@ -2,15 +2,124 @@
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
+#[cfg(any(shell_execute, shell_sidecar))]
|
|
|
+use crate::api::process::Command;
|
|
|
#[cfg(shell_open)]
|
|
|
use crate::api::shell::Program;
|
|
|
-use crate::ShellScopeConfig;
|
|
|
-#[cfg(any(shell_execute, shell_sidecar))]
|
|
|
-use crate::{api::process::Command, ExecuteArgs, ShellScopeAllowedArg};
|
|
|
+
|
|
|
+use regex::Regex;
|
|
|
+
|
|
|
+use std::collections::HashMap;
|
|
|
+
|
|
|
+/// Allowed representation of `Execute` command arguments.
|
|
|
+#[derive(Debug, Clone, serde::Deserialize)]
|
|
|
+#[serde(untagged, deny_unknown_fields)]
|
|
|
+#[non_exhaustive]
|
|
|
+pub enum ExecuteArgs {
|
|
|
+ /// No arguments
|
|
|
+ None,
|
|
|
+
|
|
|
+ /// A single string argument
|
|
|
+ Single(String),
|
|
|
+
|
|
|
+ /// Multiple string arguments
|
|
|
+ List(Vec<String>),
|
|
|
+
|
|
|
+ /// Multiple string arguments in a key-value fashion
|
|
|
+ Map(HashMap<String, String>),
|
|
|
+}
|
|
|
+
|
|
|
+impl ExecuteArgs {
|
|
|
+ /// Whether the argument list is empty or not.
|
|
|
+ pub fn is_empty(&self) -> bool {
|
|
|
+ match self {
|
|
|
+ Self::None => true,
|
|
|
+ Self::Single(s) if s.is_empty() => true,
|
|
|
+ Self::List(l) => l.is_empty(),
|
|
|
+ Self::Map(m) => m.is_empty(),
|
|
|
+ _ => false,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<()> for ExecuteArgs {
|
|
|
+ fn from(_: ()) -> Self {
|
|
|
+ Self::None
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<String> for ExecuteArgs {
|
|
|
+ fn from(string: String) -> Self {
|
|
|
+ Self::Single(string)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<Vec<String>> for ExecuteArgs {
|
|
|
+ fn from(vec: Vec<String>) -> Self {
|
|
|
+ Self::List(vec)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<HashMap<String, String>> for ExecuteArgs {
|
|
|
+ fn from(map: HashMap<String, String>) -> Self {
|
|
|
+ Self::Map(map)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Shell scope configuration.
|
|
|
+#[derive(Debug, Clone)]
|
|
|
+pub struct ScopeConfig {
|
|
|
+ /// The validation regex that `shell > open` paths must match against.
|
|
|
+ pub open: Option<Regex>,
|
|
|
+
|
|
|
+ /// All allowed commands, using their unique command name as the keys.
|
|
|
+ pub scopes: HashMap<String, ScopeAllowedCommand>,
|
|
|
+}
|
|
|
+
|
|
|
+/// A configured scoped shell command.
|
|
|
+#[derive(Debug, Clone)]
|
|
|
+pub struct ScopeAllowedCommand {
|
|
|
+ /// The shell command to be called.
|
|
|
+ pub command: std::path::PathBuf,
|
|
|
+
|
|
|
+ /// The arguments the command is allowed to be called with.
|
|
|
+ pub args: Option<Vec<ScopeAllowedArg>>,
|
|
|
+
|
|
|
+ /// If this command is a sidecar command.
|
|
|
+ pub sidecar: bool,
|
|
|
+}
|
|
|
+
|
|
|
+/// A configured argument to a scoped shell command.
|
|
|
+#[derive(Debug, Clone)]
|
|
|
+pub enum ScopeAllowedArg {
|
|
|
+ /// A non-configurable argument.
|
|
|
+ Fixed(String),
|
|
|
+
|
|
|
+ /// An argument with a value to be evaluated at runtime, optionally must pass a regex validation.
|
|
|
+ Var {
|
|
|
+ /// The key name of the argument variable
|
|
|
+ name: String,
|
|
|
+
|
|
|
+ /// The validation, if set, that the variable value must pass in order to be called.
|
|
|
+ validate: Option<Regex>,
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+impl ScopeAllowedArg {
|
|
|
+ /// If the argument is fixed.
|
|
|
+ pub fn is_fixed(&self) -> bool {
|
|
|
+ matches!(self, Self::Fixed(_))
|
|
|
+ }
|
|
|
+
|
|
|
+ /// If the argument is a variable value.
|
|
|
+ pub fn is_var(&self) -> bool {
|
|
|
+ matches!(self, Self::Var { .. })
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
/// Scope for filesystem access.
|
|
|
#[derive(Clone)]
|
|
|
-pub struct Scope(ShellScopeConfig);
|
|
|
+pub struct Scope(ScopeConfig);
|
|
|
|
|
|
/// All errors that can happen while validating a scoped command.
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
@@ -95,7 +204,7 @@ pub enum ScopeError {
|
|
|
|
|
|
impl Scope {
|
|
|
/// Creates a new shell scope.
|
|
|
- pub fn new(scope: ShellScopeConfig) -> Self {
|
|
|
+ pub fn new(scope: ScopeConfig) -> Self {
|
|
|
Self(scope)
|
|
|
}
|
|
|
|
|
@@ -124,8 +233,8 @@ impl Scope {
|
|
|
(Some(list), ExecuteArgs::Map(args)) => list
|
|
|
.iter()
|
|
|
.map(|arg| match arg {
|
|
|
- ShellScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()),
|
|
|
- ShellScopeAllowedArg::Var { name, validate } => {
|
|
|
+ ScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()),
|
|
|
+ ScopeAllowedArg::Var { name, validate } => {
|
|
|
let value = args
|
|
|
.get(name)
|
|
|
.ok_or_else(|| ScopeError::MissingVar(name.into()))?
|
|
@@ -146,15 +255,13 @@ impl Scope {
|
|
|
}
|
|
|
})
|
|
|
.collect(),
|
|
|
- (Some(list), arg) if arg.is_empty() && list.iter().all(ShellScopeAllowedArg::is_fixed) => {
|
|
|
- list
|
|
|
- .iter()
|
|
|
- .map(|arg| match arg {
|
|
|
- ShellScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()),
|
|
|
- _ => unreachable!(),
|
|
|
- })
|
|
|
- .collect()
|
|
|
- }
|
|
|
+ (Some(list), arg) if arg.is_empty() && list.iter().all(ScopeAllowedArg::is_fixed) => list
|
|
|
+ .iter()
|
|
|
+ .map(|arg| match arg {
|
|
|
+ ScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()),
|
|
|
+ _ => unreachable!(),
|
|
|
+ })
|
|
|
+ .collect(),
|
|
|
(Some(list), _) if list.is_empty() => Err(ScopeError::InvalidInput(command_name.into())),
|
|
|
(Some(_), _) => Err(ScopeError::InvalidInput(command_name.into())),
|
|
|
}?;
|