Skip to content

Commit 470dbcd

Browse files
committed
feat(core): implement recursive .app bundle discovery in zip extractor
1 parent 808ed03 commit 470dbcd

2 files changed

Lines changed: 41 additions & 3 deletions

File tree

src/core/engine.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub enum OrchestratorError {
1313
Extraction(#[from] ExtractionError),
1414
#[error("Failed to parse Info.plist: {0}")]
1515
PlistParse(#[from] PlistError),
16+
#[error("Could not locate App Bundle (.app) inside {0}. Found entries: {1}")]
17+
AppBundleNotFoundWithContext(String, String),
1618
#[error("Could not locate App Bundle (.app) inside IPAPayload")]
1719
AppBundleNotFound,
1820
}
@@ -62,8 +64,23 @@ impl Engine {
6264

6365
let app_bundle_path = extracted_ipa
6466
.get_app_bundle_path()
65-
.map_err(|e| OrchestratorError::Extraction(ExtractionError::Io(e)))?
66-
.ok_or(OrchestratorError::AppBundleNotFound)?;
67+
.map_err(|e| OrchestratorError::Extraction(ExtractionError::Io(e)))?;
68+
69+
let app_bundle_path = match app_bundle_path {
70+
Some(p) => p,
71+
None => {
72+
let mut entries = Vec::new();
73+
if let Ok(rd) = std::fs::read_dir(&extracted_ipa.payload_dir) {
74+
for entry in rd.flatten().take(10) {
75+
entries.push(entry.file_name().to_string_lossy().into_owned());
76+
}
77+
}
78+
return Err(OrchestratorError::AppBundleNotFoundWithContext(
79+
extracted_ipa.payload_dir.display().to_string(),
80+
entries.join(", "),
81+
));
82+
}
83+
};
6784

6885
self.run_on_bundle(&app_bundle_path, run_started)
6986
}

src/parsers/zip_extractor.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,31 @@ impl ExtractedIpa {
2525
return Ok(None);
2626
}
2727

28+
// Try direct root search first
2829
for entry in fs::read_dir(&self.payload_dir)? {
2930
let entry = entry?;
3031
let path = entry.path();
3132
if path.extension().and_then(|e| e.to_str()) == Some("app") {
3233
return Ok(Some(path));
3334
}
3435
}
36+
37+
// Fallback: recursive search
38+
let mut queue = vec![self.payload_dir.clone()];
39+
while let Some(dir) = queue.pop() {
40+
if let Ok(entries) = fs::read_dir(dir) {
41+
for entry in entries.flatten() {
42+
let path = entry.path();
43+
if path.is_dir() {
44+
if path.extension().and_then(|e| e.to_str()) == Some("app") {
45+
return Ok(Some(path));
46+
}
47+
queue.push(path);
48+
}
49+
}
50+
}
51+
}
52+
3553
Ok(None)
3654
}
3755
}
@@ -48,7 +66,10 @@ pub fn extract_ipa<P: AsRef<Path>>(ipa_path: P) -> Result<ExtractedIpa, Extracti
4866

4967
archive.extract(extract_path)?;
5068

51-
let payload_dir = extract_path.join("Payload");
69+
let mut payload_dir = extract_path.join("Payload");
70+
if !payload_dir.exists() {
71+
payload_dir = extract_path.to_path_buf();
72+
}
5273

5374
Ok(ExtractedIpa {
5475
temp_dir,

0 commit comments

Comments
 (0)