From 3201cb9f012b6f6afab80200da0476fef6d8b7ee Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 30 Dec 2024 07:47:54 +0100 Subject: [PATCH] Stop parsing invalid CSI/SS3 sequences as alt-[/alt-o This situation can be triggered in practice inside a terminal like tmux 3.5 by running tmux new-session fish -C 'sleep 2' -d reader -o log-file and typing "alt-escape x" The log shows that we drop treat this as alt-[ and drop the x on the floor. reader: Read char alt-\[ -- Key { modifiers: Modifiers { ctrl: false, alt: true, shift: false }, codepoint: '[' } -- [27, 91, 120] This input ("\e[x") is ambiguous. It looks like it could mean "alt-[,x". However that conflicts with a potential future CSI code, so it makes no sense to try to support this. Returning "None" from parse_csi() causes this weird behavior of returning "alt-[" and dropping the rest of the parsed sequence. This is too easy; it has even crept into a bunch of places where the input sequence is actually valid like "VT200 button released" but where we definitely don't want to report any key. Fix the default: report no key for all unknown sequences and intentionally-suppressed sequences. Treat it at "alt-[" only when there is no input byte available, which is more or less unambiguous, hence a strong enough signal that this is a actually "alt-[". --- src/input_common.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/input_common.rs b/src/input_common.rs index 3aa4525a4..c291ea890 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -10,8 +10,8 @@ use crate::flog::{FloggableDebug, FLOG}; use crate::fork_exec::flog_safe::FLOG_SAFE; use crate::global_safety::RelaxedAtomicBool; use crate::key::{ - self, alt, canonicalize_control_char, canonicalize_keyed_control_char, function_key, shift, - Key, Modifiers, ViewportPosition, + self, alt, canonicalize_control_char, canonicalize_keyed_control_char, ctrl, function_key, + shift, Key, Modifiers, ViewportPosition, }; use crate::reader::{reader_current_data, reader_test_and_clear_interrupted}; use crate::threads::{iothread_port, is_main_thread}; @@ -773,6 +773,7 @@ pub trait InputEventQueuer { } return None; }; + let invalid = Key::from_raw(key::Invalid); if buffer.len() == 2 && next == b'\x1b' { return Some( match self.parse_escape_sequence(buffer, have_escape_prefix) { @@ -780,17 +781,17 @@ pub trait InputEventQueuer { nested_sequence.modifiers.alt = true; nested_sequence } - None => Key::from_raw(key::Invalid), + None => invalid, }, ); } if next == b'[' { // potential CSI - return Some(self.parse_csi(buffer).unwrap_or(alt('['))); + return Some(self.parse_csi(buffer).unwrap_or(invalid)); } if next == b'O' { // potential SS3 - return Some(self.parse_ss3(buffer).unwrap_or(alt('O'))); + return Some(self.parse_ss3(buffer).unwrap_or(invalid)); } match canonicalize_control_char(next) { Some(mut key) => { @@ -872,10 +873,12 @@ pub trait InputEventQueuer { } fn parse_csi(&mut self, buffer: &mut Vec) -> Option { - let mut next_char = |zelf: &mut Self| zelf.try_readb(buffer).unwrap_or(0xff); // The maximum number of CSI parameters is defined by NPAR, nominally 16. let mut params = [[0_u32; 4]; 16]; - let mut c = next_char(self); + let Some(mut c) = self.try_readb(buffer) else { + return Some(ctrl('[')); + }; + let mut next_char = |zelf: &mut Self| zelf.try_readb(buffer).unwrap_or(0xff); let private_mode; if matches!(c, b'?' | b'<' | b'=' | b'>') { // private mode @@ -1063,11 +1066,11 @@ pub trait InputEventQueuer { } // rxvt style 200 => { self.paste_start_buffering(); - return Some(Key::from_raw(key::Invalid)); + return None; } 201 => { self.paste_commit(); - return Some(Key::from_raw(key::Invalid)); + return None; } _ => return None, }, @@ -1113,11 +1116,11 @@ pub trait InputEventQueuer { b'Z' => shift(key::Tab), b'I' => { self.push_front(CharEvent::Implicit(ImplicitEvent::FocusIn)); - return Some(Key::from_raw(key::Invalid)); + return None; } b'O' => { self.push_front(CharEvent::Implicit(ImplicitEvent::FocusOut)); - return Some(Key::from_raw(key::Invalid)); + return None; } _ => return None, }; @@ -1140,13 +1143,12 @@ pub trait InputEventQueuer { fn parse_ss3(&mut self, buffer: &mut Vec) -> Option { let mut raw_mask = 0; - let mut code = b'0'; - loop { + let Some(mut code) = self.try_readb(buffer) else { + return Some(alt('O')); + }; + while (b'0'..=b'9').contains(&code) { raw_mask = raw_mask * 10 + u32::from(code - b'0'); code = self.try_readb(buffer).unwrap_or(0xff); - if !(b'0'..=b'9').contains(&code) { - break; - } } let modifiers = parse_mask(raw_mask.saturating_sub(1)); #[rustfmt::skip]