feat(plugins): web requests api (#2879)

* feat(plugins): web requests api

* fix e2e tests

* fix e2e tests again
This commit is contained in:
Aram Drevekenin 2023-10-20 14:20:00 +02:00 committed by GitHub
parent 41e953f177
commit b59b29a534
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1124 additions and 108 deletions

694
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,7 @@ impl ZellijPlugin for State {
PermissionType::RunCommands, PermissionType::RunCommands,
PermissionType::OpenTerminalsOrPlugins, PermissionType::OpenTerminalsOrPlugins,
PermissionType::WriteToStdin, PermissionType::WriteToStdin,
PermissionType::WebAccess,
]); ]);
self.configuration = configuration; self.configuration = configuration;
subscribe(&[ subscribe(&[
@ -257,6 +258,22 @@ impl ZellijPlugin for State {
context, context,
); );
}, },
Key::Ctrl('4') => {
let mut headers = BTreeMap::new();
let mut context = BTreeMap::new();
let body = vec![1, 2, 3];
headers.insert("header1".to_owned(), "value1".to_owned());
headers.insert("header2".to_owned(), "value2".to_owned());
context.insert("user_key_1".to_owned(), "user_value1".to_owned());
context.insert("user_key_2".to_owned(), "user_value2".to_owned());
web_request(
"https://example.com/foo?arg1=val1&arg2=val2",
HttpVerb::Post,
headers,
body,
context,
);
},
_ => {}, _ => {},
}, },
Event::CustomMessage(message, payload) => { Event::CustomMessage(message, payload) => {

View file

@ -3,8 +3,13 @@ use zellij_utils::consts::{
session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name, session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name,
ZELLIJ_SOCK_DIR, 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::errors::{prelude::*, BackgroundJobContext, ContextType};
use zellij_utils::surf::{
self,
http::{Method, Url},
RequestBuilder,
};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::fs; use std::fs;
@ -40,6 +45,15 @@ pub enum BackgroundJob {
PathBuf, PathBuf,
BTreeMap<String, String>, BTreeMap<String, String>,
), // command, args, env_variables, cwd, context ), // command, args, env_variables, cwd, context
WebRequest(
PluginId,
ClientId,
String, // url
HttpVerb,
BTreeMap<String, String>, // headers
Vec<u8>, // body
BTreeMap<String, String>, // context
),
Exit, Exit,
} }
@ -57,6 +71,7 @@ impl From<&BackgroundJob> for BackgroundJobContext {
BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo, BackgroundJob::ReportSessionInfo(..) => BackgroundJobContext::ReportSessionInfo,
BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo, BackgroundJob::ReportLayoutInfo(..) => BackgroundJobContext::ReportLayoutInfo,
BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand, BackgroundJob::RunCommand(..) => BackgroundJobContext::RunCommand,
BackgroundJob::WebRequest(..) => BackgroundJobContext::WebRequest,
BackgroundJob::Exit => BackgroundJobContext::Exit, 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 => { BackgroundJob::Exit => {
for loading_plugin in loading_plugins.values() { for loading_plugin in loading_plugins.values() {
loading_plugin.store(false, Ordering::SeqCst); loading_plugin.store(false, Ordering::SeqCst);

View file

@ -546,6 +546,7 @@ fn create_plugin_thread_with_background_jobs_receiver(
let _ = to_screen.send(ScreenInstruction::Exit); let _ = to_screen.send(ScreenInstruction::Exit);
let _ = to_server.send(ServerInstruction::KillSession); let _ = to_server.send(ServerInstruction::KillSession);
let _ = to_plugin.send(PluginInstruction::Exit); let _ = to_plugin.send(PluginInstruction::Exit);
let _ = to_background_jobs.send(BackgroundJob::Exit);
let _ = plugin_thread.join(); let _ = plugin_thread.join();
} }
}; };
@ -5418,3 +5419,78 @@ pub fn run_command_with_env_vars_and_cwd_plugin_command() {
.clone(); .clone();
assert_snapshot!(format!("{:#?}", new_tab_event)); 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));
}

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4864 assertion_line: 5189
expression: "format!(\"{:#?}\", permissions)" expression: "format!(\"{:#?}\", permissions)"
--- ---
Some( Some(
@ -11,5 +11,6 @@ Some(
RunCommands, RunCommands,
OpenTerminalsOrPlugins, OpenTerminalsOrPlugins,
WriteToStdin, WriteToStdin,
WebAccess,
], ],
) )

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 4767 assertion_line: 5101
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
@ -13,5 +13,6 @@ Some(
RunCommands, RunCommands,
OpenTerminalsOrPlugins, OpenTerminalsOrPlugins,
WriteToStdin, WriteToStdin,
WebAccess,
], ],
) )

