mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
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:
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user