1use std::collections::BTreeMap;
4use std::fs::File;
5use std::io::{Cursor, Read, Seek};
6use std::path::Path;
7
8use zip::ZipArchive;
9
10use crate::archive::{self, ZipError};
11use crate::error::{Error, FileOperation};
12use crate::manifest::Manifest;
13use crate::res::{EntryValue, ResourceRef, ResourceTable, Value};
14use crate::types::{Abi, LibraryName};
15
16pub const MANIFEST_LIMIT: u64 = 16 << 20;
18pub const RESOURCE_TABLE_LIMIT: u64 = 256 << 20;
20const MAX_ALIASES: usize = 8;
22
23pub struct Apk<R: Read + Seek = File> {
38 archive: ZipArchive<R>,
39 names: Vec<String>,
40}
41
42impl Apk<File> {
43 pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
44 let path = path.as_ref();
45 Self::new(File::open(path).map_err(Error::file(FileOperation::Open, path))?)
46 }
47}
48
49impl<'a> Apk<Cursor<&'a [u8]>> {
50 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
51 Self::new(Cursor::new(bytes))
52 }
53}
54
55impl<R: Read + Seek> Apk<R> {
56 pub fn new(reader: R) -> Result<Self, Error> {
57 let (archive, _) = archive::open(reader)?;
58 let names = (0..archive.len()).map(|index| archive::entry_name(&archive, index)).collect::<Result<_, _>>()?;
59 Ok(Self { archive, names })
60 }
61
62 pub fn entries(&self) -> impl Iterator<Item = &str> {
64 self.names.iter().map(String::as_str)
65 }
66
67 pub fn contains(&self, name: &str) -> bool {
69 self.archive.index_for_name(name).is_some()
70 }
71
72 pub fn read(&mut self, name: &str) -> Result<Vec<u8>, Error> {
74 self.read_limited(name, u64::MAX)
75 }
76
77 pub fn read_limited(&mut self, name: &str, limit: u64) -> Result<Vec<u8>, Error> {
80 Ok(archive::read_entry(&mut self.archive, name, limit)?)
81 }
82
83 pub fn manifest(&mut self) -> Result<Manifest, Error> {
85 read_manifest(&mut self.archive)?.ok_or_else(|| ZipError::MissingEntry { name: MANIFEST.into() }.into())
86 }
87
88 pub fn label(&mut self) -> Result<Option<String>, Error> {
91 let mut id = match self.manifest()?.label() {
92 Some(Value::String(label)) => return Ok(Some(label.clone())),
93 Some(Value::Reference(ResourceRef::Id(id))) => *id,
94 _ => return Ok(None),
95 };
96 if !self.contains(RESOURCE_TABLE) {
97 return Ok(None);
98 }
99 let bytes = self.read_limited(RESOURCE_TABLE, RESOURCE_TABLE_LIMIT)?;
100 let table = ResourceTable::parse(&bytes)?;
101 for _ in 0..MAX_ALIASES {
102 match table.get(id).and_then(|resource| resource.default_value()) {
103 Some(EntryValue::Simple(Value::String(label))) => return Ok(Some(label)),
104 Some(EntryValue::Simple(Value::Reference(ResourceRef::Id(next)))) => id = next,
105 _ => break,
106 }
107 }
108 Ok(None)
109 }
110
111 pub fn native_libraries(&self) -> BTreeMap<Abi, Vec<LibraryName>> {
113 let mut libraries: BTreeMap<Abi, Vec<LibraryName>> = BTreeMap::new();
114 for name in &self.names {
115 let Some((abi, file)) = name.strip_prefix("lib/").and_then(|rest| rest.split_once('/')) else { continue };
116 if let (Ok(abi), Ok(library)) = (abi.parse(), LibraryName::from_file_name(file)) {
117 libraries.entry(abi).or_default().push(library);
118 }
119 }
120 libraries
121 }
122}
123
124const MANIFEST: &str = "AndroidManifest.xml";
125const RESOURCE_TABLE: &str = "resources.arsc";
126
127pub(crate) fn read_manifest<R: Read + Seek>(archive: &mut ZipArchive<R>) -> Result<Option<Manifest>, Error> {
129 match archive::read_entry(archive, MANIFEST, MANIFEST_LIMIT) {
130 Ok(bytes) => Ok(Some(Manifest::decode(&bytes)?)),
131 Err(ZipError::MissingEntry { .. }) => Ok(None),
132 Err(error) => Err(error.into()),
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::archive::{ApkWriter, EntryOptions};
140 use crate::types::ArchivePath;
141
142 #[test]
143 fn reads_are_bounded_by_decompressed_size() {
144 let mut writer = ApkWriter::new(Cursor::new(Vec::new()));
145 writer.add(&ArchivePath::known("bomb.bin"), &vec![0u8; 1 << 20], EntryOptions::DEFLATED).unwrap();
147 let bytes = writer.finish().unwrap().into_inner();
148 let mut apk = Apk::from_bytes(&bytes).unwrap();
149 assert!(matches!(
150 apk.read_limited("bomb.bin", 1024),
151 Err(Error::Zip(ZipError::EntryTooLarge { limit: 1024, .. }))
152 ));
153 assert_eq!(apk.read_limited("bomb.bin", 1 << 20).unwrap().len(), 1 << 20);
154 assert!(matches!(apk.read("missing"), Err(Error::Zip(ZipError::MissingEntry { .. }))));
155 assert!(matches!(apk.manifest(), Err(Error::Zip(ZipError::MissingEntry { .. }))));
156 }
157}