verifyos_cli/parsers/
zip_extractor.rs1use std::fs;
2use std::io;
3use std::path::{Path, PathBuf};
4use tempfile::TempDir;
5use zip::ZipArchive;
6
7#[derive(Debug, thiserror::Error)]
8pub enum ExtractionError {
9 #[error("IO Error: {0}")]
10 Io(#[from] io::Error),
11 #[error("Zip Error: {0}")]
12 Zip(#[from] zip::result::ZipError),
13 #[error("Invalid IPA path: {0}")]
14 InvalidPath(PathBuf),
15}
16
17pub struct ExtractedIpa {
18 pub temp_dir: TempDir,
19 pub payload_dir: PathBuf,
20}
21
22impl ExtractedIpa {
23 pub fn get_app_bundle_path(&self) -> io::Result<Option<PathBuf>> {
24 if !self.payload_dir.exists() {
25 return Ok(None);
26 }
27
28 for entry in fs::read_dir(&self.payload_dir)? {
30 let entry = entry?;
31 let path = entry.path();
32 if path.extension().and_then(|e| e.to_str()) == Some("app") {
33 return Ok(Some(path));
34 }
35 }
36
37 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
53 Ok(None)
54 }
55
56 pub fn get_project_path(&self) -> io::Result<Option<PathBuf>> {
57 let mut project = None;
58 let mut queue = vec![self.payload_dir.clone()];
59
60 while let Some(dir) = queue.pop() {
61 if let Ok(entries) = fs::read_dir(dir) {
62 for entry in entries.flatten() {
63 let path = entry.path();
64 if path.is_dir() {
65 let extension = path.extension().and_then(|e| e.to_str());
66 if extension == Some("xcworkspace") {
67 return Ok(Some(path));
68 }
69 if project.is_none() && extension == Some("xcodeproj") {
70 project = Some(path.clone());
71 }
72 queue.push(path);
73 }
74 }
75 }
76 }
77
78 Ok(project)
79 }
80
81 pub fn discover_targets(&self) -> io::Result<Vec<(PathBuf, String)>> {
82 let mut targets = Vec::new();
83 let mut queue = vec![self.payload_dir.clone()];
84
85 while let Some(dir) = queue.pop() {
86 if let Ok(entries) = fs::read_dir(dir) {
87 for entry in entries.flatten() {
88 let path = entry.path();
89 let extension = path.extension().and_then(|e| e.to_str());
90
91 if path.is_dir() {
92 match extension {
93 Some("app") => targets.push((path.clone(), "app".to_string())),
94 Some("xcodeproj") => {
95 targets.push((path.clone(), "project".to_string()))
96 }
97 Some("xcworkspace") => {
98 targets.push((path.clone(), "workspace".to_string()))
99 }
100 _ => {
101 let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
103 if name != "node_modules"
104 && name != ".git"
105 && name != "DerivedData"
106 && name != "build"
107 {
108 queue.push(path);
109 }
110 }
111 }
112 } else if extension == Some("ipa") {
113 targets.push((path.clone(), "ipa".to_string()));
114 }
115 }
116 }
117 }
118 Ok(targets)
119 }
120}
121
122pub fn extract_ipa<P: AsRef<Path>>(ipa_path: P) -> Result<ExtractedIpa, ExtractionError> {
123 let path = ipa_path.as_ref();
124 if !path.exists() {
125 return Err(ExtractionError::InvalidPath(path.to_path_buf()));
126 }
127 let file = fs::File::open(path)?;
128 let mut archive = ZipArchive::new(file)?;
129 let temp_dir = tempfile::tempdir()?;
130 let extract_path = temp_dir.path();
131
132 archive.extract(extract_path)?;
133
134 let mut payload_dir = extract_path.join("Payload");
135 if !payload_dir.exists() {
136 payload_dir = extract_path.to_path_buf();
137 }
138
139 Ok(ExtractedIpa {
140 temp_dir,
141 payload_dir,
142 })
143}