Match running app paths literally (#81)

This commit is contained in:
Benziza 2026-10-11 08:18:20 +02:00 committed by GitHub
parent dba8ea12cc
commit 9eb2c60f18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 66 additions and 1 deletions

View File

@ -128,7 +128,72 @@ pub fn is_running(path: &Path) -> bool {
.is_some_and(|out| out.to_ascii_lowercase().contains(&name.to_ascii_lowercase()))
} else {
let Some(p) = path.to_str() else { return false };
let pattern = pgrep_pattern(p);
// pgrep exits 0 when something matches; it never matches itself.
capture("pgrep", &["-f", "--", p], Duration::from_secs(5)).is_some()
capture("pgrep", &["-f", "--", &pattern], Duration::from_secs(5)).is_some()
}
}
/// Quote the path's metacharacters for pgrep's extended regular expression.
fn pgrep_pattern(path: &str) -> String {
let mut pattern = String::with_capacity(path.len());
for c in path.chars() {
if matches!(c, '.' | '^' | '$' | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '\\') {
pattern.push('\\');
}
pattern.push(c);
}
pattern
}
#[cfg(test)]
#[cfg(unix)]
mod tests {
use std::io::{BufRead, BufReader};
use std::process::Child;
use super::*;
struct RunningProbe(Child);
impl RunningProbe {
fn start(path: &str) -> Self {
let child = command("/bin/sh")
.args(["-c", "printf 'ready\\n'; read line", path])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.unwrap();
let mut probe = Self(child);
let mut ready = String::new();
BufReader::new(probe.0.stdout.take().unwrap()).read_line(&mut ready).unwrap();
assert_eq!(ready, "ready\n");
probe
}
}
impl Drop for RunningProbe {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
#[test]
fn running_paths_are_matched_literally() {
let root = format!("/tmp/artcraft-launcher-process-{}", std::process::id());
for (actual, requested, expected) in [
("plain/PhotoCraft.app", "plain/PhotoCraft.app", true),
("Apps (work)/PhotoCraft.app", "Apps (work)/PhotoCraft.app", true),
("Apps [work]/PhotoCraft.app", "Apps [work]/PhotoCraft.app", true),
("other/PhotoCraftXapp", "other/PhotoCraft.app", false),
("symbols/应用.^$*+?()[]{}|\\.app", "symbols/应用.^$*+?()[]{}|\\.app", true),
] {
let actual = format!("{root}/{actual}");
let requested = format!("{root}/{requested}");
let _probe = RunningProbe::start(&actual);
assert_eq!(is_running(Path::new(&requested)), expected, "{requested}");
}
}
}