View file

@ -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",
},
),
)

View file

@ -18,7 +18,7 @@ use std::{
use wasmer::{imports, AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports}; use wasmer::{imports, AsStoreMut, Function, FunctionEnv, FunctionEnvMut, Imports};
use wasmer_wasi::WasiEnv; use wasmer_wasi::WasiEnv;
use zellij_utils::data::{ use zellij_utils::data::{
CommandType, ConnectToSession, PermissionStatus, PermissionType, PluginPermission, CommandType, ConnectToSession, HttpVerb, PermissionStatus, PermissionType, PluginPermission,
}; };
use zellij_utils::input::permission::PermissionCache; 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) => { PluginCommand::RunCommand(command_line, env_variables, cwd, context) => {
run_command(env, 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) => { PluginCommand::PostMessageTo(plugin_message) => {
post_message_to(env, plugin_message)? post_message_to(env, plugin_message)?
}, },
@ -607,12 +610,6 @@ fn run_command(
cwd: PathBuf, cwd: PathBuf,
context: BTreeMap<String, String>, 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() { if command_line.is_empty() {
log::error!("Command cannot be empty"); log::error!("Command cannot be empty");
} else { } 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<()> { fn post_message_to(env: &ForeignFunctionEnv, plugin_message: PluginMessage) -> Result<()> {
let worker_name = plugin_message let worker_name = plugin_message
.worker_name .worker_name
@ -1198,6 +1217,7 @@ fn check_command_permission(
| PluginCommand::OpenCommandPaneInPlace(..) | PluginCommand::OpenCommandPaneInPlace(..)
| PluginCommand::RunCommand(..) | PluginCommand::RunCommand(..)
| PluginCommand::ExecCmd(..) => PermissionType::RunCommands, | PluginCommand::ExecCmd(..) => PermissionType::RunCommands,
PluginCommand::WebRequest(..) => PermissionType::WebAccess,
PluginCommand::Write(..) | PluginCommand::WriteChars(..) => PermissionType::WriteToStdin, PluginCommand::Write(..) | PluginCommand::WriteChars(..) => PermissionType::WriteToStdin,
PluginCommand::SwitchTabTo(..) PluginCommand::SwitchTabTo(..)
| PluginCommand::SwitchToMode(..) | PluginCommand::SwitchToMode(..)

View file

@ -207,6 +207,21 @@ pub fn run_command_with_env_variables_and_cwd(
unsafe { host_run_plugin_command() }; unsafe { host_run_plugin_command() };
} }
pub fn web_request<S: AsRef<str>>(
url: S,
verb: HttpVerb,
headers: BTreeMap<String, String>,
body: Vec<u8>,
context: BTreeMap<String, String>,
) where
S: ToString,
{
let plugin_command = PluginCommand::WebRequest(url.to_string(), verb, headers, body, context);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
/// Hide the plugin pane (suppress it) from the UI /// Hide the plugin pane (suppress it) from the UI
pub fn hide_self() { pub fn hide_self() {
let plugin_command = PluginCommand::HideSelf; let plugin_command = PluginCommand::HideSelf;

View file

@ -54,6 +54,7 @@ interprocess = "1.2.1"
async-std = { version = "1.3.0", features = ["unstable"] } async-std = { version = "1.3.0", features = ["unstable"] }
notify-debouncer-full = "0.1.0" notify-debouncer-full = "0.1.0"
humantime = "2.1.0" humantime = "2.1.0"
surf = { version = "2.3.2", default-features = false, features = ["h1-client-rustls"] }
[dev-dependencies] [dev-dependencies]
insta = { version = "1.6.0", features = ["backtrace"] } insta = { version = "1.6.0", features = ["backtrace"] }

View file

@ -11,7 +11,7 @@ pub struct Event {
pub name: i32, pub name: i32,
#[prost( #[prost(
oneof = "event::Payload", oneof = "event::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14" tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"
)] )]
pub payload: ::core::option::Option<event::Payload>, pub payload: ::core::option::Option<event::Payload>,
} }
@ -46,6 +46,8 @@ pub mod event {
SessionUpdatePayload(super::SessionUpdatePayload), SessionUpdatePayload(super::SessionUpdatePayload),
#[prost(message, tag = "14")] #[prost(message, tag = "14")]
RunCommandResultPayload(super::RunCommandResultPayload), RunCommandResultPayload(super::RunCommandResultPayload),
#[prost(message, tag = "15")]
WebRequestResultPayload(super::WebRequestResultPayload),
} }
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
@ -68,6 +70,18 @@ pub struct RunCommandResultPayload {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct WebRequestResultPayload {
#[prost(int32, tag = "1")]
pub status: i32,
#[prost(message, repeated, tag = "2")]
pub headers: ::prost::alloc::vec::Vec<Header>,
#[prost(bytes = "vec", tag = "3")]
pub body: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "4")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContextItem { pub struct ContextItem {
#[prost(string, tag = "1")] #[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String, pub name: ::prost::alloc::string::String,
@ -76,6 +90,14 @@ pub struct ContextItem {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct Header {
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PermissionRequestResultPayload { pub struct PermissionRequestResultPayload {
#[prost(bool, tag = "1")] #[prost(bool, tag = "1")]
pub granted: bool, pub granted: bool,
@ -287,6 +309,7 @@ pub enum EventType {
PermissionRequestResult = 15, PermissionRequestResult = 15,
SessionUpdate = 16, SessionUpdate = 16,
RunCommandResult = 17, RunCommandResult = 17,
WebRequestResult = 18,
} }
impl EventType { impl EventType {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -313,6 +336,7 @@ impl EventType {
EventType::PermissionRequestResult => "PermissionRequestResult", EventType::PermissionRequestResult => "PermissionRequestResult",
EventType::SessionUpdate => "SessionUpdate", EventType::SessionUpdate => "SessionUpdate",
EventType::RunCommandResult => "RunCommandResult", EventType::RunCommandResult => "RunCommandResult",
EventType::WebRequestResult => "WebRequestResult",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -336,6 +360,7 @@ impl EventType {
"PermissionRequestResult" => Some(Self::PermissionRequestResult), "PermissionRequestResult" => Some(Self::PermissionRequestResult),
"SessionUpdate" => Some(Self::SessionUpdate), "SessionUpdate" => Some(Self::SessionUpdate),
"RunCommandResult" => Some(Self::RunCommandResult), "RunCommandResult" => Some(Self::RunCommandResult),
"WebRequestResult" => Some(Self::WebRequestResult),
_ => None, _ => None,
} }
} }

View file

@ -5,7 +5,7 @@ pub struct PluginCommand {
pub name: i32, pub name: i32,
#[prost( #[prost(
oneof = "plugin_command::Payload", oneof = "plugin_command::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43" tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44"
)] )]
pub payload: ::core::option::Option<plugin_command::Payload>, pub payload: ::core::option::Option<plugin_command::Payload>,
} }
@ -98,6 +98,8 @@ pub mod plugin_command {
OpenCommandPaneInPlacePayload(super::OpenCommandPanePayload), OpenCommandPaneInPlacePayload(super::OpenCommandPanePayload),
#[prost(message, tag = "43")] #[prost(message, tag = "43")]
RunCommandPayload(super::RunCommandPayload), RunCommandPayload(super::RunCommandPayload),
#[prost(message, tag = "44")]
WebRequestPayload(super::WebRequestPayload),
} }
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
@ -178,6 +180,20 @@ pub struct RunCommandPayload {
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct WebRequestPayload {
#[prost(string, tag = "1")]
pub url: ::prost::alloc::string::String,
#[prost(enumeration = "HttpVerb", tag = "2")]
pub verb: i32,
#[prost(message, repeated, tag = "3")]
pub headers: ::prost::alloc::vec::Vec<super::event::Header>,
#[prost(bytes = "vec", tag = "4")]
pub body: ::prost::alloc::vec::Vec<u8>,
#[prost(message, repeated, tag = "5")]
pub context: ::prost::alloc::vec::Vec<ContextItem>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EnvVariable { pub struct EnvVariable {
#[prost(string, tag = "1")] #[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String, pub name: ::prost::alloc::string::String,
@ -294,6 +310,7 @@ pub enum CommandName {
OpenCommandInPlace = 69, OpenCommandInPlace = 69,
OpenFileInPlace = 70, OpenFileInPlace = 70,
RunCommand = 71, RunCommand = 71,
WebRequest = 72,
} }
impl CommandName { impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -374,6 +391,7 @@ impl CommandName {
CommandName::OpenCommandInPlace => "OpenCommandInPlace", CommandName::OpenCommandInPlace => "OpenCommandInPlace",
CommandName::OpenFileInPlace => "OpenFileInPlace", CommandName::OpenFileInPlace => "OpenFileInPlace",
CommandName::RunCommand => "RunCommand", CommandName::RunCommand => "RunCommand",
CommandName::WebRequest => "WebRequest",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -451,6 +469,39 @@ impl CommandName {
"OpenCommandInPlace" => Some(Self::OpenCommandInPlace), "OpenCommandInPlace" => Some(Self::OpenCommandInPlace),
"OpenFileInPlace" => Some(Self::OpenFileInPlace), "OpenFileInPlace" => Some(Self::OpenFileInPlace),
"RunCommand" => Some(Self::RunCommand), "RunCommand" => Some(Self::RunCommand),
"WebRequest" => Some(Self::WebRequest),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum HttpVerb {
Get = 0,
Post = 1,
Put = 2,
Delete = 3,
}
impl HttpVerb {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
HttpVerb::Get => "Get",
HttpVerb::Post => "Post",
HttpVerb::Put => "Put",
HttpVerb::Delete => "Delete",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"Get" => Some(Self::Get),
"Post" => Some(Self::Post),
"Put" => Some(Self::Put),
"Delete" => Some(Self::Delete),
_ => None, _ => None,
} }
} }

View file

@ -7,6 +7,7 @@ pub enum PermissionType {
RunCommands = 3, RunCommands = 3,
OpenTerminalsOrPlugins = 4, OpenTerminalsOrPlugins = 4,
WriteToStdin = 5, WriteToStdin = 5,
WebAccess = 6,
} }
impl PermissionType { impl PermissionType {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -21,6 +22,7 @@ impl PermissionType {
PermissionType::RunCommands => "RunCommands", PermissionType::RunCommands => "RunCommands",
PermissionType::OpenTerminalsOrPlugins => "OpenTerminalsOrPlugins", PermissionType::OpenTerminalsOrPlugins => "OpenTerminalsOrPlugins",
PermissionType::WriteToStdin => "WriteToStdin", PermissionType::WriteToStdin => "WriteToStdin",
PermissionType::WebAccess => "WebAccess",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -32,6 +34,7 @@ impl PermissionType {
"RunCommands" => Some(Self::RunCommands), "RunCommands" => Some(Self::RunCommands),
"OpenTerminalsOrPlugins" => Some(Self::OpenTerminalsOrPlugins), "OpenTerminalsOrPlugins" => Some(Self::OpenTerminalsOrPlugins),
"WriteToStdin" => Some(Self::WriteToStdin), "WriteToStdin" => Some(Self::WriteToStdin),
"WebAccess" => Some(Self::WebAccess),
_ => None, _ => None,
} }
} }

View file

@ -498,6 +498,15 @@ pub enum Event {
SessionUpdate(Vec<SessionInfo>), SessionUpdate(Vec<SessionInfo>),
RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR, RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR,
// context // context
WebRequestResult(
u16,
BTreeMap<String, String>,
Vec<u8>,
BTreeMap<String, String>,
), // status,
// headers,
// body,
// context
} }
#[derive( #[derive(
@ -524,6 +533,7 @@ pub enum Permission {
RunCommands, RunCommands,
OpenTerminalsOrPlugins, OpenTerminalsOrPlugins,
WriteToStdin, WriteToStdin,
WebAccess,
} }
impl PermissionType { impl PermissionType {
@ -539,6 +549,7 @@ impl PermissionType {
PermissionType::RunCommands => "Run commands".to_owned(), PermissionType::RunCommands => "Run commands".to_owned(),
PermissionType::OpenTerminalsOrPlugins => "Start new terminals and plugins".to_owned(), PermissionType::OpenTerminalsOrPlugins => "Start new terminals and plugins".to_owned(),
PermissionType::WriteToStdin => "Write to standard input (STDIN)".to_owned(), PermissionType::WriteToStdin => "Write to standard input (STDIN)".to_owned(),
PermissionType::WebAccess => "Make web requests".to_owned(),
} }
} }
} }
@ -991,6 +1002,14 @@ impl PluginMessage {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HttpVerb {
Get,
Post,
Put,
Delete,
}
#[derive(Debug, Clone, EnumDiscriminants, ToString)] #[derive(Debug, Clone, EnumDiscriminants, ToString)]
#[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))] #[strum_discriminants(derive(EnumString, Hash, Serialize, Deserialize))]
#[strum_discriminants(name(CommandType))] #[strum_discriminants(name(CommandType))]
@ -1067,12 +1086,16 @@ pub enum PluginCommand {
OpenFileInPlace(FileToOpen), OpenFileInPlace(FileToOpen),
OpenCommandPaneInPlace(CommandToRun), OpenCommandPaneInPlace(CommandToRun),
RunCommand( RunCommand(
Vec<String>, Vec<String>, // command
BTreeMap<String, String>, BTreeMap<String, String>, // env_variables
PathBuf, PathBuf, // cwd
BTreeMap<String, String>, BTreeMap<String, String>, // context
), // command, ),
// env_Variables, WebRequest(
// cwd, String, // url
// context HttpVerb,
BTreeMap<String, String>, // headers
Vec<u8>, // body
BTreeMap<String, String>, // context
),
} }

