From 067013a3e8055ce04f24048505e8f83117cbebb5 Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Tue, 23 Sep 2025 10:30:25 -0400 Subject: [PATCH 1/4] check for Info.plist inside app --- src/utils/build/validation.rs | 39 ++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/utils/build/validation.rs b/src/utils/build/validation.rs index 9b0c0b433d..cbf893f8a0 100644 --- a/src/utils/build/validation.rs +++ b/src/utils/build/validation.rs @@ -1,7 +1,9 @@ #[cfg(all(target_os = "macos", target_arch = "aarch64"))] use std::path::Path; +use walkdir::WalkDir; use anyhow::Result; +use log::error; pub fn is_zip_file(bytes: &[u8]) -> bool { if bytes.len() < 4 { @@ -63,7 +65,42 @@ where let info_plist = path.join("Info.plist"); let products_dir = path.join("Products"); - info_plist.exists() && products_dir.exists() && products_dir.is_dir() + if !info_plist.exists() { + error!("Invalid XCArchive: Missing required Info.plist file at XCArchive root"); + return false; + } + + if !products_dir.exists() || !products_dir.is_dir() { + error!("Invalid XCArchive: Missing Products/ directory"); + return false; + } + + // All .app bundles in Products/Applications/*.app should have an Info.plist file + let applications_dir = path.join("Products").join("Applications"); + if applications_dir.exists() && applications_dir.is_dir() { + for entry in WalkDir::new(&applications_dir) + .max_depth(1) + .into_iter() + .flatten() + { + let entry_path = entry.path(); + if entry_path.is_dir() + && entry_path != applications_dir + && entry_path.extension().and_then(|s| s.to_str()) == Some("app") + { + let app_info_plist = entry_path.join("Info.plist"); + if !app_info_plist.exists() { + error!( + "Invalid XCArchive: Missing required Info.plist file in .app bundle: {}", + entry_path.display() + ); + return false; + } + } + } + } + + true } /// A path is an Apple app if it points to an xarchive directory From 9a1aa5a20b1d871c524e6b82e5089520675eb3e4 Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Tue, 23 Sep 2025 10:49:25 -0400 Subject: [PATCH 2/4] lint --- src/utils/build/validation.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/utils/build/validation.rs b/src/utils/build/validation.rs index cbf893f8a0..56f140ab22 100644 --- a/src/utils/build/validation.rs +++ b/src/utils/build/validation.rs @@ -1,9 +1,7 @@ -#[cfg(all(target_os = "macos", target_arch = "aarch64"))] -use std::path::Path; -use walkdir::WalkDir; - use anyhow::Result; -use log::error; + +#[cfg(all(target_os = "macos", target_arch = "aarch64"))] +use {log::error, std::path::Path, walkdir::WalkDir}; pub fn is_zip_file(bytes: &[u8]) -> bool { if bytes.len() < 4 { From 711543ce2407d09c91baa394e5ed7e8950578a40 Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Tue, 23 Sep 2025 12:06:18 -0400 Subject: [PATCH 3/4] glob for all .app --- src/utils/build/validation.rs | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/utils/build/validation.rs b/src/utils/build/validation.rs index 56f140ab22..fa3281e58b 100644 --- a/src/utils/build/validation.rs +++ b/src/utils/build/validation.rs @@ -1,7 +1,11 @@ use anyhow::Result; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] -use {log::error, std::path::Path, walkdir::WalkDir}; +use { + glob::{glob_with, MatchOptions}, + log::error, + std::path::Path, +}; pub fn is_zip_file(bytes: &[u8]) -> bool { if bytes.len() < 4 { @@ -73,28 +77,24 @@ where return false; } - // All .app bundles in Products/Applications/*.app should have an Info.plist file - let applications_dir = path.join("Products").join("Applications"); - if applications_dir.exists() && applications_dir.is_dir() { - for entry in WalkDir::new(&applications_dir) - .max_depth(1) - .into_iter() - .flatten() - { - let entry_path = entry.path(); - if entry_path.is_dir() - && entry_path != applications_dir - && entry_path.extension().and_then(|s| s.to_str()) == Some("app") - { - let app_info_plist = entry_path.join("Info.plist"); - if !app_info_plist.exists() { - error!( - "Invalid XCArchive: Missing required Info.plist file in .app bundle: {}", - entry_path.display() - ); - return false; - } - } + // All .app bundles within the XCArchive should have an Info.plist file + let paths = match glob_with( + &path.join("Products/**/*.app").to_string_lossy(), + MatchOptions::new(), + ) { + Ok(paths) => paths, + Err(err) => { + error!("Error creating glob pattern: {}", err); + return false; + } + }; + for app_path in paths.flatten().filter(|path| path.is_dir()) { + if !app_path.join("Info.plist").exists() { + error!( + "Invalid XCArchive: Missing required Info.plist file in .app bundle: {}", + app_path.display() + ); + return false; } } From 614f6c5da278141418e4d2056b45d9245c40103e Mon Sep 17 00:00:00 2001 From: Trevor Elkins Date: Wed, 24 Sep 2025 14:21:46 -0400 Subject: [PATCH 4/4] propagate errors upward --- src/commands/build/upload.rs | 4 ++-- src/utils/build/validation.rs | 32 +++++++++++++------------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index 46e663d53d..cbcbf3aa32 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -375,7 +375,7 @@ fn validate_is_supported_build(path: &Path, bytes: &[u8]) -> Result<()> { debug!("Validating build format for: {}", path.display()); #[cfg(all(target_os = "macos", target_arch = "aarch64"))] - if is_apple_app(path) { + if is_apple_app(path)? { debug!("Detected XCArchive directory"); return Ok(()); } @@ -449,7 +449,7 @@ fn normalize_file(path: &Path, bytes: &[u8]) -> Result { fn handle_directory(path: &Path) -> Result { let temp_dir = TempDir::create()?; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] - if is_apple_app(path) { + if is_apple_app(path)? { handle_asset_catalogs(path, temp_dir.path()); } normalize_directory(path, temp_dir.path()) diff --git a/src/utils/build/validation.rs b/src/utils/build/validation.rs index fa3281e58b..984b7ff8a2 100644 --- a/src/utils/build/validation.rs +++ b/src/utils/build/validation.rs @@ -3,7 +3,6 @@ use anyhow::Result; #[cfg(all(target_os = "macos", target_arch = "aarch64"))] use { glob::{glob_with, MatchOptions}, - log::error, std::path::Path, }; @@ -57,7 +56,7 @@ pub fn is_ipa_file(bytes: &[u8]) -> Result { } #[cfg(all(target_os = "macos", target_arch = "aarch64"))] -pub fn is_xcarchive_directory

