use crate::collect::model::Snapshot;
const REPLACEMENT: char = '·';
fn unsafe_for_display(c: char) -> bool {
c.is_control()
|| matches!(c, '\u{200E}' | '\u{200F}' | '\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}')
}
fn scrub(s: &mut String) {
if s.chars().any(unsafe_for_display) {
*s = s
.chars()
.map(|c| {
if unsafe_for_display(c) {
REPLACEMENT
} else {
c
}
})
.collect();
}
}
fn scrub_opt(s: &mut Option<String>) {
if let Some(s) = s.as_mut() {
scrub(s);
}
}
pub fn scrub_snapshot(snap: &mut Snapshot) {
scrub(&mut snap.host.hostname);
scrub(&mut snap.host.os);
scrub(&mut snap.host.cpu_model);
for d in &mut snap.disks {
scrub(&mut d.mount_point);
scrub(&mut d.device);
scrub(&mut d.fs_type);
}
for n in &mut snap.net {
scrub(&mut n.name);
}
for p in &mut snap.procs {
scrub(&mut p.user);
scrub(&mut p.name);
scrub(&mut p.cmd);
}
for g in &mut snap.gpus {
scrub(&mut g.name);
scrub(&mut g.vendor);
scrub_opt(&mut g.driver);
scrub_opt(&mut g.live_data_hint);
}
for z in &mut snap.power.thermal_zones {
scrub(&mut z.name);
}
for f in &mut snap.power.fans {
scrub(&mut f.name);
}
scrub_opt(&mut snap.power.live_data_hint);
for s in &mut snap.services {
scrub(&mut s.name);
scrub(&mut s.detail);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collect::model::{
DiskUsageTick, FanTick, GpuTick, InterfaceTick, ProcTick, ServiceTick, ThermalZone,
};
#[test]
fn strips_osc_terminal_title_sequence() {
let mut s = String::from("sh\u{1b}]0;owned\u{7}x");
scrub(&mut s);
assert_eq!(s, "sh·]0;owned·x");
}
#[test]
fn strips_c0_c1_and_del() {
for c in ['\u{1b}', '\u{7}', '\t', '\n', '\r', '\u{7f}', '\u{9b}'] {
let mut s = format!("a{c}b");
scrub(&mut s);
assert_eq!(s, "a·b", "U+{:04X} survived", c as u32);
}
}
#[test]
fn strips_bidi_overrides() {
let mut s = String::from("gnp\u{202E}.exe");
scrub(&mut s);
assert_eq!(s, "gnp·.exe");
}
#[test]
fn leaves_ordinary_text_untouched() {
for input in [
"kernel_task",
"/System/Volumes/Data",
"日本語",
"café",
"🦀",
] {
let mut s = String::from(input);
scrub(&mut s);
assert_eq!(s, input);
}
}
#[test]
fn leaves_combining_marks_and_zwj_untouched() {
let mut s = String::from("e\u{301} \u{1F468}\u{200D}\u{1F4BB}");
let before = s.clone();
scrub(&mut s);
assert_eq!(s, before);
}
#[test]
fn every_string_field_is_scrubbed() {
let evil = "x\u{1b}[2Jy";
let clean = "x·[2Jy";
let mut snap = Snapshot {
disks: vec![DiskUsageTick {
mount_point: evil.into(),
device: evil.into(),
fs_type: evil.into(),
..Default::default()
}],
net: vec![InterfaceTick {
name: evil.into(),
..Default::default()
}],
procs: vec![ProcTick {
user: evil.into(),
name: evil.into(),
cmd: evil.into(),
..Default::default()
}],
gpus: vec![GpuTick {
name: evil.into(),
vendor: evil.into(),
driver: Some(evil.into()),
live_data_hint: Some(evil.into()),
..Default::default()
}],
services: vec![ServiceTick {
name: evil.into(),
detail: evil.into(),
..Default::default()
}],
..Default::default()
};
snap.host.hostname = evil.into();
snap.host.os = evil.into();
snap.host.cpu_model = evil.into();
snap.power.thermal_zones = vec![ThermalZone {
name: evil.into(),
temp_c: 0.0,
}];
snap.power.fans = vec![FanTick {
name: evil.into(),
rpm: 0,
target_rpm: None,
}];
snap.power.live_data_hint = Some(evil.into());
scrub_snapshot(&mut snap);
let json = serde_json::to_string(&snap).expect("serialize");
assert!(!json.contains('\u{1b}'), "raw ESC survived");
assert!(!json.contains("\\u001b"), "escaped ESC survived");
assert!(json.contains(clean), "expected scrubbed text in output");
}
}