fix(apps): proper exec field-code stripping with quote-awareness
This commit is contained in:
@@ -45,6 +45,60 @@ impl DesktopEntrySource for FsDesktopEntrySource {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clean_exec(exec: &str) -> String {
|
||||
// Tokenize respecting double-quoted strings, then filter field codes.
|
||||
let mut tokens: Vec<String> = Vec::new();
|
||||
let mut chars = exec.chars().peekable();
|
||||
|
||||
while let Some(&ch) = chars.peek() {
|
||||
if ch.is_whitespace() {
|
||||
chars.next();
|
||||
continue;
|
||||
}
|
||||
if ch == '"' {
|
||||
// Consume opening quote
|
||||
chars.next();
|
||||
let mut token = String::from('"');
|
||||
while let Some(&c) = chars.peek() {
|
||||
chars.next();
|
||||
if c == '"' {
|
||||
token.push('"');
|
||||
break;
|
||||
}
|
||||
token.push(c);
|
||||
}
|
||||
// Strip embedded field codes like %f inside the quoted string
|
||||
// (between the quotes, before re-assembling)
|
||||
let inner = &token[1..token.len().saturating_sub(1)];
|
||||
let cleaned_inner: String = inner
|
||||
.split_whitespace()
|
||||
.filter(|s| !is_field_code(s))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
tokens.push(format!("\"{cleaned_inner}\""));
|
||||
} else {
|
||||
let mut token = String::new();
|
||||
while let Some(&c) = chars.peek() {
|
||||
if c.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
chars.next();
|
||||
token.push(c);
|
||||
}
|
||||
if !is_field_code(&token) {
|
||||
tokens.push(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokens.join(" ")
|
||||
}
|
||||
|
||||
fn is_field_code(s: &str) -> bool {
|
||||
let b = s.as_bytes();
|
||||
b.len() == 2 && b[0] == b'%' && b[1].is_ascii_alphabetic()
|
||||
}
|
||||
|
||||
pub fn resolve_icon_path(name: &str) -> Option<String> {
|
||||
if name.starts_with('/') && Path::new(name).exists() {
|
||||
return Some(name.to_string());
|
||||
@@ -111,16 +165,7 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopEntry> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let exec_clean: String = exec?
|
||||
.split_whitespace()
|
||||
.filter(|s| !s.starts_with('%'))
|
||||
.fold(String::new(), |mut acc, s| {
|
||||
if !acc.is_empty() {
|
||||
acc.push(' ');
|
||||
}
|
||||
acc.push_str(s);
|
||||
acc
|
||||
});
|
||||
let exec_clean: String = clean_exec(&exec?);
|
||||
|
||||
Some(DesktopEntry {
|
||||
name: AppName::new(name?),
|
||||
@@ -130,3 +175,28 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopEntry> {
|
||||
keywords,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod exec_tests {
|
||||
use super::clean_exec;
|
||||
|
||||
#[test]
|
||||
fn strips_bare_field_code() {
|
||||
assert_eq!(clean_exec("app --file %f"), "app --file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_multiple_field_codes() {
|
||||
assert_eq!(clean_exec("app %U --flag"), "app --flag");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_quoted_value() {
|
||||
assert_eq!(clean_exec(r#"app --arg="value" %U"#), r#"app --arg="value""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_plain_exec() {
|
||||
assert_eq!(clean_exec("firefox"), "firefox");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user