Preserve ambiguous terminal input
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / macos-client (macos-aarch64, macos-14) (push) Canceled after 0s
ci / macos-client (macos-x86_64, macos-13) (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest, , , ) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14, , , ) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13, , , ) (push) Canceled after 0s
ci / package-release (windows-aarch64, windows-latest, aarch64, windows, aarch64-pc-windows-msvc) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest, , , ) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / macos-client (macos-aarch64, macos-14) (push) Canceled after 0s
ci / macos-client (macos-x86_64, macos-13) (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest, , , ) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14, , , ) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13, , , ) (push) Canceled after 0s
ci / package-release (windows-aarch64, windows-latest, aarch64, windows, aarch64-pc-windows-msvc) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest, , , ) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s
This commit is contained in:
+76
-15
@@ -8436,7 +8436,7 @@ fn strip_unowned_terminal_reports(bytes: Vec<u8>, mouse_tracking: bool) -> (Vec<
|
||||
return (bytes, false);
|
||||
}
|
||||
let before = bytes.len();
|
||||
let stripped = strip_stale_mouse_reports(&bytes);
|
||||
let stripped = strip_complete_terminal_mouse_reports(&bytes);
|
||||
let changed = stripped.len() != before;
|
||||
(stripped, changed)
|
||||
}
|
||||
@@ -8572,6 +8572,42 @@ fn strip_stale_mouse_reports(bytes: &[u8]) -> Vec<u8> {
|
||||
out
|
||||
}
|
||||
|
||||
fn strip_complete_terminal_mouse_reports(bytes: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::with_capacity(bytes.len());
|
||||
let mut offset = 0;
|
||||
while offset < bytes.len() {
|
||||
if let Some(len) = prefixed_mouse_report_len(bytes, offset) {
|
||||
offset += len;
|
||||
} else {
|
||||
out.push(bytes[offset]);
|
||||
offset += 1;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn prefixed_mouse_report_len(bytes: &[u8], offset: usize) -> Option<usize> {
|
||||
match bytes.get(offset).copied()? {
|
||||
0x1b if bytes.get(offset + 1) == Some(&b'[') => match bytes.get(offset + 2).copied() {
|
||||
Some(b'M') if offset + 6 <= bytes.len() => Some(6),
|
||||
Some(b'<') => mouse_report_params_len(bytes, offset + 3, 2).map(|len| len + 3),
|
||||
Some(byte) if byte.is_ascii_digit() => {
|
||||
mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
0x9b => match bytes.get(offset + 1).copied() {
|
||||
Some(b'M') if offset + 5 <= bytes.len() => Some(5),
|
||||
Some(b'<') => mouse_report_params_len(bytes, offset + 2, 2).map(|len| len + 2),
|
||||
Some(byte) if byte.is_ascii_digit() => {
|
||||
mouse_report_params_len(bytes, offset + 1, 2).map(|len| len + 1)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn stale_mouse_report_len(bytes: &[u8], offset: usize) -> Option<usize> {
|
||||
match bytes.get(offset).copied()? {
|
||||
0x1b if bytes.get(offset + 1) == Some(&b'[') => match bytes.get(offset + 2).copied() {
|
||||
@@ -8643,25 +8679,27 @@ fn stale_mouse_report_maybe_incomplete(bytes: &[u8], offset: usize) -> bool {
|
||||
return false;
|
||||
};
|
||||
match rest {
|
||||
[0x1b] | [0x1b, b'['] | [0x1b, b'[', b'<'] => true,
|
||||
[0x1b, b'[', b'<'] => true,
|
||||
[0x1b, b'[', b'M', ..] if rest.len() < 6 => true,
|
||||
[0x1b, b'[', b'<', params @ ..] | [0x1b, b'[', params @ ..]
|
||||
if params_are_mouse_prefix(params, 2) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
[0x9b] | [0x9b, b'<'] => true,
|
||||
[0x1b, b'[', b'<', params @ ..] if params_are_terminal_report_prefix(params) => true,
|
||||
[0x1b, b'[', params @ ..] if params_are_mouse_prefix(params, 2) => true,
|
||||
[0x9b, b'<'] => true,
|
||||
[0x9b, b'M', ..] if rest.len() < 5 => true,
|
||||
[0x9b, b'<', params @ ..] | [0x9b, params @ ..] if params_are_mouse_prefix(params, 2) => {
|
||||
true
|
||||
}
|
||||
params if params_are_mouse_prefix(params, 2) => true,
|
||||
[0x9b, b'<', params @ ..] if params_are_terminal_report_prefix(params) => true,
|
||||
[0x9b, params @ ..] if params_are_mouse_prefix(params, 2) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn params_are_terminal_report_prefix(params: &[u8]) -> bool {
|
||||
!params.is_empty()
|
||||
&& params
|
||||
.iter()
|
||||
.all(|byte| byte.is_ascii_digit() || *byte == b';')
|
||||
}
|
||||
|
||||
fn params_are_mouse_prefix(params: &[u8], min_semicolons: usize) -> bool {
|
||||
let mut semicolons = 0usize;
|
||||
let mut semicolons = 0;
|
||||
let mut saw_digit = false;
|
||||
for byte in params {
|
||||
match byte {
|
||||
@@ -13969,6 +14007,22 @@ mod tests {
|
||||
assert_eq!(preserved, b"\x1b[<35;10;1M");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_unowned_filter_preserves_ambiguous_user_input() {
|
||||
for input in [
|
||||
b"\x1b".as_slice(),
|
||||
b"\x1b[".as_slice(),
|
||||
b"\x1b[A".as_slice(),
|
||||
b"\x1b[200~paste\x1b[201~".as_slice(),
|
||||
b"35;152;1M".as_slice(),
|
||||
b"printf 35;152;1M\r".as_slice(),
|
||||
] {
|
||||
let (preserved, changed) = strip_unowned_terminal_reports(input.to_vec(), false);
|
||||
assert!(!changed, "live filter changed {input:?}");
|
||||
assert_eq!(preserved, input);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transient_udp_send_errors_are_not_terminal_fatal() {
|
||||
#[cfg(unix)]
|
||||
@@ -14059,12 +14113,19 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_three_field_mouse_prefix_is_suppressed() {
|
||||
assert_eq!(strip_stale_mouse_reports(b"35;152;"), b"");
|
||||
fn only_explicit_incomplete_mouse_prefixes_are_suppressed() {
|
||||
assert_eq!(strip_stale_mouse_reports(b"35;152;"), b"35;152;");
|
||||
assert_eq!(strip_stale_mouse_reports(b"\x1b[<35;152;"), b"");
|
||||
assert_eq!(strip_stale_mouse_reports(b"\x9b35;152;"), b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lone_escape_survives_stale_reconnect_filter() {
|
||||
assert_eq!(strip_stale_mouse_reports(b"\x1b"), b"\x1b");
|
||||
assert_eq!(strip_stale_mouse_reports(b"\x1b["), b"\x1b[");
|
||||
assert_eq!(strip_stale_mouse_reports(b"\x1b[A"), b"\x1b[A");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_focus_reports_are_stripped_from_pending_input() {
|
||||
assert_eq!(strip_stale_mouse_reports(b"\x1b[Ihello\x1b[O"), b"hello");
|
||||
|
||||
Reference in New Issue
Block a user