diff options
author | Cory Forsstrom <cforsstrom18@gmail.com> | 2023-03-01 07:11:16 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-01 16:11:16 +0100 |
commit | 6e6e2691a8b8435ac7a8535cf0c4e0645ec20fa2 (patch) | |
tree | 56bf1dec9ea1eb4dfd89b07ee70b2e2f5435522b | |
parent | 6bec2c0b30cff3bade926f379a167d51cdd4a5cc (diff) |
fix(plugins): location path decoding from Url (#2190)
* Add unit test for plugin run location parsing
* Fix file plugin parsing for relative paths
* Update test to check for path with spaces
* Add a couple more tests
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | zellij-utils/Cargo.toml | 1 | ||||
-rw-r--r-- | zellij-utils/src/input/layout.rs | 45 | ||||
-rw-r--r-- | zellij-utils/src/input/plugins.rs | 4 | ||||
-rw-r--r-- | zellij-utils/src/input/unit/layout_test.rs | 88 | ||||
-rw-r--r-- | zellij-utils/src/kdl/kdl_layout_parser.rs | 7 | ||||
-rw-r--r-- | zellij-utils/src/kdl/mod.rs | 8 |
7 files changed, 127 insertions, 27 deletions
diff --git a/Cargo.lock b/Cargo.lock index bc0565f1e..4f64bfb89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4095,6 +4095,7 @@ dependencies = [ "miette 3.3.0", "nix 0.23.1", "once_cell", + "percent-encoding", "regex", "rmp-serde", "serde", diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 32caa3910..766031866 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -23,6 +23,7 @@ lazy_static = "1.4.0" libc = "0.2" nix = "0.23.1" once_cell = "1.8.0" +percent-encoding = "2.1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strip-ansi-escapes = "0.1.0" diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 915c348a8..d06833416 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -23,7 +23,6 @@ use std::str::FromStr; use super::plugins::{PluginTag, PluginsConfigError}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use std::convert::TryFrom; use std::vec::Vec; use std::{ fmt, @@ -221,6 +220,35 @@ pub enum RunPluginLocation { Zellij(PluginTag), } +impl RunPluginLocation { + pub fn parse(location: &str) -> Result<Self, PluginsConfigError> { + let url = Url::parse(location)?; + + let decoded_path = percent_encoding::percent_decode_str(url.path()).decode_utf8_lossy(); + + match url.scheme() { + "zellij" => Ok(Self::Zellij(PluginTag::new(decoded_path))), + "file" => { + let path = if location.starts_with("file:/") { + // 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 + } 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() + }; + + Ok(Self::File(PathBuf::from(path.as_ref()))) + }, + _ => Err(PluginsConfigError::InvalidUrlScheme(url)), + } + } +} + impl From<&RunPluginLocation> for Url { fn from(location: &RunPluginLocation) -> Self { let url = match location { @@ -965,21 +993,6 @@ fn split_space( Ok(pane_positions) } -impl TryFrom<Url> for RunPluginLocation { - type Error = PluginsConfigError; - - fn try_from(url: Url) -> Result<Self, Self::Error> { - match url.scheme() { - "zellij" => Ok(Self::Zellij(PluginTag::new(url.path()))), - "file" => { - let path = PathBuf::from(url.path()); - Ok(Self::File(path)) - }, - _ => Err(PluginsConfigError::InvalidUrl(url)), - } - } -} - impl Default for SplitDirection { fn default() -> Self { SplitDirection::Horizontal diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs index 1ab3e831f..47cc3aa74 100644 --- a/zellij-utils/src/input/plugins.rs +++ b/zellij-utils/src/input/plugins.rs @@ -225,8 +225,10 @@ impl Default for PluginType { pub enum PluginsConfigError { #[error("Duplication in plugin tag names is not allowed: '{}'", String::from(.0.clone()))] DuplicatePlugins(PluginTag), + #[error("Failed to parse url: {0:?}")] + InvalidUrl(#[from] url::ParseError), #[error("Only 'file:' and 'zellij:' url schemes are supported for plugin lookup. '{0}' does not match either.")] - InvalidUrl(Url), + InvalidUrlScheme(Url), #[error("Could not find plugin at the path: '{0:?}'")] InvalidPluginLocation(PathBuf), } diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs index 670273b0f..c8c6c64dc 100644 --- a/zellij-utils/src/input/unit/layout_test.rs +++ b/zellij-utils/src/input/unit/layout_test.rs @@ -1942,3 +1942,91 @@ fn cannot_define_stacked_panes_with_grandchildren_in_pane_template() { let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None, None); assert!(layout.is_err(), "error provided for tab name with space"); } + +fn run_plugin_location_parsing() { + let kdl_layout = r#" + layout { + pane { + plugin location="zellij:tab-bar" + } + pane { + plugin location="file:/path/to/my/plugin.wasm" + } + pane { + plugin location="file:plugin.wasm" + } + pane { + plugin location="file:relative/with space/plugin.wasm" + } + pane { + plugin location="file:///absolute/with space/plugin.wasm" + } + pane { + plugin location="file:c:/absolute/windows/plugin.wasm" + } + } + "#; + let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None, None).unwrap(); + let expected_layout = Layout { + template: Some(( + TiledPaneLayout { + children: vec![ + TiledPaneLayout { + run: Some(Run::Plugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")), + })), + ..Default::default() + }, + TiledPaneLayout { + run: Some(Run::Plugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::File(PathBuf::from( + "/path/to/my/plugin.wasm", + )), + })), + ..Default::default() + }, + TiledPaneLayout { + run: Some(Run::Plugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::File(PathBuf::from("plugin.wasm")), + })), + ..Default::default() + }, + TiledPaneLayout { + run: Some(Run::Plugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::File(PathBuf::from( + "relative/with space/plugin.wasm", + )), + })), + ..Default::default() + }, + TiledPaneLayout { + run: Some(Run::Plugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::File(PathBuf::from( + "/absolute/with space/plugin.wasm", + )), + })), + ..Default::default() + }, + TiledPaneLayout { + run: Some(Run::Plugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::File(PathBuf::from( + "c:/absolute/windows/plugin.wasm", + )), + })), + ..Default::default() + }, + ], + ..Default::default() + }, + vec![], + )), + ..Default::default() + }; + assert_eq!(layout, expected_layout); +} diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index ee6c00399..bcb21b86b 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -22,10 +22,8 @@ use crate::{ kdl_string_arguments, }; -use std::convert::TryFrom; use std::path::PathBuf; use std::vec::Vec; -use url::Url; #[derive(Debug, Clone, PartialEq, Eq)] pub enum PaneOrFloatingPane { @@ -297,14 +295,13 @@ impl<'a> KdlLayoutParser<'a> { plugin_block.span().len(), ), )?; - let url = Url::parse(string_url).map_err(|e| { + let location = RunPluginLocation::parse(&string_url).map_err(|e| { ConfigError::new_layout_kdl_error( - format!("Failed to parse url: {:?}", e), + e.to_string(), url_node.span().offset(), url_node.span().len(), ) })?; - let location = RunPluginLocation::try_from(url)?; 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 5e6fe1505..64fe0bac4 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -13,7 +13,6 @@ use std::collections::HashMap; use std::fs::File; use std::io::Read; use strum::IntoEnumIterator; -use url::Url; use miette::NamedSource; @@ -1387,14 +1386,13 @@ impl RunPlugin { kdl_node.span().len(), ), )?; - let url = Url::parse(string_url).map_err(|e| { - ConfigError::new_kdl_error( - format!("Failed to parse url: {:?}", e), + let location = RunPluginLocation::parse(string_url).map_err(|e| { + ConfigError::new_layout_kdl_error( + e.to_string(), kdl_node.span().offset(), kdl_node.span().len(), ) })?; - let location = RunPluginLocation::try_from(url)?; Ok(RunPlugin { _allow_exec_host_cmd, location, |