View file

@ -447,6 +447,7 @@ pub enum BackgroundJobContext {
ReportSessionInfo, ReportSessionInfo,
ReportLayoutInfo, ReportLayoutInfo,
RunCommand, RunCommand,
WebRequest,
Exit, Exit,
} }

View file

@ -24,7 +24,8 @@ pub mod logging; // Requires log4rs
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
pub use ::{ pub use ::{
anyhow, async_channel, async_std, clap, common_path, humantime, interprocess, lazy_static, anyhow, async_channel, async_std, clap, common_path, humantime, interprocess, lazy_static,
libc, miette, nix, notify_debouncer_full, regex, serde, signal_hook, tempfile, termwiz, vte, libc, miette, nix, notify_debouncer_full, regex, serde, signal_hook, surf, tempfile, termwiz,
vte,
}; };
pub use ::prost; pub use ::prost;

View file

@ -41,6 +41,7 @@ enum EventType {
PermissionRequestResult = 15; PermissionRequestResult = 15;
SessionUpdate = 16; SessionUpdate = 16;
RunCommandResult = 17; RunCommandResult = 17;
WebRequestResult = 18;
} }
message EventNameList { message EventNameList {
@ -63,6 +64,7 @@ message Event {
PermissionRequestResultPayload permission_request_result_payload = 12; PermissionRequestResultPayload permission_request_result_payload = 12;
SessionUpdatePayload session_update_payload = 13; SessionUpdatePayload session_update_payload = 13;
RunCommandResultPayload run_command_result_payload = 14; RunCommandResultPayload run_command_result_payload = 14;
WebRequestResultPayload web_request_result_payload = 15;
} }
} }
@ -77,11 +79,23 @@ message RunCommandResultPayload {
repeated ContextItem context = 4; repeated ContextItem context = 4;
} }
message WebRequestResultPayload {
int32 status = 1;
repeated Header headers = 2;
bytes body = 3;
repeated ContextItem context = 4;
}
message ContextItem { message ContextItem {
string name = 1; string name = 1;
string value = 2; string value = 2;
} }
message Header {
string name = 1;
string value = 2;
}
message PermissionRequestResultPayload { message PermissionRequestResultPayload {
bool granted = 1; bool granted = 1;
} }

