diff --git a/flake.lock b/flake.lock index e3ac5f9..8a6c504 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -20,12 +20,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1685317056, - "narHash": "sha256-XyG7iSSrgqsnT90GZOvWWbJheagWvSon4LOwjGGFq6c=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "6b554aae1cf48cb39d4a61a51f826859027a93e2", - "type": "github" + "lastModified": 0, + "narHash": "sha256-doPgfj+7FFe9rfzWo1siAV2mVCasW+Bh8I1cToAXEE4=", + "path": "/nix/store/asymc3nsl739p1wwr0w6xbjnqs3qb94p-source", + "type": "path" }, "original": { "id": "nixpkgs", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f49f06f..8accb20 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -73,6 +73,15 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "atk" version = "0.15.1" @@ -130,12 +139,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - [[package]] name = "bitflags" version = "1.3.2" @@ -196,9 +199,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" @@ -214,9 +217,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] @@ -399,9 +402,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "convert_case" @@ -458,11 +461,26 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -583,6 +601,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "deflate64" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" + [[package]] name = "deranged" version = "0.3.11" @@ -593,6 +617,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -644,6 +679,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "dtoa" version = "1.0.9" @@ -1613,9 +1659,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "loom" @@ -1632,6 +1678,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "mac" version = "0.1.1" @@ -1952,6 +2008,7 @@ version = "0.1.0" dependencies = [ "anyhow", "hex", + "reqwest", "serde", "serde_json", "sha2", @@ -2010,17 +2067,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "pathdiff" version = "0.2.1" @@ -2029,14 +2075,12 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pbkdf2" -version = "0.11.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", - "password-hash", - "sha2", ] [[package]] @@ -3338,9 +3382,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa 1.0.10", @@ -3359,9 +3403,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -4352,50 +4396,76 @@ dependencies = [ "rustix", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zip" -version = "0.6.6" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "f1f4a27345eb6f7aa7bd015ba7eb4175fa4e1b462a29874b779e0bbcf96c6ac7" dependencies = [ "aes", - "byteorder", + "arbitrary", "bzip2", "constant_time_eq", "crc32fast", "crossbeam-utils", + "deflate64", + "displaydoc", "flate2", "hmac", + "indexmap 2.2.3", + "lzma-rs", "pbkdf2", + "rand 0.8.5", "sha1", + "thiserror", "time", + "zeroize", "zstd", ] [[package]] name = "zstd" -version = "0.11.2+zstd.1.5.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ - "libc", "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index eb47f3b..801842b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,12 +16,13 @@ tauri-build = { version = "1.3.0", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.3.0", features = [ "shell-open", "dialog-confirm", "http-api", "window-close"] } -zip = "0.6.6" +zip = { version = "1.3.0", default-features = false, features = ["aes-crypto", "deflate", "deflate64", "lzma", "bzip2", "zstd", "time"] } tokio = { version = "1", features = [ "fs" ] } -sha2 = "0.10.6" +sha2 = "0.10.8" hex = "0.4.3" -time = { version = "0.3.15", features = [ "formatting" ] } +time = { version = "0.3.36", features = [ "formatting" ] } anyhow = "1.0.71" +reqwest = "0.11.24" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs new file mode 100644 index 0000000..09b68fd --- /dev/null +++ b/src-tauri/src/config.rs @@ -0,0 +1,8 @@ +pub const DOWNLOADS_DOMAIN_WHITELIST: &[&str] = &[ + "cdn.modrinth.com", + "github.com", + "raw.githubusercontent.com", + "gitlab.com", +]; + +pub const PACK_DOMAIN_WHITELIST: &[&str] = &["cdn.modrinth.com"]; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 07777f9..71179b8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,18 +5,18 @@ use std::{ io::{Cursor, Read}, - path::{Path, PathBuf}, + mem::ManuallyDrop, + path::{Component, Path, PathBuf}, }; use anyhow::{anyhow, Context}; use mrpack::PackDependency; +use reqwest::StatusCode; use sha2::Digest; -use tauri::{ - api::http::{ClientBuilder, HttpRequestBuilder, ResponseType}, - http::status::StatusCode, - Manager, -}; -use zip::ZipArchive; +use tauri::Manager; +use tokio::{fs::File, io::AsyncWriteExt}; + +mod config; fn main() { tauri::Builder::default() @@ -30,6 +30,26 @@ fn main() { .expect("error while running tauri application"); } +struct SelfCleanupFile<'a>(ManuallyDrop, &'a Path); + +impl<'a> SelfCleanupFile<'a> { + async fn new(path: &'a Path) -> tokio::io::Result { + Ok(Self(ManuallyDrop::new(File::create(path).await?), path)) + } + + fn finalize(mut self) { + unsafe { ManuallyDrop::drop(&mut self.0) }; + std::mem::forget(self); + } +} + +impl Drop for SelfCleanupFile<'_> { + fn drop(&mut self) { + unsafe { ManuallyDrop::drop(&mut self.0) }; + _ = std::fs::remove_file(self.1); + } +} + mod mrpack; #[tauri::command] @@ -93,13 +113,20 @@ async fn get_launcher_path() -> anyhow::Result { async fn install_fabriclike( app_handle: &tauri::AppHandle, - client: &tauri::api::http::Client, + client: &reqwest::Client, profile_url: String, profile_name: &str, ) -> anyhow::Result<()> { - let profile_json = client - .send(HttpRequestBuilder::new("GET", profile_url)? - .response_type(ResponseType::Text).header("User-Agent", format!("Paigaldaja/{} (+https://github.com/Fabulously-Optimized/vanilla-installer-rust)", app_handle.package_info().version))?) + let mut profile_json = client + .get(profile_url) + .header( + "User-Agent", + format!( + "Paigaldaja/{} (+https://github.com/Fabulously-Optimized/vanilla-installer-rust)", + app_handle.package_info().version + ), + ) + .send() .await?; if profile_json.status() != StatusCode::OK { return Err(anyhow!("Metadata server did not respond with 200")); @@ -111,11 +138,11 @@ async fn install_fabriclike( if !profile_dir.is_dir() { tokio::fs::create_dir_all(&profile_dir).await?; } - tokio::fs::write( - &profile_json_path, - profile_json.read().await?.data.as_str().unwrap(), - ) - .await?; + let mut profile_json_file = SelfCleanupFile::new(&profile_json_path).await?; + while let Some(chunk) = profile_json.chunk().await? { + profile_json_file.0.write_all(&chunk).await?; + } + profile_json_file.finalize(); if profile_jar_path.is_file() { tokio::fs::remove_file(&profile_jar_path).await?; } @@ -224,41 +251,94 @@ async fn canonicalize_profile_path(profile_dir: &Option) -> anyhow::Resu async fn try_download( app_handle: &tauri::AppHandle, - client: &tauri::api::http::Client, + client: &reqwest::Client, url: &str, - path: &str, + path: &Path, expected_hash: &[u8], - profile_base_path: &Path, + expected_size: usize, ) -> anyhow::Result<()> { - let request = HttpRequestBuilder::new("GET", url)? - .response_type(ResponseType::Binary) + let mut url = reqwest::Url::parse(url).context("Invalid download URL!")?; + let host = url + .host_str() + .ok_or(anyhow!("Download URL doesn't have a host?"))?; + if !config::DOWNLOADS_DOMAIN_WHITELIST.contains(&host) { + return Err(anyhow!("Domain not allowed for download: {}", host)); + } + if url.scheme() == "http" { + url.set_scheme("https") + .map_err(|()| anyhow!("Failed to upgrade HTTP to HTTPS!"))?; + } + if url.scheme() != "https" { + return Err(anyhow!( + "Weird scheme, possibly malicious: {}", + url.scheme() + )); + } + let mut resp = client + .get(url) .header( "User-Agent", format!( "Paigaldaja/{} (+https://github.com/Fabulously-Optimized/vanilla-installer-rust)", app_handle.package_info().version ), - )?; - let resp = client.send(request).await?; + ) + .send() + .await?; if resp.status() != StatusCode::OK { return Err(anyhow!("Status code was not 200, but {}", resp.status())); } - let blob = resp.bytes().await?; - let hash = std::convert::Into::<[u8; 64]>::into(sha2::Sha512::digest(&blob.data)); - if &hash != expected_hash { + let mut size = 0usize; + let mut hasher = sha2::Sha512::new(); + let mut file = SelfCleanupFile::new(path).await?; + while let Some(chunk) = resp.chunk().await? { + size += chunk.len(); + if size > expected_size { + return Err(anyhow!( + "File is bigger than expected: expected {} bytes, aborting on {} bytes", + expected_size, + size + )); + } + hasher.update(&chunk); + file.0.write_all(&chunk).await?; + } + if size < expected_size { + return Err(anyhow!( + "File is smaller than expected: expected {} bytes, got {} bytes", + expected_size, + size + )); + } + let hash: [u8; 64] = hasher.finalize().into(); + if hash != expected_hash { return Err(anyhow!( "Wrong hash: got {}, expected {}", hex::encode(hash), hex::encode(expected_hash) )); } - if let Some(parent) = PathBuf::from(path).parent() { - tokio::fs::create_dir_all(profile_base_path.join(parent)).await?; - } - tokio::fs::write(profile_base_path.join(PathBuf::from(&path)), blob.data).await?; + file.finalize(); Ok(()) } +fn parse_and_sanitize_path(path: &str) -> Option<&Path> { + if path.contains('\0') { + return None; + } + let path = Path::new(path); + let mut depth = 0usize; + for component in path.components() { + match component { + Component::Prefix(_) | Component::RootDir => return None, + Component::ParentDir => depth = depth.checked_sub(1)?, + Component::Normal(_) => depth += 1, + Component::CurDir => (), + } + } + Some(path) +} + async fn install_mrpack_inner( app_handle: tauri::AppHandle, url: String, @@ -281,10 +361,26 @@ async fn install_mrpack_inner( let _ = app_handle.emit_all("install:progress", ("clean_old", "complete")); let _ = app_handle.emit_all("install:progress", ("load_pack", "start")); let mut written_files = vec![]; - let client = ClientBuilder::new().build().unwrap(); - let request = HttpRequestBuilder::new("GET", url) - .context("Is the .mrpack URL invalid?")? - .response_type(ResponseType::Binary) + let mut url = reqwest::Url::parse(&url).context("Invalid modpack URL!")?; + let host = url + .host_str() + .ok_or(anyhow!("Modpack URL doesn't have a host?"))?; + if !config::PACK_DOMAIN_WHITELIST.contains(&host) { + return Err(anyhow!("Domain not allowed for pack download: {}", host)); + } + if url.scheme() == "http" { + url.set_scheme("https") + .map_err(|()| anyhow!("Failed to upgrade HTTP to HTTPS!"))?; + } + if url.scheme() != "https" { + return Err(anyhow!( + "Weird scheme, possibly malicious: {}", + url.scheme() + )); + } + let client = reqwest::Client::new(); + let response = client + .get(url) .header( "User-Agent", format!( @@ -292,9 +388,7 @@ async fn install_mrpack_inner( app_handle.package_info().version ), ) - .context("Could not set request metadata")?; - let response = client - .send(request) + .send() .await .context("Failed to fetch modpack data")?; if response.status() != StatusCode::OK { @@ -303,8 +397,7 @@ async fn install_mrpack_inner( let bytes = response .bytes() .await - .context("Failed to fetch modpack data")? - .data; + .context("Failed to fetch modpack data")?; let mut mrpack = zip::ZipArchive::new(Cursor::new(bytes)).context("Failed to parse modpack file")?; let index: mrpack::PackIndex = serde_json::from_reader( @@ -329,6 +422,9 @@ async fn install_mrpack_inner( "install:progress", ("download_file", "start", i, &file.path), ); + let path = parse_and_sanitize_path(&file.path) + .ok_or(anyhow!("Possibly malicious download path: {}", file.path))?; + let path = profile_base_path.join(path); if let Some(env) = file.env { if let Some(&mrpack::SideType::Unsupported) = env.get(&mrpack::EnvType::Client) { continue; @@ -342,25 +438,38 @@ async fn install_mrpack_inner( file.path ))?, )?; + if let Some(parent) = path.parent() { + tokio::fs::create_dir_all(profile_base_path.join(parent)).await?; + } let mut success = false; + let mut last_err = None; for url in file.downloads { - if let Ok(()) = try_download( + match try_download( &app_handle, &client, &url, - &file.path, + &path, &hash, - &profile_base_path, + file.file_size as usize, ) .await { - written_files.push(PathBuf::from(&file.path)); - success = true; - break; + Ok(()) => { + written_files.push(path.to_owned()); + success = true; + break; + } + Err(e) => { + last_err.replace(e); + } } } if !success { - return Err(anyhow!("Download failed for {}", file.path)); + return Err(anyhow!( + "Download failed for {}: {}", + file.path, + last_err.unwrap() + )); } let _ = app_handle.emit_all( "install:progress", @@ -369,61 +478,60 @@ async fn install_mrpack_inner( } let _ = app_handle.emit_all("install:progress", ("download_files", "complete")); let _ = app_handle.emit_all("install:progress", ("extract_overrides", "start")); + for filename in mrpack .file_names() .map(|e| e.to_string()) .collect::>() { - // This is overly complex and only used once - // But is required to work around rust-lang/rust#63768 - fn complex_helper_function( - archive: &mut ZipArchive, - filename: &str, - ) -> anyhow::Result)>> - where - T: std::io::Read, - T: std::io::Seek, + if filename.starts_with("overrides") + && mrpack.by_name(&format!("client-{filename}")).is_ok() { - if filename.starts_with("overrides") - && archive.by_name(&("client-".to_string() + filename)).is_ok() - { - return Ok(None); - } - let mut file = archive.by_name(filename)?; - if file.is_file() { - if let Ok(path) = file.mangled_name().strip_prefix("overrides") { - let mut buf: Vec = vec![]; - file.read_to_end(&mut buf)?; - return Ok(Some((path.to_owned(), buf))); - } else if let Ok(path) = file.mangled_name().strip_prefix("client-overrides") { - let mut buf: Vec = vec![]; - file.read_to_end(&mut buf)?; - return Ok(Some((path.to_owned(), buf))); - } - } - Ok(None) + continue; } - if let Some((rel_path, buf)) = complex_helper_function(&mut mrpack, &filename) - .context("Failed to read configuration file; corrupted mrpack?")? + let mut buf: Vec; + let path: PathBuf; { - let path = profile_base_path.join(&rel_path); - if let Some(parent) = path.parent() { - tokio::fs::create_dir_all(parent).await.with_context(|| { - format!( - "Failed to create directories for configuration file {}", - rel_path.to_string_lossy() - ) - })?; + let mut file = mrpack + .by_name(&filename) + .context("Failed to read configuration file; corrupted mrpack?")?; + if file.is_dir() { + continue; } - tokio::fs::write(&path, buf).await.with_context(|| { + let path_ref = file + .enclosed_name() + .ok_or(anyhow!("Possibly malicious config path: {}", file.name()))?; + path = if let Ok(path) = path_ref + .strip_prefix("overrides") + .or_else(|_| path_ref.strip_prefix("client-overrides")) + .map(Path::to_owned) + { + path + } else { + continue; + }; + buf = vec![]; + file.read_to_end(&mut buf) + .context("Failed to read configuration file; corrupted mrpack?")?; + } + let abs_path = profile_base_path.join(&path); + if let Some(parent) = abs_path.parent() { + tokio::fs::create_dir_all(parent).await.with_context(|| { format!( - "Failed to write configuration file {}", - rel_path.to_string_lossy() + "Failed to create directories for configuration file at {}", + path.to_string_lossy() ) })?; - written_files.push(rel_path); } + tokio::fs::write(&abs_path, &buf).await.with_context(|| { + format!( + "Failed to write configuration file at {}", + abs_path.to_string_lossy() + ) + })?; + written_files.push(path); } + let _ = app_handle.emit_all("install:progress", ("extract_overrides", "complete")); let _ = app_handle.emit_all("install:progress", ("install_loader", "start")); if index.dependencies.contains_key(&PackDependency::Forge) {