pop-os / freedesktop-desktop-entry

Rust crate for navigating Freedesktop desktop entries
Mozilla Public License 2.0
32 stars 17 forks source link

`DesktopEntry::get_args` (private, used in `DesktopEntry::parse_exec*`) does not handle quoting correctly #36

Open quantenzitrone opened 5 days ago

quantenzitrone commented 5 days ago

Try this abomination desktop entry:

[Desktop Entry]
Name=Default Terminal
Type=Application
Exec=printf "|||%%s|||\\\\n" "quoting terminal" "with 'complex' arguments," "quotes \\"," ""    "empty args," "new\nlines," "and \\"back\\\\slashes\\""
Categories=TerminalEmulator;

from Vladimir-csp/xdg-terminal-exec

parse_exec() parses this to:

[
    "printf",
    "\"|||%%s|||\\\\\\\\n\"",
    "\"quoting",
    "terminal\"",
    "\"with",
    "'complex'",
    "arguments,\"",
    "\"quotes",
    "\\\\\",\"",
    "\"\"",
    "\"empty",
    "args,\"",
    "\"new\\nlines,\"",
    "\"and",
    "\\\\\"back\\\\\\\\slashes\\\\\"\"",
]

which should be parsed to:

[
    "printf",
    "|||%s|||\\\\n",
    "quoting terminal",
    "with 'complex' arguments,",
    "quotes \\\",",
    "",
    "empty args,",
    "new\nlines",
    "and \\\"back\\\\slashes\\\"",
]

(if i unescaped and re-rust-escaped this manually correctly)

so basically what the spec says about the Exec attribute

Quoting must be done by enclosing the argument between double quotes and escaping the double quote character, backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character. Implementations must undo quoting before expanding field codes and before passing the argument to the executable program. [...] Literal percentage characters must be escaped as %%.

quantenzitrone commented 5 days ago

I have successfully parsed this Exec field with

let exec: &str = <the exec arg from that file>;
let exec = exec
    .replace("\\\"", "\"")
    .replace("\\`", "`")
    .replace("\\$", "$")
    .replace("\\\\", "\\")
    .replace("%%", "%");
shlex::split(&exec).unwrap().iter().map(|s| s.replace("\\n", "\n")).collect()

not sure if this will cover all cases