aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/client_transport.rs108
-rw-r--r--src/server/clients.rs1
-rw-r--r--src/server/headless.rs27
3 files changed, 126 insertions, 10 deletions
diff --git a/src/server/client_transport.rs b/src/server/client_transport.rs
index 743a108f..2675b4c3 100644
--- a/src/server/client_transport.rs
+++ b/src/server/client_transport.rs
@@ -383,24 +383,62 @@ enum InputEventLimit {
WithinLimits,
TooManyEvents,
PasteTooLarge { size: usize },
+ InputPayloadTooLarge { size: usize },
}
fn input_event_limit(events: &[ClientInputEvent]) -> InputEventLimit {
- if events.len() > MAX_INPUT_EVENT_BATCH {
- return InputEventLimit::TooManyEvents;
- }
-
+ let mut expanded_events = 0usize;
let mut paste_bytes = 0usize;
+ let mut input_bytes = 0usize;
for event in events {
- if let ClientInputEvent::Paste { text } = event {
- paste_bytes = paste_bytes.saturating_add(text.len());
+ expanded_events = expanded_events.saturating_add(match event {
+ ClientInputEvent::Key { repeat_count, .. } => usize::from((*repeat_count).max(1)),
+ _ => 1,
+ });
+ match event {
+ ClientInputEvent::Key {
+ repeat_count,
+ generated_text,
+ source,
+ ..
+ } => {
+ if let Some(text) = generated_text {
+ input_bytes = input_bytes.saturating_add(
+ text.len()
+ .saturating_mul(usize::from((*repeat_count).max(1))),
+ );
+ }
+ if let crate::protocol::ClientKeySource::Vt { bytes } = source {
+ input_bytes = input_bytes.saturating_add(bytes.len());
+ }
+ }
+ ClientInputEvent::TextCommit(text) => {
+ input_bytes = input_bytes.saturating_add(text.len());
+ }
+ ClientInputEvent::Paste { text } => {
+ paste_bytes = paste_bytes.saturating_add(text.len());
+ }
+ ClientInputEvent::Mouse { .. }
+ | ClientInputEvent::FocusGained
+ | ClientInputEvent::FocusLost => {}
}
}
- if paste_bytes > MAX_INPUT_PAYLOAD {
- InputEventLimit::PasteTooLarge { size: paste_bytes }
- } else {
+ if expanded_events > MAX_INPUT_EVENT_BATCH {
+ return InputEventLimit::TooManyEvents;
+ }
+
+ let payload_bytes = paste_bytes.saturating_add(input_bytes);
+ if payload_bytes <= MAX_INPUT_PAYLOAD {
InputEventLimit::WithinLimits
+ } else if input_bytes == 0 {
+ InputEventLimit::PasteTooLarge {
+ size: payload_bytes,
+ }
+ } else {
+ InputEventLimit::InputPayloadTooLarge {
+ size: payload_bytes,
+ }
}
}
@@ -717,6 +755,17 @@ fn client_read_loop(
max: MAX_INPUT_PAYLOAD,
}
}
+ InputEventLimit::InputPayloadTooLarge { size } => {
+ warn!(
+ client_id,
+ size,
+ max = MAX_INPUT_PAYLOAD,
+ "oversized structured input payload from client, closing"
+ );
+ let _ = server_event_tx
+ .blocking_send(ServerEvent::ClientDisconnected { client_id });
+ break;
+ }
},
ClientMessage::ObserveTerminal { target } => {
ServerEvent::ClientObserveTerminal { client_id, target }
@@ -1388,6 +1437,10 @@ new_tab = "ctrl+notakey"
code: crate::protocol::ClientKeyCode::Enter,
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
+
+ repeat_count: 1,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
},
ClientInputEvent::FocusGained,
];
@@ -1550,6 +1603,43 @@ new_tab = "ctrl+notakey"
}
#[test]
+ fn structured_input_limits_charge_grouped_repeats_and_text_payloads() {
+ let grouped = ClientInputEvent::Key {
+ code: crate::protocol::ClientKeyCode::Char('x'),
+ modifiers: 0,
+ kind: crate::protocol::ClientKeyKind::Press,
+ repeat_count: (MAX_INPUT_EVENT_BATCH + 1) as u16,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
+ };
+ assert_eq!(
+ input_event_limit(&[grouped]),
+ InputEventLimit::TooManyEvents
+ );
+
+ let repeated_text = ClientInputEvent::Key {
+ code: crate::protocol::ClientKeyCode::Char('x'),
+ modifiers: 0,
+ kind: crate::protocol::ClientKeyKind::Press,
+ repeat_count: MAX_INPUT_EVENT_BATCH as u16,
+ generated_text: Some("x".repeat((MAX_INPUT_PAYLOAD / MAX_INPUT_EVENT_BATCH) + 1)),
+ source: crate::protocol::ClientKeySource::Synthesized,
+ };
+ assert!(matches!(
+ input_event_limit(&[repeated_text]),
+ InputEventLimit::InputPayloadTooLarge { size } if size > MAX_INPUT_PAYLOAD
+ ));
+
+ let text = ClientInputEvent::TextCommit("x".repeat(MAX_INPUT_PAYLOAD + 1));
+ assert_eq!(
+ input_event_limit(&[text]),
+ InputEventLimit::InputPayloadTooLarge {
+ size: MAX_INPUT_PAYLOAD + 1
+ }
+ );
+ }
+
+ #[test]
fn handshake_timeout_is_within_five_second_deadline() {
// The handshake timeout must be short enough that
// the connection is guaranteed to close within 5 seconds even with
diff --git a/src/server/clients.rs b/src/server/clients.rs
index c6c617ec..bb0f31c6 100644
--- a/src/server/clients.rs
+++ b/src/server/clients.rs
@@ -257,6 +257,7 @@ pub(crate) fn events_include_interaction(events: &[crate::raw_input::RawInputEve
matches!(
event,
crate::raw_input::RawInputEvent::Key(_)
+ | crate::raw_input::RawInputEvent::Text(_)
| crate::raw_input::RawInputEvent::Mouse(_)
| crate::raw_input::RawInputEvent::Paste(_)
| crate::raw_input::RawInputEvent::OuterFocusGained
diff --git a/src/server/headless.rs b/src/server/headless.rs
index 6fdb83ce..ff85f253 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -4544,6 +4544,7 @@ fn events_for_app_routing(
}
crate::raw_input::RawInputEvent::OuterFocusLost if !source_is_foreground => None,
crate::raw_input::RawInputEvent::Key(_)
+ | crate::raw_input::RawInputEvent::Text(_)
| crate::raw_input::RawInputEvent::Mouse(_)
| crate::raw_input::RawInputEvent::Paste(_) => {
source_is_foreground = true;
@@ -7539,6 +7540,10 @@ next_tab = ""
code: crate::protocol::ClientKeyCode::Char('j'),
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
+
+ repeat_count: 1,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
}],
}));
server.foreground_client_id = Some(2);
@@ -7558,7 +7563,7 @@ next_tab = ""
.expect("synthetic release from background client"),
Bytes::from_static(b"\x1b[106;1:3u")
);
- assert!(server.app.pressed_terminal_keys.is_empty());
+ assert!(server.app.input_leases.is_empty());
}
#[tokio::test]
@@ -7602,6 +7607,10 @@ next_tab = ""
code: crate::protocol::ClientKeyCode::Char('x'),
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Release,
+
+ repeat_count: 1,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
},
crate::protocol::ClientInputEvent::FocusLost,
],
@@ -7652,6 +7661,10 @@ next_tab = ""
code: crate::protocol::ClientKeyCode::Char('x'),
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Release,
+
+ repeat_count: 1,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
}],
}));
assert_eq!(server.foreground_client_id, Some(3));
@@ -7863,6 +7876,10 @@ next_tab = ""
code: crate::protocol::ClientKeyCode::Enter,
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
+
+ repeat_count: 1,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
}],
}));
@@ -7899,6 +7916,10 @@ next_tab = ""
code: crate::protocol::ClientKeyCode::Esc,
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
+
+ repeat_count: 1,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
}],
}));
@@ -7932,6 +7953,10 @@ next_tab = ""
code: crate::protocol::ClientKeyCode::Down,
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
+
+ repeat_count: 1,
+ generated_text: None,
+ source: crate::protocol::ClientKeySource::Synthesized,
}],
}));