View file

@ -198,6 +198,25 @@ impl TryFrom<ProtobufEvent> for Event {
}, },
_ => Err("Malformed payload for the RunCommandResult Event"), _ => Err("Malformed payload for the RunCommandResult Event"),
}, },
Some(ProtobufEventType::WebRequestResult) => match protobuf_event.payload {
Some(ProtobufEventPayload::WebRequestResultPayload(web_request_result_payload)) => {
Ok(Event::WebRequestResult(
web_request_result_payload.status as u16,
web_request_result_payload
.headers
.into_iter()
.map(|h| (h.name, h.value))
.collect(),
web_request_result_payload.body,
web_request_result_payload
.context
.into_iter()
.map(|c_i| (c_i.name, c_i.value))
.collect(),
))
},
_ => Err("Malformed payload for the WebRequestResult Event"),
},
None => Err("Unknown Protobuf Event"), None => Err("Unknown Protobuf Event"),
} }
} }
@ -370,6 +389,26 @@ impl TryFrom<Event> for ProtobufEvent {
)), )),
}) })
}, },
Event::WebRequestResult(status, headers, body, context) => {
let web_request_result_payload = WebRequestResultPayload {
status: status as i32,
headers: headers
.into_iter()
.map(|(name, value)| Header { name, value })
.collect(),
body,
context: context
.into_iter()
.map(|(name, value)| ContextItem { name, value })
.collect(),
};
Ok(ProtobufEvent {
name: ProtobufEventType::WebRequestResult as i32,
payload: Some(event::Payload::WebRequestResultPayload(
web_request_result_payload,
)),
})
},
} }
} }
} }
@ -816,6 +855,7 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult, ProtobufEventType::PermissionRequestResult => EventType::PermissionRequestResult,
ProtobufEventType::SessionUpdate => EventType::SessionUpdate, ProtobufEventType::SessionUpdate => EventType::SessionUpdate,
ProtobufEventType::RunCommandResult => EventType::RunCommandResult, ProtobufEventType::RunCommandResult => EventType::RunCommandResult,
ProtobufEventType::WebRequestResult => EventType::WebRequestResult,
}) })
} }
} }
@ -842,6 +882,7 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult, EventType::PermissionRequestResult => ProtobufEventType::PermissionRequestResult,
EventType::SessionUpdate => ProtobufEventType::SessionUpdate, EventType::SessionUpdate => ProtobufEventType::SessionUpdate,
EventType::RunCommandResult => ProtobufEventType::RunCommandResult, EventType::RunCommandResult => ProtobufEventType::RunCommandResult,
EventType::WebRequestResult => ProtobufEventType::WebRequestResult,
}) })
} }
} }