(path: P) -> bool +pub fn validate_xcarchive_directory

(path: P) -> Result<()> where P: AsRef, { @@ -68,41 +67,36 @@ where let products_dir = path.join("Products"); if !info_plist.exists() { - error!("Invalid XCArchive: Missing required Info.plist file at XCArchive root"); - return false; + anyhow::bail!("Invalid XCArchive: Missing required Info.plist file at XCArchive root"); } if !products_dir.exists() || !products_dir.is_dir() { - error!("Invalid XCArchive: Missing Products/ directory"); - return false; + anyhow::bail!("Invalid XCArchive: Missing Products/ directory"); } // All .app bundles within the XCArchive should have an Info.plist file - let paths = match glob_with( + let paths = glob_with( &path.join("Products/**/*.app").to_string_lossy(), MatchOptions::new(), - ) { - Ok(paths) => paths, - Err(err) => { - error!("Error creating glob pattern: {}", err); - return false; - } - }; + )?; for app_path in paths.flatten().filter(|path| path.is_dir()) { if !app_path.join("Info.plist").exists() { - error!( + anyhow::bail!( "Invalid XCArchive: Missing required Info.plist file in .app bundle: {}", app_path.display() ); - return false; } } - true + Ok(()) } /// A path is an Apple app if it points to an xarchive directory #[cfg(all(target_os = "macos", target_arch = "aarch64"))] -pub fn is_apple_app(path: &Path) -> bool { - path.is_dir() && is_xcarchive_directory(path) +pub fn is_apple_app(path: &Path) -> Result { + if !path.is_dir() { + return Ok(false); + } + validate_xcarchive_directory(path)?; + Ok(true) }