diff options
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/client_transport.rs | 108 | ||||
| -rw-r--r-- | src/server/clients.rs | 1 | ||||
| -rw-r--r-- | src/server/headless.rs | 27 |
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, }], })); |