View file

@ -83,6 +83,7 @@ enum CommandName {
OpenCommandInPlace = 69; OpenCommandInPlace = 69;
OpenFileInPlace = 70; OpenFileInPlace = 70;
RunCommand = 71; RunCommand = 71;
WebRequest = 72;
} }
message PluginCommand { message PluginCommand {
@ -130,6 +131,7 @@ message PluginCommand {
OpenFilePayload open_terminal_in_place_payload = 41; OpenFilePayload open_terminal_in_place_payload = 41;
OpenCommandPanePayload open_command_pane_in_place_payload = 42; OpenCommandPanePayload open_command_pane_in_place_payload = 42;
RunCommandPayload run_command_payload = 43; RunCommandPayload run_command_payload = 43;
WebRequestPayload web_request_payload = 44;
} }
} }
@ -179,6 +181,21 @@ message RunCommandPayload {
repeated ContextItem context = 4; repeated ContextItem context = 4;
} }
message WebRequestPayload {
string url = 1;
HttpVerb verb = 2;
repeated event.Header headers = 3;
bytes body = 4;
repeated ContextItem context = 5;
}
enum HttpVerb {
Get = 0;
Post = 1;
Put = 2;
Delete = 3;
}
message EnvVariable { message EnvVariable {
string name = 1; string name = 1;
string value = 2; string value = 2;

View file

@ -1,24 +1,47 @@
pub use super::generated_api::api::{ pub use super::generated_api::api::{
action::{PaneIdAndShouldFloat, SwitchToModePayload}, action::{PaneIdAndShouldFloat, SwitchToModePayload},
event::EventNameList as ProtobufEventNameList, event::{EventNameList as ProtobufEventNameList, Header},
input_mode::InputMode as ProtobufInputMode, input_mode::InputMode as ProtobufInputMode,
plugin_command::{ plugin_command::{
plugin_command::Payload, CommandName, ContextItem, EnvVariable, ExecCmdPayload, plugin_command::Payload, CommandName, ContextItem, EnvVariable, ExecCmdPayload,
IdAndNewName, MovePayload, OpenCommandPanePayload, OpenFilePayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, MovePayload, OpenCommandPanePayload,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload, OpenFilePayload, PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
RequestPluginPermissionPayload, ResizePayload, RunCommandPayload, SetTimeoutPayload, RequestPluginPermissionPayload, ResizePayload, RunCommandPayload, SetTimeoutPayload,
SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload,
WebRequestPayload,
}, },
plugin_permission::PermissionType as ProtobufPermissionType, plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction, resize::ResizeAction as ProtobufResizeAction,
}; };
use crate::data::{ConnectToSession, PermissionType, PluginCommand}; use crate::data::{ConnectToSession, HttpVerb, PermissionType, PluginCommand};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::path::PathBuf; use std::path::PathBuf;
impl Into<HttpVerb> for ProtobufHttpVerb {
fn into(self) -> HttpVerb {
match self {
ProtobufHttpVerb::Get => HttpVerb::Get,
ProtobufHttpVerb::Post => HttpVerb::Post,
ProtobufHttpVerb::Put => HttpVerb::Put,
ProtobufHttpVerb::Delete => HttpVerb::Delete,
}
}
}
impl Into<ProtobufHttpVerb> for HttpVerb {
fn into(self) -> ProtobufHttpVerb {
match self {
HttpVerb::Get => ProtobufHttpVerb::Get,
HttpVerb::Post => ProtobufHttpVerb::Post,
HttpVerb::Put => ProtobufHttpVerb::Put,
HttpVerb::Delete => ProtobufHttpVerb::Delete,
}
}
}
impl TryFrom<ProtobufPluginCommand> for PluginCommand { impl TryFrom<ProtobufPluginCommand> for PluginCommand {
type Error = &'static str; type Error = &'static str;
fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result<Self, &'static str> { fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result<Self, &'static str> {
@ -577,6 +600,34 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
}, },
_ => Err("Mismatched payload for RunCommand"), _ => Err("Mismatched payload for RunCommand"),
}, },
Some(CommandName::WebRequest) => match protobuf_plugin_command.payload {
Some(Payload::WebRequestPayload(web_request_payload)) => {
let context: BTreeMap<String, String> = web_request_payload
.context
.into_iter()
.map(|e| (e.name, e.value))
.collect();
let headers: BTreeMap<String, String> = web_request_payload
.headers
.into_iter()
.map(|e| (e.name, e.value))
.collect();
let verb = match ProtobufHttpVerb::from_i32(web_request_payload.verb) {
Some(verb) => verb.into(),
None => {
return Err("Unrecognized http verb");
},
};
Ok(PluginCommand::WebRequest(
web_request_payload.url,
verb,
headers,
web_request_payload.body,
context,
))
},
_ => Err("Mismatched payload for WebRequest"),
},
None => Err("Unrecognized plugin command"), None => Err("Unrecognized plugin command"),
} }
} }
@ -972,6 +1023,27 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
})), })),
}) })
}, },
PluginCommand::WebRequest(url, verb, headers, body, context) => {
let context: Vec<_> = context
.into_iter()
.map(|(name, value)| ContextItem { name, value })
.collect();
let headers: Vec<_> = headers
.into_iter()
.map(|(name, value)| Header { name, value })
.collect();
let verb: ProtobufHttpVerb = verb.into();
Ok(ProtobufPluginCommand {
name: CommandName::WebRequest as i32,
payload: Some(Payload::WebRequestPayload(WebRequestPayload {
url,
verb: verb as i32,
body,
headers,
context,
})),
})
},
} }
} }
} }

