Browse / github.com/storytold/craft-launcher / patch
Fix XDG autostart command quoting
Fix XDG autostart command quoting Autostart currently escapes only double quotes in the executable path. Paths containing backslashes, dollar signs, backticks or percent signs can produce an incorrect Desktop Entry Exec argument. Reuse the existing Desktop Entry quoting function in a pure autostart renderer. Reject control characters and non-UTF-8 paths before writing the entry. Keep the ArtCraft Launcher identity and --hidden argument unchanged. The proposed regressions cover special-character and Unicode paths, the hidden flag and identity fields, and rejected paths. Linux and FreeBSD use this renderer; macOS and Windows r
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- IanHollow (imported from pull request #420, open)
- Base
0f295d4b9c90d15c2e38e36c322bdbc0fa943a54- Size
- +45 −4 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
9b6f4844bf21f348d4bcac64
How it went
No other operator has reported using it yet.
Diff
raw · crates/native/src/login.rs
@@ -85,14 +85,20 @@ pub fn set_enabled(on: bool) -> Result<(), String> { if !on { return remove(&path); }- let exe = program()?.display().to_string().replace('"', "\\\"");- let entry = format!(- "[Desktop Entry]\nType=Application\nName=ArtCraft Launcher\nComment=Install and update the Craft App Suite\nExec=\"{exe}\" {HIDDEN_FLAG}\nIcon={ID}\nTerminal=false\nX-GNOME-Autostart-enabled=true\n"- );+ let entry = autostart_entry(&program()?).ok_or("this program's path cannot be written to an autostart entry")?; crate::paths::write_atomic(&path, entry.as_bytes()) } } +/// Render the XDG entry before touching the existing autostart file.+#[cfg(not(any(target_os = "macos", windows)))]+fn autostart_entry(program: &std::path::Path) -> Option<String> {+ let exe = crate::desktop_entry::exec_quote(program)?;+ Some(format!(+ "[Desktop Entry]\nType=Application\nName=ArtCraft Launcher\nComment=Install and update the Craft App Suite\nExec={exe} {HIDDEN_FLAG}\nIcon={ID}\nTerminal=false\nX-GNOME-Autostart-enabled=true\n"+ ))+}+ #[cfg(windows)] fn windows_run() -> Option<winreg::RegKey> { winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER).open_subkey(r"Software\Microsoft\Windows\CurrentVersion\Run").ok()@@ -111,3 +117,38 @@ fn remove(path: &std::path::Path) -> Result<(), String> { Err(e) => Err(format!("couldn't remove {} ({e})", path.display())), } }++#[cfg(all(test, not(any(target_os = "macos", windows))))]+mod autostart_tests {+ use std::path::Path;++ use super::*;++ #[test]+ fn autostart_quotes_the_program_and_preserves_the_hidden_flag() {+ for program in [+ "/opt/ArtCraft Launcher/artcraft-launcher",+ r#"/opt/Apps/\"quotes\" $HOME `command`\\100%/artcraft-launcher"#,+ "/opt/应用/artcraft-launcher",+ ] {+ let entry = autostart_entry(Path::new(program)).unwrap();+ assert_eq!(crate::desktop_entry::exec_program(&entry).as_deref(), Some(program));+ let quoted = crate::desktop_entry::exec_quote(Path::new(program)).unwrap();+ assert!(entry.lines().any(|line| line == format!("Exec={quoted} --hidden")));+ assert!(entry.contains("\nName=ArtCraft Launcher\n"));+ assert!(entry.contains("\nIcon=ai.storyteller.artcraft-launcher\n"));+ assert!(entry.contains("\nX-GNOME-Autostart-enabled=true\n"));+ }+ }++ #[test]+ fn autostart_refuses_control_characters_and_non_utf8_paths() {+ use std::os::unix::ffi::OsStrExt;++ for program in ["/opt/app\nHidden=false", "/opt/app\rName=other", "/opt/app\tname", "/opt/app\0name"] {+ assert_eq!(autostart_entry(Path::new(program)), None);+ }+ let program = std::ffi::OsStr::from_bytes(b"/opt/app\xff");+ assert_eq!(autostart_entry(Path::new(program)), None);+ }+}