diff --git a/Cargo.lock b/Cargo.lock index 68f3dcbd66..84385768b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1342,9 +1342,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -9062,6 +9062,7 @@ dependencies = [ "serde_with", "sha1_smol", "sha2", + "shlex", "sqlx", "sysinfo", "tauri", diff --git a/Cargo.toml b/Cargo.toml index 2c80cd5e31..f44bc50123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,7 @@ serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde i sha1 = "0.10.6" sha1_smol = { version = "1.0.1", features = ["std"] } sha2 = "0.10.9" +shlex = "1.3.0" spdx = "0.10.9" sqlx = { version = "0.8.6", default-features = false } sysinfo = { version = "0.36.1", default-features = false } diff --git a/packages/app-lib/Cargo.toml b/packages/app-lib/Cargo.toml index c6af3741c5..21f9683898 100644 --- a/packages/app-lib/Cargo.toml +++ b/packages/app-lib/Cargo.toml @@ -36,6 +36,7 @@ rgb.workspace = true phf.workspace = true itertools.workspace = true derive_more = { workspace = true, features = ["display"] } +shlex.workspace = true chrono = { workspace = true, features = ["serde"] } daedalus.workspace = true diff --git a/packages/app-lib/src/api/profile/mod.rs b/packages/app-lib/src/api/profile/mod.rs index 27869d5124..141fd6b6fa 100644 --- a/packages/app-lib/src/api/profile/mod.rs +++ b/packages/app-lib/src/api/profile/mod.rs @@ -663,7 +663,14 @@ async fn run_credentials( .filter(|hook_command| !hook_command.is_empty()); if let Some(hook) = pre_launch_hooks { // TODO: hook parameters - let mut cmd = hook.split(' '); + let mut cmd = shlex::split(hook) + .ok_or_else(|| { + crate::ErrorKind::LauncherError(format!( + "Invalid pre-launch command: {hook}", + )) + })? + .into_iter(); + if let Some(command) = cmd.next() { let full_path = get_full_path(&profile.path).await?; let result = Command::new(command) diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index 1b7a7d7e0e..db29817fb8 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -567,7 +567,19 @@ pub async fn launch_minecraft( let args = version_info.arguments.clone().unwrap_or_default(); let mut command = match wrapper { Some(hook) => { - let mut command = Command::new(hook); + let mut cmd = shlex::split(hook) + .ok_or_else(|| { + crate::ErrorKind::LauncherError(format!( + "Invalid wrapper command: {hook}", + )) + })? + .into_iter(); + let mut command = Command::new(cmd.next().ok_or( + crate::ErrorKind::LauncherError( + "Empty wrapper command".to_owned(), + ), + )?); + command.args(cmd); command.arg(&java_version.path); command } diff --git a/packages/app-lib/src/state/process.rs b/packages/app-lib/src/state/process.rs index 4cff0a33e9..0945e27cb1 100644 --- a/packages/app-lib/src/state/process.rs +++ b/packages/app-lib/src/state/process.rs @@ -743,7 +743,14 @@ impl Process { // We do not wait on the post exist command to finish running! We let it spawn + run on its own. // This behaviour may be changed in the future if let Some(hook) = post_exit_command { - let mut cmd = hook.split(' '); + let mut cmd = shlex::split(&hook) + .ok_or_else(|| { + crate::ErrorKind::LauncherError(format!( + "Invalid post-exit command: {hook}", + )) + })? + .into_iter(); + if let Some(command) = cmd.next() { let mut command = Command::new(command); command.args(cmd).current_dir( diff --git a/packages/app-lib/src/state/profiles.rs b/packages/app-lib/src/state/profiles.rs index f11eeb489c..9654e7a980 100644 --- a/packages/app-lib/src/state/profiles.rs +++ b/packages/app-lib/src/state/profiles.rs @@ -103,10 +103,11 @@ impl ProfileInstallStage { pub enum LauncherFeatureVersion { None, MigratedServerLastPlayTime, + MigratedLaunchHooks, } impl LauncherFeatureVersion { - pub const MOST_RECENT: Self = Self::MigratedServerLastPlayTime; + pub const MOST_RECENT: Self = Self::MigratedLaunchHooks; pub fn as_str(&self) -> &'static str { match *self { @@ -114,6 +115,7 @@ impl LauncherFeatureVersion { Self::MigratedServerLastPlayTime => { "migrated_server_last_play_time" } + Self::MigratedLaunchHooks => "migrated_launch_hooks", } } @@ -123,6 +125,7 @@ impl LauncherFeatureVersion { "migrated_server_last_play_time" => { Self::MigratedServerLastPlayTime } + "migrated_launch_hooks" => Self::MigratedLaunchHooks, _ => Self::None, } } @@ -781,6 +784,30 @@ impl Profile { self.launcher_feature_version = LauncherFeatureVersion::MigratedServerLastPlayTime; } + LauncherFeatureVersion::MigratedServerLastPlayTime => { + let quoter = shlex::Quoter::new().allow_nul(true); + + // Previously split by spaces + if let Some(pre_launch) = self.hooks.pre_launch.as_ref() { + self.hooks.pre_launch = + Some(quoter.join(pre_launch.split(' ')).unwrap()) + } + + // Previously treated as complete path to command + if let Some(wrapper) = self.hooks.wrapper.as_ref() { + self.hooks.wrapper = + Some(quoter.quote(wrapper).unwrap().to_string()) + } + + // Previously split by spaces + if let Some(post_exit) = self.hooks.post_exit.as_ref() { + self.hooks.post_exit = + Some(quoter.join(post_exit.split(' ')).unwrap()) + } + + self.launcher_feature_version = + LauncherFeatureVersion::MigratedLaunchHooks; + } LauncherFeatureVersion::MOST_RECENT => unreachable!( "LauncherFeatureVersion::MOST_RECENT was not updated" ),