View file

@ -9,4 +9,5 @@ enum PermissionType {
RunCommands = 3; RunCommands = 3;
OpenTerminalsOrPlugins = 4; OpenTerminalsOrPlugins = 4;
WriteToStdin = 5; WriteToStdin = 5;
WebAccess = 6;
} }

View file

@ -19,6 +19,7 @@ impl TryFrom<ProtobufPermissionType> for PermissionType {
Ok(PermissionType::OpenTerminalsOrPlugins) Ok(PermissionType::OpenTerminalsOrPlugins)
}, },
ProtobufPermissionType::WriteToStdin => Ok(PermissionType::WriteToStdin), ProtobufPermissionType::WriteToStdin => Ok(PermissionType::WriteToStdin),
ProtobufPermissionType::WebAccess => Ok(PermissionType::WebAccess),
} }
} }
} }
@ -39,6 +40,7 @@ impl TryFrom<PermissionType> for ProtobufPermissionType {
Ok(ProtobufPermissionType::OpenTerminalsOrPlugins) Ok(ProtobufPermissionType::OpenTerminalsOrPlugins)
}, },
PermissionType::WriteToStdin => Ok(ProtobufPermissionType::WriteToStdin), PermissionType::WriteToStdin => Ok(ProtobufPermissionType::WriteToStdin),
PermissionType::WebAccess => Ok(ProtobufPermissionType::WebAccess),
} }
} }
} }