fix(plugins): allow loading relative urls (#2539)

* fix(plugins): allow loading relative urls

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2023-06-14 13:44:46 +02:00 committed by GitHub
parent 59239cc113
commit f19334754c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 46 deletions

View file

@ -1,13 +1,16 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 2496 assertion_line: 2889
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
StartOrReloadPluginPane( StartOrReloadPluginPane(
File( RunPlugin {
_allow_exec_host_cmd: false,
location: File(
"/path/to/my/plugin.wasm", "/path/to/my/plugin.wasm",
), ),
},
None, None,
), ),
) )

View file

@ -26,7 +26,7 @@ use zellij_utils::{
input::{ input::{
actions::Action, actions::Action,
command::{RunCommand, RunCommandAction, TerminalAction}, command::{RunCommand, RunCommandAction, TerminalAction},
layout::Layout, layout::{Layout, RunPlugin, RunPluginLocation},
plugins::PluginType, plugins::PluginType,
}, },
serde, serde,
@ -946,10 +946,19 @@ fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) {
env.plugin_env.name() env.plugin_env.name()
) )
}; };
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
wasi_read_string(&env.plugin_env.wasi_env) 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| Url::parse(&url).map_err(|e| anyhow!("Failed to parse url: {}", e)))
.and_then(|url| { .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); apply_action!(action, error_msg, env);
Ok(()) Ok(())
}) })

View file

@ -17,7 +17,7 @@ use zellij_utils::{
actions::{Action, SearchDirection, SearchOption}, actions::{Action, SearchDirection, SearchOption},
command::TerminalAction, command::TerminalAction,
get_mode_info, get_mode_info,
layout::{Layout, RunPluginLocation}, layout::Layout,
}, },
ipc::{ ipc::{
ClientAttributes, ClientToServerMsg, ExitReason, IpcReceiverWithContext, ServerToClientMsg, ClientAttributes, ClientToServerMsg, ExitReason, IpcReceiverWithContext, ServerToClientMsg,
@ -615,14 +615,9 @@ pub(crate) fn route_action(
)) ))
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::StartOrReloadPlugin(url) => { Action::StartOrReloadPlugin(run_plugin) => {
let run_plugin_location =
RunPluginLocation::parse(url.as_str()).with_context(err_context)?;
senders senders
.send_to_screen(ScreenInstruction::StartOrReloadPluginPane( .send_to_screen(ScreenInstruction::StartOrReloadPluginPane(run_plugin, None))
run_plugin_location,
None,
))
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::LaunchOrFocusPlugin(run_plugin, should_float) => { Action::LaunchOrFocusPlugin(run_plugin, should_float) => {

View file

@ -260,7 +260,7 @@ pub enum ScreenInstruction {
NewTiledPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is NewTiledPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is
// optional pane title // optional pane title
NewFloatingPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is an NewFloatingPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is an
StartOrReloadPluginPane(RunPluginLocation, Option<String>), StartOrReloadPluginPane(RunPlugin, Option<String>),
// optional pane title // optional pane title
AddPlugin( AddPlugin(
Option<bool>, // should_float Option<bool>, // should_float
@ -2573,14 +2573,10 @@ pub(crate) fn screen_thread_main(
size, 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 tab_index = screen.active_tab_indices.values().next().unwrap_or(&1);
let size = Size::default(); let size = Size::default();
let should_float = Some(false); let should_float = Some(false);
let run_plugin = RunPlugin {
_allow_exec_host_cmd: false,
location: run_plugin_location,
};
screen screen
.bus .bus
.senders .senders

View file

@ -375,7 +375,7 @@ pub enum CliAction {
/// Query all tab names /// Query all tab names
QueryTabNames, QueryTabNames,
StartOrReloadPlugin { StartOrReloadPlugin {
url: Url, url: String,
}, },
LaunchOrFocusPlugin { LaunchOrFocusPlugin {
#[clap(short, long, value_parser)] #[clap(short, long, value_parser)]

View file

@ -16,7 +16,6 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use url::Url;
use crate::position::Position; use crate::position::Position;
@ -233,7 +232,7 @@ pub enum Action {
/// Open a new tiled (embedded, non-floating) plugin pane /// Open a new tiled (embedded, non-floating) plugin pane
NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name
NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name
StartOrReloadPlugin(Url), StartOrReloadPlugin(RunPlugin),
} }
impl Action { impl Action {
@ -287,14 +286,18 @@ impl Action {
close_on_exit, close_on_exit,
start_suspended, 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 let Some(plugin) = plugin {
if floating { 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) format!("Failed to parse plugin loction {plugin}: {}", e)
})?; })?;
Ok(vec![Action::NewFloatingPluginPane(plugin, name)]) Ok(vec![Action::NewFloatingPluginPane(plugin, name)])
} else { } 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) format!("Failed to parse plugin location {plugin}: {}", e)
})?; })?;
// it is intentional that a new tiled plugin pane cannot include a // it is intentional that a new tiled plugin pane cannot include a
@ -310,10 +313,6 @@ impl Action {
} else if !command.is_empty() { } else if !command.is_empty() {
let mut command = command.clone(); let mut command = command.clone();
let (command, args) = (PathBuf::from(command.remove(0)), command); 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_start = start_suspended;
let hold_on_close = !close_on_exit; let hold_on_close = !close_on_exit;
let run_command_action = RunCommandAction { let run_command_action = RunCommandAction {
@ -474,9 +473,19 @@ impl Action {
CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]), CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]),
CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]), CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]),
CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]), 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 } => { 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))?; .map_err(|e| format!("Failed to parse plugin location: {}", e))?;
let run_plugin = RunPlugin { let run_plugin = RunPlugin {
location: run_plugin_location, location: run_plugin_location,

View file

@ -221,7 +221,7 @@ pub enum RunPluginLocation {
} }
impl RunPluginLocation { impl RunPluginLocation {
pub fn parse(location: &str) -> Result<Self, PluginsConfigError> { pub fn parse(location: &str, cwd: Option<PathBuf>) -> Result<Self, PluginsConfigError> {
let url = Url::parse(location)?; let url = Url::parse(location)?;
let decoded_path = percent_encoding::percent_decode_str(url.path()).decode_utf8_lossy(); 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. // Path is absolute, its safe to use URL path.
// //
// This is the case if the scheme and : delimiter are followed by a / slash // 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 { } else {
// URL dep doesn't handle relative paths with `file` schema properly, // URL dep doesn't handle relative paths with `file` schema properly,
// it always makes them absolute. Use raw location string instead. // it always makes them absolute. Use raw location string instead.
// //
// Unwrap is safe here since location is a valid URL // 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),
}
}; };
let path = match shellexpand::full(&path.to_string_lossy().to_string()) {
Ok(Self::File(PathBuf::from(path.as_ref()))) 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)), _ => Err(PluginsConfigError::InvalidUrlScheme(url)),
} }

