use wasmtime::component::ResourceTable;
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
use crate::env::ShellEnv;
use super::generated::yosh::plugin::types::ErrorCode;
use super::pattern::CommandPattern;
mod commands;
mod files;
mod filesystem;
mod io;
mod variables;
pub(super) use commands::{deny_commands_exec, host_commands_exec};
pub(super) use files::{
deny_files_append_file, deny_files_create_dir, deny_files_metadata, deny_files_read_dir,
deny_files_read_file, deny_files_remove_dir, deny_files_remove_file, deny_files_write_file,
host_files_append_file, host_files_create_dir, host_files_metadata, host_files_read_dir,
host_files_read_file, host_files_remove_dir, host_files_remove_file, host_files_write_file,
};
pub(super) use filesystem::{
deny_filesystem_cwd, deny_filesystem_set_cwd, host_filesystem_cwd, host_filesystem_set_cwd,
};
pub(super) use io::{deny_io_write, host_io_write};
pub(super) use variables::{
deny_variables_export_env, deny_variables_get, deny_variables_set, host_variables_export_env,
host_variables_get, host_variables_set,
};
pub struct HostContext {
pub(super) env: *mut ShellEnv,
#[allow(dead_code)]
pub(super) plugin_name: String,
#[allow(dead_code)]
pub(super) capabilities: u32,
pub(super) wasi: WasiCtx,
pub(super) resource_table: ResourceTable,
pub(super) allowed_commands: Vec<CommandPattern>,
}
unsafe impl Send for HostContext {}
unsafe impl Sync for HostContext {}
impl HostContext {
pub fn new_for_plugin(plugin_name: impl Into<String>, capabilities: u32) -> Self {
let wasi = WasiCtxBuilder::new().build();
HostContext {
env: std::ptr::null_mut(),
plugin_name: plugin_name.into(),
capabilities,
wasi,
resource_table: ResourceTable::new(),
allowed_commands: Vec::new(),
}
}
pub(super) fn ensure_bound(&self) -> Result<(), ErrorCode> {
if self.env.is_null() {
Err(ErrorCode::Denied)
} else {
Ok(())
}
}
pub(super) fn bound_env_ref(&self) -> Result<&ShellEnv, ErrorCode> {
if self.env.is_null() {
Err(ErrorCode::Denied)
} else {
Ok(unsafe { &*self.env })
}
}
pub(super) fn bound_env_with<R, F>(&self, f: F) -> Result<R, ErrorCode>
where
F: FnOnce(&mut ShellEnv) -> R,
{
if self.env.is_null() {
Err(ErrorCode::Denied)
} else {
Ok(f(unsafe { &mut *self.env }))
}
}
}
impl WasiView for HostContext {
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi
}
fn table(&mut self) -> &mut ResourceTable {
&mut self.resource_table
}
}
#[cfg(test)]
pub(super) mod test_helpers {
use super::super::pattern::CommandPattern;
use super::HostContext;
use crate::env::ShellEnv;
use yosh_plugin_api::CAP_ALL;
pub fn null_env_ctx() -> HostContext {
HostContext::new_for_plugin("<test>", CAP_ALL)
}
pub fn bound_env_ctx(env: &mut ShellEnv) -> HostContext {
let mut ctx = HostContext::new_for_plugin("<test>", CAP_ALL);
ctx.env = env as *mut ShellEnv;
ctx
}
pub fn ctx_with_allowed(env: &mut ShellEnv, patterns: &[&str]) -> HostContext {
let mut ctx = bound_env_ctx(env);
ctx.allowed_commands = patterns
.iter()
.map(|s| CommandPattern::parse(s).expect("valid pattern"))
.collect();
ctx
}
}
#[cfg(test)]
mod tests {
use super::super::generated::yosh::plugin::types::ErrorCode;
use super::test_helpers::{bound_env_ctx, null_env_ctx};
use crate::env::ShellEnv;
#[test]
fn ensure_bound_returns_denied_when_env_null() {
let ctx = null_env_ctx();
assert_eq!(ctx.ensure_bound(), Err(ErrorCode::Denied));
}
#[test]
fn ensure_bound_returns_ok_when_env_bound() {
let mut env = ShellEnv::new("yosh", vec![]);
let ctx = bound_env_ctx(&mut env);
assert_eq!(ctx.ensure_bound(), Ok(()));
}
}