const MAX_RENDERED_LEN: usize = 512;
const TRUNCATION_MARKER: &str = "… [truncated]";
const MAX_RENDERED_LINES: usize = 40;
const LINE_TRUNCATION_MARKER: &str = "… [truncated: block too long to display]";
pub(crate) fn operator_safe_block(s: &str) -> OperatorBlock {
let total = s.lines().count();
let mut shortened = 0usize;
let mut lines: Vec<String> = s
.lines()
.take(MAX_RENDERED_LINES)
.map(|line| {
let indent_len = line.len() - line.trim_start().len();
let rendered = operator_safe(line);
if rendered.ends_with(TRUNCATION_MARKER) {
shortened += 1;
}
format!("{}{}", &line[..indent_len], rendered)
})
.collect();
let withheld = total.saturating_sub(MAX_RENDERED_LINES);
if withheld > 0 {
lines.push(format!("{LINE_TRUNCATION_MARKER} ({withheld} more)"));
}
OperatorBlock {
text: lines.join("\n"),
withheld_lines: withheld,
shortened_lines: shortened,
}
}
pub(crate) struct OperatorBlock {
text: String,
withheld_lines: usize,
shortened_lines: usize,
}
impl OperatorBlock {
pub(crate) fn text(&self) -> &str {
&self.text
}
pub(crate) fn withheld_lines(&self) -> usize {
self.withheld_lines
}
pub(crate) fn shortened_lines(&self) -> usize {
self.shortened_lines
}
pub(crate) fn is_complete(&self) -> bool {
self.withheld_lines == 0 && self.shortened_lines == 0
}
}
pub(crate) fn operator_safe_block_full(s: &str) -> OperatorBlock {
let text = s
.lines()
.map(|line| {
let indent_len = line.len() - line.trim_start().len();
format!("{}{}", &line[..indent_len], operator_safe_unbounded(line))
})
.collect::<Vec<_>>()
.join("\n");
OperatorBlock {
text,
withheld_lines: 0,
shortened_lines: 0,
}
}
pub(crate) fn operator_safe(s: &str) -> String {
let out = operator_safe_chars(s);
if out.chars().count() > MAX_RENDERED_LEN {
let cut: String = out.chars().take(MAX_RENDERED_LEN).collect();
return format!("{cut}{TRUNCATION_MARKER}");
}
out
}
fn operator_safe_chars(s: &str) -> String {
let mut out = String::with_capacity(s.len().min(MAX_RENDERED_LEN));
let mut pending_space = false;
for ch in s.chars() {
let keep = match ch {
'\t' | '\n' | '\r' | ' ' => {
pending_space = true;
continue;
}
c if (c as u32) < 0x20 || c == '\u{7f}' => false,
c if ('\u{80}'..='\u{9f}').contains(&c) => false,
'\u{2028}'..='\u{202e}' | '\u{2066}'..='\u{2069}' => false,
'\u{200b}'..='\u{200f}' | '\u{feff}' => false,
'\u{061c}' => false,
'\u{00ad}' | '\u{034f}' | '\u{180b}'..='\u{180e}' | '\u{2060}'..='\u{2064}' | '\u{206a}'..='\u{206f}' | '\u{fff9}'..='\u{fffb}' | '\u{e0000}'..='\u{e007f}' => false,
_ => true,
};
if !keep {
continue;
}
if pending_space && !out.is_empty() {
out.push(' ');
}
pending_space = false;
out.push(ch);
}
out
}
pub(crate) fn operator_safe_unbounded(s: &str) -> String {
operator_safe_chars(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_json_block_cannot_smuggle_bidi_or_zero_width_past_the_prompt() {
let hostile = serde_json::to_string_pretty(&serde_json::json!({
"path": "/etc/\u{202e}gnc.d/passwd\u{202c}",
"note": "safe\u{200b}ish",
}))
.expect("json");
assert!(
hostile.contains('\u{202e}'),
"serde_json escaped the override"
);
let block = operator_safe_block(&hostile);
let safe = block.text();
assert!(
!safe.contains('\u{202e}'),
"bidi override survived: {safe:?}"
);
assert!(!safe.contains('\u{200b}'), "zero-width survived: {safe:?}");
assert!(safe.lines().count() > 1, "block was flattened: {safe:?}");
assert!(safe.contains("passwd"), "visible text was lost: {safe:?}");
}
#[test]
fn a_tall_block_is_bounded_and_says_so() {
let tall = (0..500)
.map(|i| format!("line {i}"))
.collect::<Vec<_>>()
.join("\n");
let block = operator_safe_block(&tall);
let safe = block.text();
assert_eq!(safe.lines().count(), MAX_RENDERED_LINES + 1);
assert!(
safe.contains(LINE_TRUNCATION_MARKER),
"truncation unmarked: {safe:?}"
);
assert_eq!(block.withheld_lines(), 500 - MAX_RENDERED_LINES);
assert!(
safe.ends_with(&format!("({} more)", 500 - MAX_RENDERED_LINES)),
"the count must be on screen, not only in the error: {safe:?}"
);
assert!(!block.is_complete());
}
#[test]
fn a_block_that_fits_reports_complete() {
let short = "line one\nline two\nline three";
let block = operator_safe_block(short);
assert!(block.is_complete());
assert_eq!(block.withheld_lines(), 0);
assert_eq!(block.shortened_lines(), 0);
assert_eq!(block.text(), short);
}
#[test]
fn a_wide_block_is_counted_as_incomplete() {
let wide = format!(" \"paths\": \"{}\"", "/a/long/path".repeat(200));
let block = operator_safe_block(&wide);
assert_eq!(block.withheld_lines(), 0);
assert_eq!(block.shortened_lines(), 1);
assert!(!block.is_complete());
}
#[test]
fn the_full_renderer_keeps_the_character_rules_and_drops_the_bounds() {
let tall: String = (0..500)
.map(|i| format!("line {i} \u{202e}\u{200b}"))
.collect::<Vec<_>>()
.join("\n");
let block = operator_safe_block_full(&tall);
assert!(block.is_complete());
assert_eq!(block.text().lines().count(), 500);
assert!(block.text().contains("line 499"));
assert!(!block.text().contains('\u{202e}'));
assert!(!block.text().contains('\u{200b}'));
assert!(!block.text().contains(TRUNCATION_MARKER));
let wide = format!(" \"paths\": \"{}\"", "/a/long/path".repeat(200));
let block = operator_safe_block_full(&wide);
assert!(block.is_complete());
assert!(
!block.text().contains(TRUNCATION_MARKER),
"the length cap is still cutting lines in the full view: {}",
block.text()
);
assert!(
block.text().matches("/a/long/path").count() == 200,
"the full view dropped part of a line: {} of 200 present",
block.text().matches("/a/long/path").count()
);
}
#[test]
fn every_invisible_formatting_character_is_dropped() {
for ch in [
'\u{061c}', '\u{00ad}', '\u{034f}', '\u{180e}', '\u{2060}', '\u{2064}', '\u{206f}', '\u{fffb}', '\u{e0041}', ] {
let hostile = format!("AptRemove{ch}Everything");
let safe = operator_safe(&hostile);
assert!(
!safe.chars().any(|c| c == ch),
"U+{:04X} survived operator_safe: {safe:?}",
ch as u32
);
assert_eq!(
safe, "AptRemoveEverything",
"U+{:04X} must vanish without disturbing the visible text",
ch as u32
);
}
}
#[test]
fn a_summary_cannot_rewrite_the_line_it_is_printed_on() {
let hostile = "install vim\u{1b}[2K\rremove nothing";
let safe = operator_safe(hostile);
assert!(!safe.contains('\u{1b}'), "ESC survived: {safe:?}");
assert!(!safe.contains('\r'), "CR survived: {safe:?}");
assert_eq!(safe, "install vim[2K remove nothing");
}
#[test]
fn a_summary_cannot_move_the_cursor_off_its_own_line() {
for seq in ["\u{1b}[1A", "\u{1b}[10;10H", "\u{1b}[s\u{1b}[u", "\u{1b}M"] {
let safe = operator_safe(&format!("upgrade{seq}kernel"));
assert!(
!safe.contains('\u{1b}'),
"ESC survived from {seq:?}: {safe:?}"
);
}
}
#[test]
fn c1_control_characters_are_dropped_too() {
let safe = operator_safe("upgrade\u{9b}2K\rkernel");
assert!(!safe.contains('\u{9b}'), "C1 CSI survived: {safe:?}");
}
#[test]
fn a_summary_cannot_forge_extra_lines() {
let safe = operator_safe("install vim\n\n 3 RemoveSwap LOW auto");
assert!(!safe.contains('\n'), "newline survived: {safe:?}");
assert_eq!(safe, "install vim 3 RemoveSwap LOW auto");
}
#[test]
fn bidi_overrides_and_zero_width_characters_are_dropped() {
for ch in [
'\u{202a}', '\u{202b}', '\u{202c}', '\u{202d}', '\u{202e}', '\u{2066}', '\u{2067}',
'\u{2068}', '\u{2069}', '\u{200b}', '\u{200e}', '\u{feff}',
'\u{2028}', '\u{2029}',
] {
let safe = operator_safe(&format!("remove{ch}nothing"));
assert!(!safe.contains(ch), "U+{:04X} survived: {safe:?}", ch as u32);
}
}
#[test]
fn an_overlong_summary_is_capped_and_says_so() {
let safe = operator_safe(&"A".repeat(10_000));
assert!(
safe.chars().count() <= MAX_RENDERED_LEN + TRUNCATION_MARKER.chars().count(),
"not capped: {} chars",
safe.chars().count()
);
assert!(safe.ends_with(TRUNCATION_MARKER), "truncation not marked");
}
#[test]
fn truncation_respects_char_boundaries() {
let safe = operator_safe(&"🔒".repeat(10_000));
assert!(safe.ends_with(TRUNCATION_MARKER));
assert!(safe.chars().count() <= MAX_RENDERED_LEN + TRUNCATION_MARKER.chars().count());
}
#[test]
fn ordinary_summaries_pass_through_intact() {
for s in [
"install vim and git",
"restart nginx.service",
"add 2 GiB of swap at /swapfile",
"rotate /var/log/nginx/*.log daily, keep 14",
"café — naïve — 日本語 — emoji 🔒 all survive",
] {
assert_eq!(operator_safe(s), s, "mangled an ordinary summary");
}
}
#[test]
fn whitespace_collapses_and_trims() {
assert_eq!(operator_safe(" install vim \t\n "), "install vim");
assert_eq!(operator_safe(""), "");
assert_eq!(operator_safe("\u{1b}\u{1b}\u{1b}"), "");
}
}