Merge #1192: Strip bidi control characters from names and report reasons (@Silvertree2010)

# Conflicts:
#	apps/figcraft-server/src/store_tests.rs
This commit is contained in:
bflatastic
2026-10-24 09:21:04 +07:00
2 changed files with 28 additions and 2 deletions
+12 -2
View File
@@ -291,7 +291,17 @@ fn valid_id(s: &str) -> bool {
/// A name others may see: trimmed, no control characters, at most 256 characters. /// A name others may see: trimmed, no control characters, at most 256 characters.
fn clean_name(s: &str) -> String { fn clean_name(s: &str) -> String {
s.trim().chars().filter(|c| !c.is_control()).take(256).collect() s.trim().chars().filter(|c| !c.is_control() && !is_bidi_control(*c)).take(256).collect()
}
/// A report's reason: trimmed, control and bidi characters turned into spaces, at most `MAX_REASON` characters.
fn clean_reason(s: &str) -> String {
s.trim().chars().map(|c| if c.is_control() || is_bidi_control(c) { ' ' } else { c }).take(MAX_REASON).collect()
}
/// Bidirectional format characters, which could reorder what a terminal shows.
fn is_bidi_control(c: char) -> bool {
matches!(c, '\u{61C}' | '\u{200E}' | '\u{200F}' | '\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}')
} }
/// Take the data directory's lock: `None` when someone else holds it. /// Take the data directory's lock: `None` when someone else holds it.
@@ -785,7 +795,7 @@ impl Store {
/// Keep a report about `file` (from someone who joined it with `link`, at `ip`). /// Keep a report about `file` (from someone who joined it with `link`, at `ip`).
pub fn keep_report(&mut self, file: &str, link: Option<&str>, ip: IpAddr, reason: &str) -> Result<(), String> { pub fn keep_report(&mut self, file: &str, link: Option<&str>, ip: IpAddr, reason: &str) -> Result<(), String> {
let reason: String = reason.trim().chars().map(|c| if c.is_control() { ' ' } else { c }).take(MAX_REASON).collect(); let reason = clean_reason(reason);
if reason.is_empty() { if reason.is_empty() {
return Err("say what is wrong with the file".into()); return Err("say what is wrong with the file".into());
} }
+16
View File
@@ -363,4 +363,20 @@ fn a_log_that_isnt_one_is_moved_aside() {
assert!(store.problems().iter().any(|p| p.contains("moved aside")), "{:?}", store.problems()); assert!(store.problems().iter().any(|p| p.contains("moved aside")), "{:?}", store.problems());
let kept = fs::read_dir(&folder).unwrap().filter_map(|e| e.ok()).any(|e| fs::read(e.path()).is_ok_and(|b| b == junk)); let kept = fs::read_dir(&folder).unwrap().filter_map(|e| e.ok()).any(|e| fs::read(e.path()).is_ok_and(|b| b == junk));
assert!(kept, "the old log's bytes are kept beside the file"); assert!(kept, "the old log's bytes are kept beside the file");
const BIDI: [char; 12] =
['\u{61C}', '\u{200E}', '\u{200F}', '\u{202A}', '\u{202B}', '\u{202C}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{2069}'];
#[test]
fn names_and_report_reasons_lose_bidi_controls() {
let dir = TempDir::new("bidi");
let mut store = Store::open(dir.path(), cfg(dir.path())).unwrap();
for (n, m) in BIDI.into_iter().enumerate() {
let ip: std::net::IpAddr = format!("192.0.2.{}", n + 1).parse().unwrap();
let (id, _, _) = publish(&mut store, &format!("a{m}b"));
assert_eq!(store.list(&|_| None)[0].name, "ab", "name U+{:04X}", m as u32);
store.keep_report(&id, None, ip, &format!("a{m}b")).unwrap();
assert_eq!(store.reports().last().unwrap().reason, "a b", "reason U+{:04X}", m as u32);
store.remove(&id).unwrap();
}
} }