pacenotes_shared/
lookup_key.rs1pub fn valid_id(id: &str) -> bool {
6 !id.is_empty()
7 && id
8 .bytes()
9 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'-')
10}
11
12pub fn valid_name(name: &str) -> bool {
17 if name.is_empty() || name.contains('\\') {
18 return false;
19 }
20 if name.chars().any(|c| c.is_ascii_control()) {
23 return false;
24 }
25 name.split('/')
26 .all(|segment| !segment.is_empty() && segment != "." && segment != "..")
27}
28
29#[cfg(test)]
30mod tests {
31 #[test]
32 fn valid_id_is_the_rule_for_games_and_directories() {
33 assert!(super::valid_id("public"));
34 assert!(super::valid_id("rbr"));
35 assert!(super::valid_id("premium-voices_2"));
36 assert!(!super::valid_id(""));
37 assert!(!super::valid_id("Public"));
38 assert!(!super::valid_id(".."));
39 assert!(!super::valid_id("a/b"));
40 assert!(!super::valid_id("a\\b"));
41 assert!(!super::valid_id("a.b"));
42 }
43
44 #[test]
45 fn a_name_can_never_widen_into_a_filesystem_path() {
46 for good in [
47 "basic",
48 "style/Descriptive",
49 "codriver/polish/male/Kamil",
50 "language/english",
51 ] {
52 assert!(super::valid_name(good), "{good:?} should be valid");
53 }
54 for bad in [
55 "",
56 "..",
57 "codriver/../../etc",
58 "codriver/./Kamil",
59 "/codriver/Kamil",
60 "codriver/Kamil/",
61 "codriver//Kamil",
62 "codriver\\polish",
63 ] {
64 assert!(!super::valid_name(bad), "{bad:?} should be rejected");
65 }
66 }
67
68 #[test]
69 fn a_name_can_never_carry_a_control_character() {
70 for bad in [
71 "codriver/Kamil\nmuteReplayPacenotes=1",
72 "codriver/Kamil\r\nenableGUI=0",
73 "codriver/\tKamil",
74 "codriver/Ka\0mil",
75 "codriver/Kamil\u{7f}",
76 ] {
77 assert!(!super::valid_name(bad), "{bad:?} should be rejected");
78 }
79 assert!(super::valid_name("style/Numeric Standard"));
80 assert!(super::valid_name("codriver/polish/male/MiĆosz"));
81 }
82}