Skip to main content

andysu_cli/
apk.rs

1//! APK reading: zip entries and a minimal binary-XML (AXML) parser for AndroidManifest.xml.
2
3use std::io::Read;
4use std::path::Path;
5
6use crate::R;
7
8#[derive(Debug, Clone, PartialEq)]
9pub enum Val {
10    Str(String),
11    Int(i64),
12    Bool(bool),
13    Ref(u32),
14    Other(u32),
15}
16
17impl Val {
18    pub fn as_str(&self) -> Option<&str> {
19        if let Val::Str(s) = self {
20            Some(s)
21        } else {
22            None
23        }
24    }
25}
26
27#[derive(Debug, Clone)]
28pub struct Element {
29    pub name: String,
30    pub depth: usize,
31    pub attrs: Vec<(String, Val)>,
32}
33
34impl Element {
35    pub fn attr(&self, name: &str) -> Option<&Val> {
36        self.attrs.iter().find(|(n, _)| n == name).map(|(_, v)| v)
37    }
38}
39
40fn u16_at(b: &[u8], o: usize) -> R<u16> {
41    b.get(o..o + 2).map(|x| u16::from_le_bytes([x[0], x[1]])).ok_or_else(|| "AXML truncated".into())
42}
43
44fn u32_at(b: &[u8], o: usize) -> R<u32> {
45    b.get(o..o + 4).map(|x| u32::from_le_bytes([x[0], x[1], x[2], x[3]])).ok_or_else(|| "AXML truncated".into())
46}
47
48fn string_pool(b: &[u8], c: usize) -> R<Vec<String>> {
49    let count = u32_at(b, c + 8)? as usize;
50    let utf8 = u32_at(b, c + 16)? & 0x100 != 0;
51    let strings_start = u32_at(b, c + 20)? as usize;
52    let header = u16_at(b, c + 2)? as usize;
53    let mut out = Vec::with_capacity(count);
54    for i in 0..count {
55        let off = c + strings_start + u32_at(b, c + header + i * 4)? as usize;
56        let s = if utf8 {
57            let mut o = off;
58            let l16 = b[o] as usize;
59            o += if l16 & 0x80 != 0 { 2 } else { 1 };
60            let mut l8 = b[o] as usize;
61            if l8 & 0x80 != 0 {
62                l8 = ((l8 & 0x7f) << 8) | b[o + 1] as usize;
63                o += 2;
64            } else {
65                o += 1;
66            }
67            String::from_utf8_lossy(b.get(o..o + l8).ok_or("AXML string out of range")?).to_string()
68        } else {
69            let mut o = off;
70            let mut l = u16_at(b, o)? as usize;
71            o += 2;
72            if l & 0x8000 != 0 {
73                l = ((l & 0x7fff) << 16) | u16_at(b, o)? as usize;
74                o += 2;
75            }
76            let units: Vec<u16> = (0..l).map(|k| u16_at(b, o + 2 * k)).collect::<R<_>>()?;
77            String::from_utf16_lossy(&units)
78        };
79        out.push(s);
80    }
81    Ok(out)
82}
83
84/// Parse binary XML into a flat list of start elements with their depth.
85pub fn parse_axml(b: &[u8]) -> R<Vec<Element>> {
86    if u16_at(b, 0)? != 0x0003 {
87        return Err("not a binary XML file".into());
88    }
89    let mut pos = u16_at(b, 2)? as usize;
90    let mut strings = vec![];
91    let mut depth = 0usize;
92    let mut out = vec![];
93    let s = |strings: &Vec<String>, i: u32| -> String {
94        if i == u32::MAX {
95            String::new()
96        } else {
97            strings.get(i as usize).cloned().unwrap_or_default()
98        }
99    };
100    while pos + 8 <= b.len() {
101        let ty = u16_at(b, pos)?;
102        let size = u32_at(b, pos + 4)? as usize;
103        if size < 8 {
104            return Err("AXML chunk with bad size".into());
105        }
106        match ty {
107            0x0001 => strings = string_pool(b, pos)?,
108            0x0102 => {
109                let hs = u16_at(b, pos + 2)? as usize;
110                let ext = pos + hs;
111                let name = s(&strings, u32_at(b, ext + 4)?);
112                let astart = u16_at(b, ext + 8)? as usize;
113                let asize = u16_at(b, ext + 10)? as usize;
114                let acount = u16_at(b, ext + 12)? as usize;
115                let mut attrs = vec![];
116                for k in 0..acount {
117                    let a = ext + astart + k * asize;
118                    let an = s(&strings, u32_at(b, a + 4)?);
119                    let raw = u32_at(b, a + 8)?;
120                    let dtype = b[a + 15];
121                    let data = u32_at(b, a + 16)?;
122                    let v = if raw != u32::MAX {
123                        Val::Str(s(&strings, raw))
124                    } else {
125                        match dtype {
126                            0x03 => Val::Str(s(&strings, data)),
127                            0x10 | 0x11 => Val::Int(data as i32 as i64),
128                            0x12 => Val::Bool(data != 0),
129                            0x01 => Val::Ref(data),
130                            _ => Val::Other(data),
131                        }
132                    };
133                    attrs.push((an, v));
134                }
135                out.push(Element { name, depth, attrs });
136                depth += 1;
137            }
138            0x0103 => depth = depth.saturating_sub(1),
139            _ => {}
140        }
141        pos += size;
142    }
143    Ok(out)
144}
145
146/// The facts AndySU needs from a manifest.
147#[derive(Debug, Clone, Default, serde::Serialize)]
148pub struct ManifestInfo {
149    pub package: String,
150    pub version_code: i64,
151    pub version_name: Option<String>,
152    pub min_sdk: i64,
153    pub permissions: Vec<String>,
154    pub activities: Vec<String>,
155    pub services: Vec<String>,
156    pub receivers: Vec<String>,
157    pub providers: Vec<String>,
158    pub features: Vec<String>,
159    pub application_class: Option<String>,
160    pub native_activity_lib: Option<String>,
161}
162
163pub fn manifest_info(axml: &[u8]) -> R<ManifestInfo> {
164    let els = parse_axml(axml)?;
165    let mut m = ManifestInfo::default();
166    let mut in_native_activity = false;
167    for e in &els {
168        let name = || e.attr("name").and_then(|v| v.as_str()).map(String::from);
169        match e.name.as_str() {
170            "manifest" => {
171                m.package = e.attr("package").and_then(|v| v.as_str()).unwrap_or_default().to_string();
172                m.version_code = match e.attr("versionCode") {
173                    Some(Val::Int(i)) => *i,
174                    Some(Val::Str(s)) => s.parse().unwrap_or(0),
175                    _ => 0,
176                };
177                m.version_name = e.attr("versionName").and_then(|v| v.as_str()).map(String::from);
178            }
179            "uses-sdk" => {
180                if let Some(Val::Int(i)) = e.attr("minSdkVersion") {
181                    m.min_sdk = *i;
182                }
183            }
184            "uses-permission" | "uses-permission-sdk-23" => m.permissions.extend(name()),
185            "uses-feature" => {
186                let required = !matches!(e.attr("required"), Some(Val::Bool(false)));
187                if required {
188                    m.features.extend(name());
189                }
190            }
191            "application" => m.application_class = name(),
192            "activity" | "activity-alias" => {
193                let n = name().unwrap_or_default();
194                in_native_activity = n == "android.app.NativeActivity";
195                m.activities.push(n);
196            }
197            "service" => m.services.extend(name()),
198            "receiver" => m.receivers.extend(name()),
199            "provider" => m.providers.extend(name()),
200            "meta-data" if in_native_activity && name().as_deref() == Some("android.app.lib_name") => {
201                m.native_activity_lib = e.attr("value").and_then(|v| v.as_str()).map(String::from);
202            }
203            _ => {}
204        }
205    }
206    Ok(m)
207}
208
209pub struct Apk {
210    zip: zip::ZipArchive<std::fs::File>,
211}
212
213impl Apk {
214    pub fn open(p: &Path) -> R<Apk> {
215        let f = std::fs::File::open(p).map_err(|e| format!("{}: {e}", p.display()))?;
216        Ok(Apk { zip: zip::ZipArchive::new(f).map_err(|e| format!("{}: {e}", p.display()))? })
217    }
218
219    pub fn names(&self) -> Vec<String> {
220        self.zip.file_names().map(String::from).collect()
221    }
222
223    pub fn read(&mut self, name: &str) -> R<Vec<u8>> {
224        let mut e = self.zip.by_name(name).map_err(|e| format!("{name}: {e}"))?;
225        let mut v = Vec::with_capacity(e.size() as usize);
226        e.read_to_end(&mut v).map_err(|e| e.to_string())?;
227        Ok(v)
228    }
229
230    pub fn manifest(&mut self) -> R<ManifestInfo> {
231        manifest_info(&self.read("AndroidManifest.xml")?)
232    }
233}