View file

@ -297,7 +297,8 @@ impl<'a> KdlLayoutParser<'a> {
plugin_block.span().len(), plugin_block.span().len(),
), ),
)?; )?;
let location = RunPluginLocation::parse(&string_url).map_err(|e| { let location =
RunPluginLocation::parse(&string_url, self.cwd_prefix(None)?).map_err(|e| {
ConfigError::new_layout_kdl_error( ConfigError::new_layout_kdl_error(
e.to_string(), e.to_string(),
url_node.span().offset(), url_node.span().offset(),

View file

@ -892,7 +892,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
let should_float = command_metadata let should_float = command_metadata
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating")) .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
.unwrap_or(false); .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 { let run_plugin = RunPlugin {
location, location,
_allow_exec_host_cmd: false, _allow_exec_host_cmd: false,
@ -1402,7 +1403,7 @@ impl Options {
} }
impl RunPlugin { impl RunPlugin {
pub fn from_kdl(kdl_node: &KdlNode) -> Result<Self, ConfigError> { pub fn from_kdl(kdl_node: &KdlNode, cwd: Option<PathBuf>) -> Result<Self, ConfigError> {
let _allow_exec_host_cmd = let _allow_exec_host_cmd =
kdl_get_child_entry_bool_value!(kdl_node, "_allow_exec_host_cmd").unwrap_or(false); 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( let string_url = kdl_get_child_entry_string_value!(kdl_node, "location").ok_or(
@ -1412,7 +1413,7 @@ impl RunPlugin {
kdl_node.span().len(), 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( ConfigError::new_layout_kdl_error(
e.to_string(), e.to_string(),
kdl_node.span().offset(), kdl_node.span().offset(),