aboutsummaryrefslogtreecommitdiff
path: root/src/api/server.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/server.rs')
-rw-r--r--src/api/server.rs220
1 files changed, 117 insertions, 103 deletions
diff --git a/src/api/server.rs b/src/api/server.rs
index edb778f7..30d92be4 100644
--- a/src/api/server.rs
+++ b/src/api/server.rs
@@ -22,8 +22,6 @@ use crate::ipc::{
socket_file_identity, LocalStream, LocalStreamRead, SocketFileIdentity,
};
-mod pane_graphics_stream;
-
#[cfg(test)]
mod subscription_socket_tests;
@@ -162,6 +160,30 @@ fn start_server_inner(
})
}
+fn retired_pane_graphics_method_error(line: &str, id: &str) -> Option<ErrorResponse> {
+ #[derive(serde::Deserialize)]
+ struct RequestMethod {
+ method: String,
+ }
+
+ let envelope = serde_json::from_str::<RequestMethod>(line).ok()?;
+ let method = envelope.method.as_str();
+ if !matches!(
+ method,
+ "pane.graphics.info" | "pane.graphics.set" | "pane.graphics.clear" | "pane.graphics.stream"
+ ) {
+ return None;
+ }
+
+ Some(ErrorResponse {
+ id: id.into(),
+ error: ErrorBody {
+ code: "unknown_method".into(),
+ message: format!("unknown method: {method}"),
+ },
+ })
+}
+
fn prepare_socket_path(path: &Path) -> std::io::Result<()> {
crate::ipc::prepare_socket_path(path, |path| {
format!(
@@ -233,16 +255,15 @@ fn handle_connection_with_stop(
} else {
String::new()
};
- write_json_line_allow_disconnect(
- &mut stream,
- &ErrorResponse {
+ let response =
+ retired_pane_graphics_method_error(line, &id).unwrap_or_else(|| ErrorResponse {
id,
error: ErrorBody {
code: "invalid_request".into(),
message: format!("invalid request: {request_error}"),
},
- },
- )?;
+ });
+ write_json_line_allow_disconnect(&mut stream, &response)?;
return Ok(());
}
};
@@ -296,22 +317,6 @@ fn handle_connection_with_stop(
}
Ok(())
}
- Method::PaneGraphicsStream(params) => {
- let result =
- pane_graphics_stream::serve(stream, request_id.clone(), params, api_tx, running);
- match &result {
- Ok(()) => crate::logging::api_request_completed(
- &request_id,
- method,
- "stream_closed",
- changes_ui,
- ),
- Err(err) => {
- crate::logging::api_request_failed(&request_id, method, &err.to_string())
- }
- }
- result
- }
Method::EventsSubscribe(params) => {
let result = stream_subscriptions(
stream,
@@ -478,7 +483,7 @@ fn handle_request(
);
}
- dispatch_to_app(request, api_tx, None, response_write_complete, None, None)
+ dispatch_to_app(request, api_tx, None, response_write_complete, None)
}
pub(crate) fn api_method_name(method: &Method) -> &'static str {
@@ -561,14 +566,6 @@ pub(crate) fn api_method_name(method: &Method) -> &'static str {
Method::PaneSendKeys(_) => "pane.send_keys",
Method::PaneSendInput(_) => "pane.send_input",
Method::PaneRead(_) => "pane.read",
- Method::PaneGraphicsSet(_) => "pane.graphics.set",
- Method::PaneGraphicsClear(_) => "pane.graphics.clear",
- Method::PaneGraphicsInfo(_) => "pane.graphics.info",
- Method::PaneGraphicsStream(_) => "pane.graphics.stream",
- Method::PaneGraphicsStreamSet(_) => "pane.graphics.stream.set",
- Method::PaneGraphicsStreamDirect(_) => "pane.graphics.stream.direct",
- Method::PaneGraphicsStreamOpen(_) => "pane.graphics.stream.open",
- Method::PaneGraphicsStreamClose(_) => "pane.graphics.stream.close",
Method::PaneReportAgent(_) => "pane.report_agent",
Method::PaneReportAgentSession(_) => "pane.report_agent_session",
Method::PaneReportMetadata(_) => "pane.report_metadata",
@@ -928,7 +925,7 @@ pub(super) fn dispatch_to_app_with_timeout(
api_tx: &ApiRequestSender,
timeout: Option<Duration>,
) -> String {
- dispatch_to_app(request, api_tx, timeout, None, None, None)
+ dispatch_to_app(request, api_tx, timeout, None, None)
}
pub(super) fn dispatch_to_app_with_caller_timeout(
@@ -941,55 +938,24 @@ pub(super) fn dispatch_to_app_with_caller_timeout(
api_tx,
timeout,
None,
- None,
Some(("timeout", "timed out waiting for agent status")),
)
}
-pub(super) fn dispatch_stream_open(
- request: Request,
- api_tx: &ApiRequestSender,
- timeout: Duration,
- active: Arc<AtomicBool>,
-) -> String {
- dispatch_to_app(request, api_tx, Some(timeout), None, Some(active), None)
-}
-
-pub(super) fn dispatch_stream_frame(
- request: Request,
- api_tx: &ApiRequestSender,
- active: Arc<AtomicBool>,
-) -> String {
- dispatch_to_app(
- request,
- api_tx,
- Some(crate::app::pane_graphics::DIRECT_OUTER_TIMEOUT),
- None,
- Some(active),
- None,
- )
-}
-
fn dispatch_to_app(
request: Request,
api_tx: &ApiRequestSender,
timeout: Option<Duration>,
response_write_complete: Option<std::sync::mpsc::Receiver<()>>,
- stream_active: Option<Arc<AtomicBool>>,
timeout_response: Option<(&str, &str)>,
) -> String {
let request_id = request.id.clone();
- let request_active = stream_active.clone();
let (respond_to, response_rx) = std::sync::mpsc::channel();
if let Err(err) = api_tx.send(ApiRequestMessage {
request,
respond_to,
response_write_complete,
- stream_active,
}) {
- if let Some(active) = request_active {
- active.store(false, Ordering::Release);
- }
return error_response_json(
request_id,
"server_unavailable",
@@ -1019,9 +985,6 @@ fn dispatch_to_app(
match response {
Ok(response) => response,
Err(err) => {
- if let Some(active) = request_active {
- active.store(false, Ordering::Release);
- }
if err.kind() == std::io::ErrorKind::TimedOut {
if let Some((code, message)) = timeout_response {
return error_response_json(request_id, code, message.into());
@@ -1300,6 +1263,93 @@ mod tests {
}
#[test]
+ fn removed_pane_graphics_methods_return_unknown_method_without_stream_upgrade() {
+ for method in [
+ "pane.graphics.info",
+ "pane.graphics.set",
+ "pane.graphics.clear",
+ "pane.graphics.stream",
+ ] {
+ let (mut client, server, _path) = local_stream_pair("removed-pane-graphics");
+ let (api_tx, mut api_rx) = mpsc::unbounded_channel::<ApiRequestMessage>();
+ writeln!(
+ client,
+ "{{\"id\":\"removed\",\"method\":\"{method}\",\"params\":{{}}}}"
+ )
+ .unwrap();
+ client.flush().unwrap();
+
+ handle_connection(
+ server,
+ &api_tx,
+ &EventHub::default(),
+ &Arc::new(AtomicBool::new(true)),
+ None,
+ )
+ .unwrap();
+
+ let response = read_line(&mut client);
+ let response: serde_json::Value = serde_json::from_str(&response).unwrap();
+ assert_eq!(response["id"], "removed", "{method}");
+ assert_eq!(response["error"]["code"], "unknown_method", "{method}");
+ assert!(response["error"]["message"]
+ .as_str()
+ .is_some_and(|message| message.contains(method)));
+ assert!(response.get("result").is_none(), "{method}");
+ assert!(api_rx.try_recv().is_err(), "{method} reached the app");
+ }
+ }
+
+ #[test]
+ fn unrelated_unknown_method_retains_standard_invalid_request_response() {
+ let (mut client, server, _path) = local_stream_pair("unknown-api-request");
+ let (api_tx, mut api_rx) = mpsc::unbounded_channel::<ApiRequestMessage>();
+ client
+ .write_all(b"{\"id\":\"unknown\",\"method\":\"nope\",\"params\":{}}\n")
+ .unwrap();
+ client.flush().unwrap();
+
+ handle_connection(
+ server,
+ &api_tx,
+ &EventHub::default(),
+ &Arc::new(AtomicBool::new(true)),
+ None,
+ )
+ .unwrap();
+
+ let response = read_line(&mut client);
+ let response: serde_json::Value = serde_json::from_str(&response).unwrap();
+ assert_eq!(response["id"], "unknown");
+ assert_eq!(response["error"]["code"], "invalid_request");
+ assert!(api_rx.try_recv().is_err());
+ }
+
+ #[test]
+ fn ordinary_api_request_still_uses_normal_connection_path() {
+ let (mut client, server, _path) = local_stream_pair("ordinary-api-request");
+ let (api_tx, _api_rx) = mpsc::unbounded_channel::<ApiRequestMessage>();
+ client
+ .write_all(b"{\"id\":\"ordinary\",\"method\":\"ping\",\"params\":{}}\n")
+ .unwrap();
+ client.flush().unwrap();
+
+ handle_connection(
+ server,
+ &api_tx,
+ &EventHub::default(),
+ &Arc::new(AtomicBool::new(true)),
+ None,
+ )
+ .unwrap();
+
+ let response = read_line(&mut client);
+ let response: serde_json::Value = serde_json::from_str(&response).unwrap();
+ assert_eq!(response["id"], "ordinary");
+ assert_eq!(response["result"]["type"], "pong");
+ }
+
+ #[test]
fn ping_request_returns_pong() {
let (tx, _rx) = mpsc::unbounded_channel();
let response = handle_request(
@@ -1758,39 +1808,3 @@ mod tests {
server_thread.join().unwrap();
}
}
-
-#[cfg(test)]
-mod pane_graphics_request_tests {
- use super::*;
- use base64::Engine as _;
-
- #[test]
- fn maximum_public_graphics_request_fits_initial_json_line() {
- let request = Request {
- id: "graphics-max".into(),
- method: Method::PaneGraphicsSet(crate::api::schema::PaneGraphicsSetParams {
- pane_id: "pane_1".into(),
- layer_id: None,
- z_index: 0,
- owner: String::new(),
- format: crate::api::schema::PaneGraphicsFormat::Png,
- image_width: 1,
- image_height: 1,
- data_base64: base64::engine::general_purpose::STANDARD
- .encode(vec![1_u8; crate::api::schema::PANE_GRAPHICS_SET_MAX_BYTES]),
- data: None,
- placement: crate::api::schema::PaneGraphicsPlacementParams::default(),
- }),
- };
- let encoded = serde_json::to_vec(&request).unwrap();
-
- assert!(encoded.len() < MAX_INITIAL_REQUEST_BYTES);
- }
-
- #[test]
- fn duplicate_method_cannot_be_reinterpreted_as_graphics_stream() {
- let encoded = r#"{"id":"duplicate","method":"ping","method":"pane.graphics.stream","params":{"pane_id":"pane_1"}}"#;
-
- assert!(serde_json::from_str::<Request>(encoded).is_err());
- }
-}