diff options
author | Aram Drevekenin <aram@poor.dev> | 2023-10-20 14:20:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-20 14:20:00 +0200 |
commit | b59b29a534578cb1910a21a394d0279cb423c2b2 (patch) | |
tree | a63f926a6ce83b6c7ee113f1088538628f2bd557 /zellij-server | |
parent | 41e953f17705be2b9834523a156a93be2518b444 (diff) |
feat(plugins): web requests api (#2879)
* feat(plugins): web requests api
* fix e2e tests
* fix e2e tests again
Diffstat (limited to 'zellij-server')
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(..) |