From f19334754cf1f8e6bd48bb9cdd905a2d5147e30e Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 14 Jun 2023 13:44:46 +0200 Subject: [PATCH] fix(plugins): allow loading relative urls (#2539) * fix(plugins): allow loading relative urls * style(fmt): rustfmt --- ..._plugin_tests__start_or_reload_plugin.snap | 11 ++++--- zellij-server/src/plugins/zellij_exports.rs | 13 +++++++-- zellij-server/src/route.rs | 11 ++----- zellij-server/src/screen.rs | 8 ++--- zellij-utils/src/cli.rs | 2 +- zellij-utils/src/input/actions.rs | 29 ++++++++++++------- zellij-utils/src/input/layout.rs | 23 +++++++++++---- zellij-utils/src/kdl/kdl_layout_parser.rs | 15 +++++----- zellij-utils/src/kdl/mod.rs | 7 +++-- 9 files changed, 73 insertions(+), 46 deletions(-) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__start_or_reload_plugin.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__start_or_reload_plugin.snap index 173b0ce468..72364a88e0 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__start_or_reload_plugin.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__start_or_reload_plugin.snap @@ -1,13 +1,16 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 2496 +assertion_line: 2889 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( StartOrReloadPluginPane( - File( - "/path/to/my/plugin.wasm", - ), + RunPlugin { + _allow_exec_host_cmd: false, + location: File( + "/path/to/my/plugin.wasm", + ), + }, None, ), ) diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index d4153ec8a0..08d54c848a 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -26,7 +26,7 @@ use zellij_utils::{ input::{ actions::Action, command::{RunCommand, RunCommandAction, TerminalAction}, - layout::Layout, + layout::{Layout, RunPlugin, RunPluginLocation}, plugins::PluginType, }, serde, @@ -946,10 +946,19 @@ fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) { env.plugin_env.name() ) }; + let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); wasi_read_string(&env.plugin_env.wasi_env) .and_then(|url| Url::parse(&url).map_err(|e| anyhow!("Failed to parse url: {}", e))) .and_then(|url| { - let action = Action::StartOrReloadPlugin(url); + RunPluginLocation::parse(url.as_str(), Some(cwd)) + .map_err(|e| anyhow!("Failed to parse plugin location: {}", e)) + }) + .and_then(|run_plugin_location| { + let run_plugin = RunPlugin { + location: run_plugin_location, + _allow_exec_host_cmd: false, + }; + let action = Action::StartOrReloadPlugin(run_plugin); apply_action!(action, error_msg, env); Ok(()) }) diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 2d78ae7453..7ff8594624 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -17,7 +17,7 @@ use zellij_utils::{ actions::{Action, SearchDirection, SearchOption}, command::TerminalAction, get_mode_info, - layout::{Layout, RunPluginLocation}, + layout::Layout, }, ipc::{ ClientAttributes, ClientToServerMsg, ExitReason, IpcReceiverWithContext, ServerToClientMsg, @@ -615,14 +615,9 @@ pub(crate) fn route_action( )) .with_context(err_context)?; }, - Action::StartOrReloadPlugin(url) => { - let run_plugin_location = - RunPluginLocation::parse(url.as_str()).with_context(err_context)?; + Action::StartOrReloadPlugin(run_plugin) => { senders - .send_to_screen(ScreenInstruction::StartOrReloadPluginPane( - run_plugin_location, - None, - )) + .send_to_screen(ScreenInstruction::StartOrReloadPluginPane(run_plugin, None)) .with_context(err_context)?; }, Action::LaunchOrFocusPlugin(run_plugin, should_float) => { diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 7401d9d77c..811777481d 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -260,7 +260,7 @@ pub enum ScreenInstruction { NewTiledPluginPane(RunPluginLocation, Option, ClientId), // Option is // optional pane title NewFloatingPluginPane(RunPluginLocation, Option, ClientId), // Option is an - StartOrReloadPluginPane(RunPluginLocation, Option), + StartOrReloadPluginPane(RunPlugin, Option), // optional pane title AddPlugin( Option, // should_float @@ -2573,14 +2573,10 @@ pub(crate) fn screen_thread_main( size, ))?; }, - ScreenInstruction::StartOrReloadPluginPane(run_plugin_location, pane_title) => { + ScreenInstruction::StartOrReloadPluginPane(run_plugin, pane_title) => { let tab_index = screen.active_tab_indices.values().next().unwrap_or(&1); let size = Size::default(); let should_float = Some(false); - let run_plugin = RunPlugin { - _allow_exec_host_cmd: false, - location: run_plugin_location, - }; screen .bus .senders diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index b222af0049..6639f810b6 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -375,7 +375,7 @@ pub enum CliAction { /// Query all tab names QueryTabNames, StartOrReloadPlugin { - url: Url, + url: String, }, LaunchOrFocusPlugin { #[clap(short, long, value_parser)] diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 36d59dde65..f6e080d80b 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -16,7 +16,6 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::str::FromStr; -use url::Url; use crate::position::Position; @@ -233,7 +232,7 @@ pub enum Action { /// Open a new tiled (embedded, non-floating) plugin pane NewTiledPluginPane(RunPluginLocation, Option), // String is an optional name NewFloatingPluginPane(RunPluginLocation, Option), // String is an optional name - StartOrReloadPlugin(Url), + StartOrReloadPlugin(RunPlugin), } impl Action { @@ -287,14 +286,18 @@ impl Action { close_on_exit, start_suspended, } => { + let current_dir = get_current_dir(); + let cwd = cwd + .map(|cwd| current_dir.join(cwd)) + .or_else(|| Some(current_dir)); if let Some(plugin) = plugin { if floating { - let plugin = RunPluginLocation::parse(&plugin).map_err(|e| { + let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| { format!("Failed to parse plugin loction {plugin}: {}", e) })?; Ok(vec![Action::NewFloatingPluginPane(plugin, name)]) } else { - let plugin = RunPluginLocation::parse(&plugin).map_err(|e| { + let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| { format!("Failed to parse plugin location {plugin}: {}", e) })?; // it is intentional that a new tiled plugin pane cannot include a @@ -310,10 +313,6 @@ impl Action { } else if !command.is_empty() { let mut command = command.clone(); let (command, args) = (PathBuf::from(command.remove(0)), command); - let current_dir = get_current_dir(); - let cwd = cwd - .map(|cwd| current_dir.join(cwd)) - .or_else(|| Some(current_dir)); let hold_on_start = start_suspended; let hold_on_close = !close_on_exit; let run_command_action = RunCommandAction { @@ -474,9 +473,19 @@ impl Action { CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]), CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]), CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]), - CliAction::StartOrReloadPlugin { url } => Ok(vec![Action::StartOrReloadPlugin(url)]), + CliAction::StartOrReloadPlugin { url } => { + let current_dir = get_current_dir(); + let run_plugin_location = RunPluginLocation::parse(&url, Some(current_dir)) + .map_err(|e| format!("Failed to parse plugin location: {}", e))?; + let run_plugin = RunPlugin { + location: run_plugin_location, + _allow_exec_host_cmd: false, + }; + Ok(vec![Action::StartOrReloadPlugin(run_plugin)]) + }, CliAction::LaunchOrFocusPlugin { url, floating } => { - let run_plugin_location = RunPluginLocation::parse(url.as_str()) + let current_dir = get_current_dir(); + let run_plugin_location = RunPluginLocation::parse(url.as_str(), Some(current_dir)) .map_err(|e| format!("Failed to parse plugin location: {}", e))?; let run_plugin = RunPlugin { location: run_plugin_location, diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index b4fcbe2c54..c1d95a125a 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -221,7 +221,7 @@ pub enum RunPluginLocation { } impl RunPluginLocation { - pub fn parse(location: &str) -> Result { + pub fn parse(location: &str, cwd: Option) -> Result { let url = Url::parse(location)?; let decoded_path = percent_encoding::percent_decode_str(url.path()).decode_utf8_lossy(); @@ -233,16 +233,29 @@ impl RunPluginLocation { // Path is absolute, its safe to use URL path. // // This is the case if the scheme and : delimiter are followed by a / slash - decoded_path + PathBuf::from(decoded_path.as_ref()) + } else if location.starts_with("file:~") { + // Unwrap is safe here since location is a valid URL + PathBuf::from(location.strip_prefix("file:").unwrap()) } else { // URL dep doesn't handle relative paths with `file` schema properly, // it always makes them absolute. Use raw location string instead. // // Unwrap is safe here since location is a valid URL - location.strip_prefix("file:").unwrap().into() + let stripped = location.strip_prefix("file:").unwrap(); + match cwd { + Some(cwd) => cwd.join(stripped), + None => PathBuf::from(stripped), + } }; - - Ok(Self::File(PathBuf::from(path.as_ref()))) + let path = match shellexpand::full(&path.to_string_lossy().to_string()) { + Ok(s) => PathBuf::from(s.as_ref()), + Err(e) => { + log::error!("Failed to shell expand plugin path: {}", e); + path + }, + }; + Ok(Self::File(path)) }, _ => Err(PluginsConfigError::InvalidUrlScheme(url)), } diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index 21441cf173..a5cab4b1bc 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -297,13 +297,14 @@ impl<'a> KdlLayoutParser<'a> { plugin_block.span().len(), ), )?; - let location = RunPluginLocation::parse(&string_url).map_err(|e| { - ConfigError::new_layout_kdl_error( - e.to_string(), - url_node.span().offset(), - url_node.span().len(), - ) - })?; + let location = + RunPluginLocation::parse(&string_url, self.cwd_prefix(None)?).map_err(|e| { + ConfigError::new_layout_kdl_error( + e.to_string(), + url_node.span().offset(), + url_node.span().len(), + ) + })?; Ok(Some(Run::Plugin(RunPlugin { _allow_exec_host_cmd, location, diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 58dda9c866..dde4849689 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -892,7 +892,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action { let should_float = command_metadata .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating")) .unwrap_or(false); - let location = RunPluginLocation::parse(&plugin_path)?; + let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + let location = RunPluginLocation::parse(&plugin_path, Some(current_dir))?; let run_plugin = RunPlugin { location, _allow_exec_host_cmd: false, @@ -1402,7 +1403,7 @@ impl Options { } impl RunPlugin { - pub fn from_kdl(kdl_node: &KdlNode) -> Result { + pub fn from_kdl(kdl_node: &KdlNode, cwd: Option) -> Result { let _allow_exec_host_cmd = kdl_get_child_entry_bool_value!(kdl_node, "_allow_exec_host_cmd").unwrap_or(false); let string_url = kdl_get_child_entry_string_value!(kdl_node, "location").ok_or( @@ -1412,7 +1413,7 @@ impl RunPlugin { kdl_node.span().len(), ), )?; - let location = RunPluginLocation::parse(string_url).map_err(|e| { + let location = RunPluginLocation::parse(string_url, cwd).map_err(|e| { ConfigError::new_layout_kdl_error( e.to_string(), kdl_node.span().offset(),