collective

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

diff --git a/crates/native/src/login.rs b/crates/native/src/login.rs--- a/crates/native/src/login.rs+++ b/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);+    }+}