use globset::{Glob, GlobMatcher};
use std::path::{Path, PathBuf};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct PathPattern {
pattern: String,
matcher: GlobMatcher,
}
impl PathPattern {
pub fn new(pattern: impl Into<String>) -> Result<Self, CapabilityError> {
let pattern = pattern.into();
let matcher = Glob::new(&pattern)
.map_err(|e| CapabilityError::InvalidGlob {
pattern: pattern.clone(),
reason: e.to_string(),
})?
.compile_matcher();
Ok(Self { pattern, matcher })
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.pattern
}
#[must_use]
pub fn matches(&self, path: &Path) -> bool {
self.matcher.is_match(path)
}
}
impl PartialEq for PathPattern {
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern
}
}
#[derive(Debug, Clone, Default)]
pub struct CapabilitySet {
pub fs_read: Vec<PathPattern>,
pub fs_write: Vec<PathPattern>,
pub env_allow: Vec<String>,
pub network: bool,
}
impl CapabilitySet {
#[cfg(any(test, doctest))]
#[must_use]
pub fn unrestricted() -> Self {
Self {
fs_read: vec![PathPattern::new("**").expect("'**' is a valid glob")],
fs_write: vec![PathPattern::new("**").expect("'**' is a valid glob")],
env_allow: vec![],
network: true,
}
}
pub fn check_read(&self, path: &Path) -> Result<(), CapabilityError> {
if self.fs_read.iter().any(|p| p.matches(path)) {
Ok(())
} else {
Err(CapabilityError::ReadDenied {
path: path.to_path_buf(),
})
}
}
pub fn check_write(&self, path: &Path) -> Result<(), CapabilityError> {
if self.fs_write.iter().any(|p| p.matches(path)) {
Ok(())
} else {
Err(CapabilityError::WriteDenied {
path: path.to_path_buf(),
})
}
}
pub fn check_env(&self, key: &str) -> Result<(), CapabilityError> {
if self.env_allow.iter().any(|k| k == key) {
Ok(())
} else {
Err(CapabilityError::EnvDenied {
var: key.to_string(),
})
}
}
pub fn check_network(&self, url: &str) -> Result<(), CapabilityError> {
if self.network {
Ok(())
} else {
Err(CapabilityError::NetworkDenied {
url: url.to_string(),
})
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum CapabilityError {
#[error("read not permitted: {}", .path.display())]
ReadDenied { path: PathBuf },
#[error("write not permitted: {}", .path.display())]
WriteDenied { path: PathBuf },
#[error("env var not permitted: {var}")]
EnvDenied { var: String },
#[error("network not permitted: {url}")]
NetworkDenied { url: String },
#[error("invalid glob pattern {pattern:?}: {reason}")]
InvalidGlob { pattern: String, reason: String },
}
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub timeout: Duration,
pub max_heap: usize,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
timeout: Duration::from_secs(5),
max_heap: 256 * 1024 * 1024,
}
}
}