summaryrefslogtreecommitdiffstats
path: root/zellij-server
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-10-20 14:20:00 +0200
committerGitHub <noreply@github.com>2023-10-20 14:20:00 +0200
commitb59b29a534578cb1910a21a394d0279cb423c2b2 (patch)
treea63f926a6ce83b6c7ee113f1088538628f2bd557 /zellij-server
parent41e953f17705be2b9834523a156a93be2518b444 (diff)
feat(plugins): web requests api (#2879)
* feat(plugins): web requests api * fix e2e tests * fix e2e tests again
Diffstat (limited to 'zellij-server')
-rw-r--r--zellij-server/src/background_jobs.rs80
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs76
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap3
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap3
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__web_request_plugin_command.snap26
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs34
6 files changed, 212 insertions, 10 deletions
diff --git a/zellij-server/src/background_jobs.rs b/zellij-server/src/background_jobs.rs
index 57e13364c..3f2777b76 100644
--- a/zellij-server/src/background_jobs.rs
+++ b/zellij-server/src/background_jobs.rs
@@ -3,8 +3,13 @@ use zellij_utils::consts::{
session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name,
ZELLIJ_SOCK_DIR,
};
-use zellij_utils::data::{Event, SessionInfo};
+use zellij_utils::data::{Event, HttpVerb, SessionInfo};
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
+use zellij_utils::surf::{
+ self,
+ http::{Method, Url},
+ RequestBuilder,
+};
use std::collections::{BTreeMap, HashMap};
use std::fs;
@@ -40,6 +45,15 @@ pub enum BackgroundJob {
PathBuf,
BTreeMap<String, String>,
), // command, args, env_variables, cwd, context
+ WebRequest(
+ PluginId,
+ ClientId,
+ String, // url
+ HttpVerb,
+ BTreeMap<String, String>, // headers
+ Vec<u8>, // body
+ BTreeMap<String, String>, // context
+ ),
Exit,
}
@@ -57,6 +71,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand,
+ BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest,
BackgroundJob::Exit => BackgroundJobContext::Exit,
}
}
@@ -285,6 +300,69 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
}
});
},
+ BackgroundJob::WebRequest(plugin_id, client_id, url, verb, headers, body, context) => {
+ task::spawn({
+ let senders = bus.senders.clone();
+ async move {
+ async fn web_request(
+ url: String,
+ verb: HttpVerb,
+ headers: BTreeMap<String, String>,
+ body: Vec<u8>,
+ ) -> Result<
+ (u16, BTreeMap<String, String>, Vec<u8>), // status_code, headers, body
+ zellij_utils::surf::Error,
+ > {
+ let url = Url::parse(&url)?;
+ let http_method = match verb {
+ HttpVerb::Get => Method::Get,
+ HttpVerb::Post => Method::Post,
+ HttpVerb::Put => Method::Put,
+ HttpVerb::Delete => Method::Delete,
+ };
+ let mut req = RequestBuilder::new(http_method, url);
+ if !body.is_empty() {
+ req = req.body(body);
+ }
+ for (header, value) in headers {
+ req = req.header(header.as_str(), value);
+ }
+ let mut res = req.await?;
+ let status_code = res.status();
+ let headers: BTreeMap<String, String> = res
+ .iter()
+ .map(|(name, value)| (name.to_string(), value.to_string()))
+ .collect();
+ let body = res.take_body().into_bytes().await?;
+ Ok((status_code as u16, headers, body))
+ }
+
+ match web_request(url, verb, headers, body).await {
+ Ok((status, headers, body)) => {
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ Some(plugin_id),
+ Some(client_id),
+ Event::WebRequestResult(status, headers, body, context),
+ )]));
+ },
+ Err(e) => {
+ log::error!("Failed to send web request: {}", e);
+ let error_body = e.to_string().as_bytes().to_vec();
+ let _ = senders.send_to_plugin(PluginInstruction::Update(vec![(
+ Some(plugin_id),
+ Some(client_id),
+ Event::WebRequestResult(
+ 400,
+ BTreeMap::new(),
+ error_body,
+ context,
+ ),
+ )]));
+ },
+ }
+ }
+ });
+ },
BackgroundJob::Exit => {
for loading_plugin in loading_plugins.values() {
loading_plugin.store(false, Ordering::SeqCst);
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index e866987d7..d9d2229ca 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -546,6 +546,7 @@ fn create_plugin_thread_with_background_jobs_receiver(
let _ = to_screen.send(ScreenInstruction::Exit);
let _ = to_server.send(ServerInstruction::KillSession);
let _ = to_plugin.send(PluginInstruction::Exit);
+ let _ = to_background_jobs.send(BackgroundJob::Exit);
let _ = plugin_thread.join();
}
};
@@ -5418,3 +5419,78 @@ pub fn run_command_with_env_vars_and_cwd_plugin_command() {
.clone();
assert_snapshot!(format!("{:#?}", new_tab_event));
}
+
+#[test]
+#[ignore]
+pub fn web_request_plugin_command() {
+ let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
+ // destructor removes the directory
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, background_jobs_receiver, screen_receiver, teardown) =
+ create_plugin_thread_with_background_jobs_receiver(Some(plugin_host_folder));
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ };
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_background_jobs_instructions = Arc::new(Mutex::new(vec![]));
+ let background_jobs_thread = log_actions_in_thread!(
+ received_background_jobs_instructions,
+ BackgroundJob::WebRequest,
+ background_jobs_receiver,
+ 1
+ );
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
+ received_screen_instructions,
+ ScreenInstruction::Exit,
+ screen_receiver,
+ 1,
+ &PermissionType::WebAccess,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ plugin_title,
+ run_plugin,
+ tab_index,
+ None,
+ client_id,
+ size,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(Key::Ctrl('4')), // this triggers the enent in the fixture plugin
+ )]));
+ background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold
+ teardown();
+ let new_tab_event = received_background_jobs_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let BackgroundJob::WebRequest(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_tab_event));
+}
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap
index beaa4adc7..cc51bef21 100644
--- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
-assertion_line: 4864
+assertion_line: 5189
expression: "format!(\"{:#?}\", permissions)"
---
Some(
@@ -11,5 +11,6 @@ Some(
RunCommands,
OpenTerminalsOrPlugins,
WriteToStdin,
+ WebAccess,
],
)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap
index 23fa6c224..d9a92db59 100644
--- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
-assertion_line: 4767
+assertion_line: 5101
expression: "format!(\"{:#?}\", new_tab_event)"
---
Some(
@@ -13,5 +13,6 @@ Some(
RunCommands,
OpenTerminalsOrPlugins,
WriteToStdin,
+ WebAccess,
],
)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__web_request_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__web_request_plugin_command.snap
new file mode 100644
index 000000000..4a97e368a
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__web_request_plugin_command.snap
@@ -0,0 +1,26 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 5494
+expression: "format!(\"{:#?}\", new_tab_event)"
+---
+Some(
+ WebRequest(
+ 0,
+ 1,
+ "https://example.com/foo?arg1=val1&arg2=val2",
+ Post,
+ {
+ "header1": "value1",
+ "header2": "value2",
+ },
+ [
+ 1,
+ 2,
+ 3,
+ ],
+ {
+ "user_key_1": "user_value1",
+ "user_key_2": "user_value2",
+ },
+ ),
+)
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index 496c7d64a..524d0a8f3 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -18,7 +18,7 @@ use std::{
use wasmer::{imports, AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports};
use wasmer_wasi::WasiEnv;
use zellij_utils::data::{
- CommandType, ConnectToSession, PermissionStatus, PermissionType, PluginPermission,
+ CommandType, ConnectToSession, HttpVerb, PermissionStatus, PermissionType, PluginPermission,
};
use zellij_utils::input::permission::PermissionCache;
@@ -130,6 +130,9 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
PluginCommand::RunCommand(command_line, env_variables, cwd, context) => {
run_command(env, command_line, env_variables, cwd, context)
},
+ PluginCommand::WebRequest(url, verb, headers, body, context) => {
+ web_request(env, url, verb, headers, body, context)
+ },
PluginCommand::PostMessageTo(plugin_message) => {
post_message_to(env, plugin_message)?
},
@@ -607,12 +610,6 @@ fn run_command(
cwd: PathBuf,
context: BTreeMap<String, String>,
) {
- let err_context = || {
- format!(
- "failed to execute command on host for plugin '{}'",
- env.plugin_env.name()
- )
- };
if command_line.is_empty() {
log::error!("Command cannot be empty");
} else {
@@ -632,6 +629,28 @@ fn run_command(
}
}
+fn web_request(
+ env: &ForeignFunctionEnv,
+ url: String,
+ verb: HttpVerb,
+ headers: BTreeMap<String, String>,
+ body: Vec<u8>,
+ context: BTreeMap<String, String>,
+) {
+ let _ = env
+ .plugin_env
+ .senders
+ .send_to_background_jobs(BackgroundJob::WebRequest(
+ env.plugin_env.plugin_id,
+ env.plugin_env.client_id,
+ url,
+ verb,
+ headers,
+ body,
+ context,
+ ));
+}
+
fn post_message_to(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> {
let worker_name = plugin_message
.worker_name
@@ -1198,6 +1217,7 @@ fn check_command_permission(
| PluginCommand::OpenCommandPaneInPlace(..)
| PluginCommand::RunCommand(..)
| PluginCommand::ExecCmd(..) => PermissionType::RunCommands,
+ PluginCommand::WebRequest(..) => PermissionType::WebAccess,
PluginCommand::Write(..) | PluginCommand::WriteChars(..) => PermissionType::WriteToStdin,
PluginCommand::SwitchTabTo(..)
| PluginCommand::SwitchToMode(..)