1mod program;
13
14pub use program::Version;
15
16use memmap2::Mmap;
17use std::borrow::Cow;
18use std::fs::File;
19use std::io::Read;
20use std::path::{Path, PathBuf};
21use std::sync::Arc;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::time::SystemTime;
24
25const CD_SIG: &[u8; 4] = b"PK\x01\x02";
26const LH_SIG: &[u8; 4] = b"PK\x03\x04";
27const EOCD_SIG: &[u8; 4] = b"PK\x05\x06";
28const ZIP64_EOCD_SIG: &[u8; 4] = b"PK\x06\x06";
29const ZIP64_LOCATOR_SIG: &[u8; 4] = b"PK\x06\x07";
30const DEFLATE_CHUNK: usize = 1 << 19;
31const MAX_DEFLATE_RATIO: u64 = 1032;
34const INITIAL_CAPACITY: usize = 64 << 20;
39const TYPICAL_RATIO: usize = 16;
41
42pub const METHOD_STORED: u16 = 0;
44pub const METHOD_DEFLATE: u16 = 8;
46
47#[derive(Debug, thiserror::Error)]
49#[non_exhaustive]
50pub enum ApkError {
51 #[error("io error on {path}: {source}")]
53 Io {
54 path: PathBuf,
56 source: std::io::Error,
58 },
59 #[error("end of central directory not found")]
61 NoEocd,
62 #[error("central directory out of range")]
64 BadCentralDirectory,
65 #[error("bad local header signature for entry {0}")]
68 BadLocalHeader(String),
69 #[error("unsupported compression method {0}")]
71 UnsupportedMethod(u16),
72 #[error("inflate failed for entry {name}: {source}")]
74 Inflate {
75 name: String,
77 source: std::io::Error,
79 },
80 #[error("size mismatch for entry {name}: expected {expected}, got {actual}")]
82 SizeMismatch {
83 name: String,
85 expected: usize,
87 actual: usize,
89 },
90 #[error("entry {0} not found")]
92 NotFound(String),
93 #[error("entry {name} declares an implausible size (compressed {comp_size}, uncompressed {uncomp_size})")]
96 ImplausibleSize {
97 name: String,
99 comp_size: u64,
101 uncomp_size: u64,
103 },
104 #[error("entry {name} exceeds the inflate limit: {reason}")]
107 LimitExceeded {
108 name: String,
110 reason: String,
112 },
113 #[error("cancelled")]
115 Cancelled,
116 #[error("no archive of {} has classes*.dex ({})", path.display(), notes.join("; "))]
118 NoCode {
119 path: PathBuf,
121 notes: Vec<String>,
123 },
124}
125
126pub type Result<T> = std::result::Result<T, ApkError>;
128
129#[derive(Debug, Clone, PartialEq, Eq)]
131#[non_exhaustive]
132pub struct Entry {
133 pub name: String,
136 pub method: u16,
138 pub comp_size: u64,
140 pub uncomp_size: u64,
142 pub local_header_off: u64,
144 pub crc32: u32,
146 pub archive: usize,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154#[non_exhaustive]
155pub struct Limits {
156 pub max_entry_bytes: u64,
158 pub max_ratio: u64,
160 pub max_dex_total_bytes: u64,
162}
163
164impl Default for Limits {
165 fn default() -> Limits {
166 Limits::new()
167 }
168}
169
170impl Limits {
171 pub const fn new() -> Limits {
174 Limits { max_entry_bytes: u64::MAX, max_ratio: MAX_DEFLATE_RATIO, max_dex_total_bytes: u64::MAX }
175 }
176
177 pub const fn guarded() -> Limits {
179 Limits { max_entry_bytes: 1 << 30, max_ratio: 100, max_dex_total_bytes: 4 << 30 }
180 }
181
182 #[must_use]
184 pub const fn max_entry_bytes(self, bytes: u64) -> Limits {
185 Limits { max_entry_bytes: bytes, ..self }
186 }
187
188 #[must_use]
191 pub const fn max_ratio(self, ratio: u64) -> Limits {
192 Limits { max_ratio: ratio, ..self }
193 }
194
195 #[must_use]
198 pub const fn max_dex_total_bytes(self, bytes: u64) -> Limits {
199 Limits { max_dex_total_bytes: bytes, ..self }
200 }
201}
202
203#[derive(Clone)]
207pub struct Apk {
208 path: PathBuf,
209 archives: Arc<[Archive]>,
210 limits: Limits,
211 notes: Arc<[String]>,
213 version: Version,
214}
215
216#[derive(Debug, Clone, Copy)]
218#[non_exhaustive]
219pub struct ArchiveId<'a> {
220 pub path: &'a Path,
222 pub label: &'a str,
225 pub directory: &'a [u8],
227}
228
229struct Archive {
231 path: PathBuf,
232 map: Data,
233 label: String,
237 bare_dex: bool,
239}
240
241enum Data {
244 Mapped(Mmap),
245 Window {
247 outer: Arc<[Archive]>,
248 archive: usize,
249 range: std::ops::Range<usize>,
250 },
251 Owned(Vec<u8>),
253}
254
255impl std::ops::Deref for Data {
256 type Target = [u8];
257
258 fn deref(&self) -> &[u8] {
259 match self {
260 Data::Mapped(map) => map,
261 Data::Window { outer, archive, range } => &outer[*archive].map[range.clone()],
263 Data::Owned(bytes) => bytes,
264 }
265 }
266}
267
268#[inline]
271fn bytes_at(b: &[u8], o: usize, n: usize) -> Option<&[u8]> {
272 b.get(o..o.checked_add(n)?)
273}
274#[inline]
275fn u16_at(b: &[u8], o: usize) -> Option<u16> {
276 bytes_at(b, o, 2).map(|s| u16::from_le_bytes([s[0], s[1]]))
277}
278#[inline]
279fn u32_at(b: &[u8], o: usize) -> Option<u32> {
280 bytes_at(b, o, 4).map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
281}
282#[inline]
283fn u64_at(b: &[u8], o: usize) -> Option<u64> {
284 bytes_at(b, o, 8).map(|s| u64::from_le_bytes(s.try_into().expect("8 bytes")))
285}
286#[inline]
289fn offset(v: u64) -> Option<usize> {
290 usize::try_from(v).ok()
291}
292
293pub fn archive_of(dex_name: &str) -> Option<&str> {
297 let entry = dex_name.split('!').next().unwrap_or(dex_name);
298 entry.rsplit_once('/').map(|(archive, _)| archive)
299}
300
301fn is_root_dex(name: &[u8]) -> bool {
304 name.ends_with(b".dex") && !name.contains(&b'/')
305}
306
307fn is_any_dex(name: &[u8]) -> bool {
310 name.ends_with(b".dex")
311}
312
313fn multidex_rank(name: &str) -> u32 {
317 let file = name.rsplit('/').next().unwrap_or(name);
318 match file.strip_prefix("classes").and_then(|rest| rest.strip_suffix(".dex")) {
319 Some("") => 1,
320 Some(n) if n.bytes().all(|b| b.is_ascii_digit()) && !n.starts_with('0') => n.parse().unwrap_or(u32::MAX),
321 _ => u32::MAX,
322 }
323}
324
325const BARE_DEX_ENTRY: &str = "classes.dex";
327const DEX_HEADER_SIZE: usize = 0x70;
329
330impl Archive {
331 fn open(path: PathBuf, label: String) -> Result<Archive> {
332 let file = File::open(&path).map_err(|source| ApkError::Io { path: path.clone(), source })?;
333 let map = unsafe { Mmap::map(&file) }.map_err(|source| ApkError::Io { path: path.clone(), source })?;
336 let bare_dex = map.starts_with(b"dex\n");
337 Ok(Archive { path, map: Data::Mapped(map), label, bare_dex })
338 }
339
340 fn entry_name(&self, inner: &[u8]) -> String {
343 let inner = String::from_utf8_lossy(inner);
344 if self.label.is_empty() { inner.into_owned() } else { format!("{}/{inner}", self.label) }
345 }
346
347 fn inner_name<'n>(&self, name: &'n str) -> Option<&'n str> {
350 if self.label.is_empty() {
351 return None;
352 }
353 name.strip_prefix(self.label.as_str())?.strip_prefix('/')
354 }
355
356 fn central_directory(&self) -> Result<(usize, usize)> {
358 let data = &self.map[..];
359 let search_start = data.len().saturating_sub(65536 + 22);
360 let eocd = memchr::memmem::rfind(&data[search_start..], EOCD_SIG).map(|p| p + search_start).ok_or(ApkError::NoEocd)?;
361 let mut cd_size = u32_at(data, eocd + 12).ok_or(ApkError::NoEocd)? as u64;
362 let mut cd_off = u32_at(data, eocd + 16).ok_or(ApkError::NoEocd)? as u64;
363 if cd_size == 0xFFFF_FFFF || cd_off == 0xFFFF_FFFF {
364 if eocd >= 20 && &data[eocd - 20..eocd - 16] == ZIP64_LOCATOR_SIG {
366 let rec = u64_at(data, eocd - 20 + 8).and_then(offset).ok_or(ApkError::NoEocd)?;
367 if bytes_at(data, rec, 4) == Some(ZIP64_EOCD_SIG) {
368 cd_size = rec.checked_add(40).and_then(|o| u64_at(data, o)).ok_or(ApkError::NoEocd)?;
369 cd_off = rec.checked_add(48).and_then(|o| u64_at(data, o)).ok_or(ApkError::NoEocd)?;
370 }
371 }
372 }
373 let (Some(off), Some(size)) = (offset(cd_off), offset(cd_size)) else {
374 return Err(ApkError::BadCentralDirectory);
375 };
376 if off.checked_add(size).map(|e| e > data.len()).unwrap_or(true) {
377 return Err(ApkError::BadCentralDirectory);
378 }
379 Ok((off, size))
380 }
381
382 fn directory(&self) -> Result<&[u8]> {
385 if self.bare_dex {
386 return Ok(&self.map[..self.map.len().min(DEX_HEADER_SIZE)]);
387 }
388 let (off, size) = self.central_directory()?;
389 Ok(&self.map[off..off + size])
390 }
391
392 fn entries_filtered(&self, index: usize, keep: &mut impl FnMut(&[u8]) -> bool) -> Result<Vec<Entry>> {
395 if self.bare_dex {
396 if !keep(BARE_DEX_ENTRY.as_bytes()) {
397 return Ok(Vec::new());
398 }
399 let len = self.map.len() as u64;
400 return Ok(vec![Entry {
401 name: self.entry_name(BARE_DEX_ENTRY.as_bytes()),
402 method: METHOD_STORED,
403 comp_size: len,
404 uncomp_size: len,
405 local_header_off: 0,
406 crc32: 0,
407 archive: index,
408 }]);
409 }
410 let data = &self.map[..];
411 let (cd_off, cd_size) = self.central_directory()?;
412 let cd_end = cd_off + cd_size;
413 let mut entries = Vec::new();
414 let mut p = cd_off;
415 while p + 46 <= cd_end {
416 if &data[p..p + 4] != CD_SIG {
417 break;
418 }
419 let name_len = u16_at(data, p + 28).unwrap_or(0) as usize;
420 let extra_len = u16_at(data, p + 30).unwrap_or(0) as usize;
421 let comment_len = u16_at(data, p + 32).unwrap_or(0) as usize;
422 let name_start = p + 46;
423 let name_end = name_start + name_len;
424 if name_end > cd_end {
425 break;
426 }
427 let name = &data[name_start..name_end];
428 if keep(name) {
429 let mut comp_size = u32_at(data, p + 20).unwrap_or(0) as u64;
430 let mut uncomp_size = u32_at(data, p + 24).unwrap_or(0) as u64;
431 let mut local_header_off = u32_at(data, p + 42).unwrap_or(0) as u64;
432 if comp_size == 0xFFFF_FFFF || uncomp_size == 0xFFFF_FFFF || local_header_off == 0xFFFF_FFFF {
433 let mut q = name_end;
435 let extra_end = name_end + extra_len;
436 while q + 4 <= extra_end {
437 let id = u16_at(data, q).unwrap_or(0);
438 let len = u16_at(data, q + 2).unwrap_or(0) as usize;
439 if id == 0x0001 {
440 let mut r = q + 4;
441 if uncomp_size == 0xFFFF_FFFF {
442 uncomp_size = u64_at(data, r).unwrap_or(0);
443 r += 8;
444 }
445 if comp_size == 0xFFFF_FFFF {
446 comp_size = u64_at(data, r).unwrap_or(0);
447 r += 8;
448 }
449 if local_header_off == 0xFFFF_FFFF {
450 local_header_off = u64_at(data, r).unwrap_or(0);
451 }
452 break;
453 }
454 q += 4 + len;
455 }
456 }
457 entries.push(Entry {
458 name: self.entry_name(name),
459 method: u16_at(data, p + 10).unwrap_or(0),
460 comp_size,
461 uncomp_size,
462 local_header_off,
463 crc32: u32_at(data, p + 16).unwrap_or(0),
464 archive: index,
465 });
466 }
467 p = name_end + extra_len + comment_len;
468 }
469 Ok(entries)
470 }
471
472 fn dex_entries_in_order(&self, index: usize) -> Result<Vec<Entry>> {
475 self.dex_entries_matching(index, is_root_dex)
476 }
477
478 fn dex_entries_matching(&self, index: usize, mut is_dex: fn(&[u8]) -> bool) -> Result<Vec<Entry>> {
481 let mut entries = self.entries_filtered(index, &mut is_dex)?;
482 entries.reverse();
484 let mut seen = std::collections::HashSet::with_capacity(entries.len());
485 entries.retain(|e| seen.insert(e.name.clone()));
486 entries.reverse();
487 Ok(entries)
488 }
489}
490
491impl Apk {
492 pub fn open(path: impl AsRef<Path>) -> Result<Apk> {
512 let path = path.as_ref().to_path_buf();
513 let sources = program::sources(&path)?;
514 let version = Version::of_sources(&sources);
515 if let [lone] = &sources[..] {
516 let archives = Arc::new([Archive::open(lone.path.clone(), String::new())?]);
517 return Ok(Apk { path, archives, limits: Limits::default(), notes: Arc::new([]), version });
518 }
519 let mut archives = Vec::with_capacity(sources.len());
520 let mut notes = Vec::new();
521 for s in sources {
522 let archive = match Archive::open(s.path, s.label) {
523 Ok(a) => a,
524 Err(e) => {
525 notes.push(format!("skipped: {e}"));
526 continue;
527 }
528 };
529 match archive.dex_entries_in_order(archives.len()) {
530 Ok(dex) if dex.is_empty() => notes.push(format!("{}: no classes*.dex (odex/vdex only?): deodex first", archive.label)),
531 Ok(_) => archives.push(archive),
532 Err(e) => notes.push(format!("{}: skipped: {e}", archive.label)),
533 }
534 }
535 if archives.is_empty() {
536 return Err(ApkError::NoCode { path, notes });
537 }
538 if let [lone] = &mut archives[..] {
539 lone.label.clear();
540 }
541 Ok(Apk { path, archives: archives.into(), limits: Limits::default(), notes: notes.into(), version })
542 }
543
544 pub fn from_bytes(path: impl Into<PathBuf>, bytes: Vec<u8>) -> Apk {
548 let path = path.into();
549 let bare_dex = bytes.starts_with(b"dex\n");
550 let archive = Archive { path: path.clone(), map: Data::Owned(bytes), label: String::new(), bare_dex };
551 Apk { path, archives: Arc::new([archive]), limits: Limits::default(), notes: Arc::new([]), version: Version::in_memory() }
552 }
553
554 pub fn version_of(path: impl AsRef<Path>) -> Result<Version> {
564 Ok(Version::of_sources(&program::sources(path.as_ref())?))
565 }
566
567 pub fn version(&self) -> &Version {
569 &self.version
570 }
571
572 pub fn resolve(spec: &Path, base: &Path) -> PathBuf {
575 program::resolve(spec, base)
576 }
577
578 pub fn with_limits(mut self, limits: Limits) -> Apk {
580 self.limits = limits;
581 self
582 }
583
584 pub fn path(&self) -> &Path {
586 &self.path
587 }
588
589 pub fn limits(&self) -> Limits {
591 self.limits
592 }
593
594 pub fn archive_count(&self) -> usize {
597 self.archives.len()
598 }
599
600 pub fn notes(&self) -> &[String] {
602 &self.notes
603 }
604
605 pub fn archive_path(&self, entry: &Entry) -> &Path {
607 &self.archive(entry).path
608 }
609
610 pub fn archive_data(&self, entry: &Entry) -> &[u8] {
612 &self.archive(entry).map
613 }
614
615 fn archive(&self, entry: &Entry) -> &Archive {
616 self.archives.get(entry.archive).expect("entry of this APK")
617 }
618
619 pub fn directories(&self) -> Result<Vec<ArchiveId<'_>>> {
627 self.archives.iter().map(|a| Ok(ArchiveId { path: &a.path, label: &a.label, directory: a.directory()? })).collect()
628 }
629
630 pub fn stem(&self) -> String {
633 let first = self.archives[0].path.file_stem().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
634 match self.archives.len() {
635 1 => first,
636 n => format!("{first}-and-{}", n - 1),
637 }
638 }
639
640 pub fn stamp(&self) -> Result<(u64, Option<SystemTime>)> {
648 let meta = |a: &Archive| std::fs::metadata(&a.path).map_err(|source| ApkError::Io { path: a.path.clone(), source });
649 if let [lone] = &self.archives[..] {
650 let m = meta(lone)?;
651 return Ok((m.len(), m.modified().ok()));
652 }
653 let mut size = 0u64;
654 let mut latest = None;
655 for a in self.archives.iter() {
656 let m = meta(a)?;
657 let since = m.modified().ok().and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok());
658 let nanos = since.map_or(0, |d| d.as_nanos() as u64);
660 size = size.rotate_left(13) ^ m.len().wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ nanos;
661 latest = latest.max(m.modified().ok());
662 }
663 Ok((size, latest))
664 }
665
666 pub fn entries_filtered(&self, mut keep: impl FnMut(&[u8]) -> bool) -> Result<Vec<Entry>> {
674 let mut entries = Vec::new();
675 for (i, a) in self.archives.iter().enumerate() {
676 entries.extend(a.entries_filtered(i, &mut keep)?);
677 }
678 Ok(entries)
679 }
680
681 pub fn entries(&self) -> Result<Vec<Entry>> {
687 self.entries_filtered(|_| true)
688 }
689
690 pub fn dex_entries(&self) -> Result<Vec<Entry>> {
699 let mut entries = self.dex_entries_in_order()?;
700 self.check_dex_total(&entries)?;
701 entries.sort_by_key(|e| e.comp_size);
702 Ok(entries)
703 }
704
705 pub fn dex_entries_in_order(&self) -> Result<Vec<Entry>> {
712 let mut entries = Vec::new();
713 for (i, a) in self.archives.iter().enumerate() {
714 entries.extend(a.dex_entries_in_order(i)?);
715 }
716 Ok(entries)
717 }
718
719 pub fn dex_entries_multidex(&self) -> Result<Vec<Entry>> {
728 let mut entries = self.dex_entries_in_order()?;
729 self.check_dex_total(&entries)?;
730 entries.sort_by_key(|e| (e.archive, multidex_rank(&e.name)));
732 Ok(entries)
733 }
734
735 pub fn all_dex_entries(&self) -> Result<Vec<Entry>> {
747 let mut entries = Vec::new();
748 for (i, a) in self.archives.iter().enumerate() {
749 entries.extend(a.dex_entries_matching(i, is_any_dex)?);
750 }
751 Ok(entries)
752 }
753
754 fn check_dex_total(&self, entries: &[Entry]) -> Result<()> {
755 let total = entries.iter().fold(0u64, |t, e| t.saturating_add(e.uncomp_size));
756 if total > self.limits.max_dex_total_bytes {
757 return Err(ApkError::LimitExceeded {
758 name: "classes*.dex".into(),
759 reason: format!("DEX entries declare {total} bytes in all, limit {}", self.limits.max_dex_total_bytes),
760 });
761 }
762 Ok(())
763 }
764
765 pub fn find_entry(&self, name: &str) -> Result<Option<Entry>> {
773 let owner = self.archives.iter().enumerate().find_map(|(i, a)| Some((i, a.inner_name(name)?)));
774 let candidates: Vec<(usize, &str)> = match owner {
775 Some(found) => vec![found],
776 None => (0..self.archives.len()).map(|i| (i, name)).collect(),
777 };
778 for (i, inner) in candidates {
779 let mut found = self.archives[i].entries_filtered(i, &mut |n: &[u8]| n == inner.as_bytes())?;
780 if let Some(e) = found.pop() {
781 return Ok(Some(e));
782 }
783 }
784 Ok(None)
785 }
786
787 pub fn raw_payload(&self, entry: &Entry) -> Result<&[u8]> {
794 let range = self.payload_range(entry)?;
795 Ok(&self.archive(entry).map[range])
796 }
797
798 fn payload_range(&self, entry: &Entry) -> Result<std::ops::Range<usize>> {
800 let archive = self.archive(entry);
801 let data = &archive.map[..];
802 let bad = || ApkError::BadLocalHeader(entry.name.clone());
803 if archive.bare_dex {
804 return offset(entry.comp_size).filter(|&n| n <= data.len()).map(|n| 0..n).ok_or_else(bad);
805 }
806 let lh = offset(entry.local_header_off).ok_or_else(bad)?;
807 if bytes_at(data, lh, 4) != Some(LH_SIG) {
808 return Err(bad());
809 }
810 let name_len = u16_at(data, lh + 26).ok_or_else(bad)? as usize;
812 let extra_len = u16_at(data, lh + 28).ok_or_else(bad)? as usize;
813 let start = lh + 30 + name_len + extra_len;
814 let end = offset(entry.comp_size).and_then(|n| start.checked_add(n));
815 end.filter(|&end| start <= end && end <= data.len()).map(|end| start..end).ok_or_else(bad)
816 }
817
818 pub fn lone_data(&self) -> Option<&[u8]> {
822 match &self.archives[..] {
823 [lone] => Some(&lone.map),
824 _ => None,
825 }
826 }
827
828 pub fn owned_bytes(&self) -> usize {
831 self.archives.iter().map(|archive| if let Data::Owned(bytes) = &archive.map { bytes.capacity() } else { 0 }).sum()
832 }
833
834 pub fn open_nested(&self, entry: &Entry) -> Result<Apk> {
844 let EntryBytes(map) = self.read_entry_owned(entry)?;
845 let bare_dex = map.starts_with(b"dex\n");
846 let nested = Archive { path: self.archive(entry).path.clone(), map, label: String::new(), bare_dex };
847 Ok(Apk {
848 path: self.path.clone(),
849 archives: Arc::new([nested]),
850 limits: self.limits,
851 notes: Arc::new([]),
852 version: self.version.clone(),
853 })
854 }
855
856 pub fn entry_range(&self, entry: &Entry) -> Result<Option<std::ops::Range<usize>>> {
868 if entry.method != METHOD_STORED {
869 return Ok(None);
870 }
871 let range = self.payload_range(entry)?;
872 self.check_declared_size(entry)?;
873 self.check_stored_size(entry, range.len())?;
874 Ok(Some(range))
875 }
876
877 pub fn read_entry_owned(&self, entry: &Entry) -> Result<EntryBytes> {
885 let data = match self.entry_range(entry)? {
886 Some(range) => Data::Window { outer: Arc::clone(&self.archives), archive: entry.archive, range },
887 None => Data::Owned(self.read_entry(entry)?.into_owned()),
888 };
889 Ok(EntryBytes(data))
890 }
891
892 pub fn read_entry(&self, entry: &Entry) -> Result<Cow<'_, [u8]>> {
899 self.read_entry_cancellable(entry, None)
900 }
901
902 pub fn read_entry_cancellable(&self, entry: &Entry, cancel: Option<&AtomicBool>) -> Result<Cow<'_, [u8]>> {
914 if cancel.is_some_and(|c| c.load(Ordering::Relaxed)) {
915 return Err(ApkError::Cancelled);
916 }
917 let payload = self.raw_payload(entry)?;
918 let bound = self.check_declared_size(entry)?;
919 let out = match entry.method {
920 METHOD_STORED => {
921 self.check_stored_size(entry, payload.len())?;
922 return Ok(Cow::Borrowed(payload));
923 }
924 #[cfg(feature = "libdeflate")]
925 METHOD_DEFLATE if entry.uncomp_size > 0 => {
926 if let Some(c) = cancel
927 && c.load(Ordering::Relaxed)
928 {
929 return Err(ApkError::Cancelled);
930 }
931 let mut out = vec![0u8; entry.uncomp_size as usize];
932 let n = libdeflater::Decompressor::new()
933 .deflate_decompress(payload, &mut out)
934 .map_err(|e| ApkError::Inflate { name: entry.name.clone(), source: std::io::Error::other(format!("{e:?}")) })?;
935 out.truncate(n);
936 out
937 }
938 METHOD_DEFLATE => {
939 let reader = CancelReader { data: payload, pos: 0, cancel };
940 let decoder = flate2::read::DeflateDecoder::new(reader);
941 let reserve = (entry.uncomp_size as usize).min(INITIAL_CAPACITY.max(payload.len().saturating_mul(TYPICAL_RATIO)));
942 let mut out = Vec::with_capacity(reserve);
943 match decoder.take(bound.saturating_add(1)).read_to_end(&mut out) {
947 Ok(_) => {}
948 Err(_) if cancel.is_some_and(|c| c.load(Ordering::Relaxed)) => return Err(ApkError::Cancelled),
949 Err(e) => return Err(ApkError::Inflate { name: entry.name.clone(), source: e }),
950 }
951 if out.len() as u64 > bound {
952 return Err(self.past_bound(entry, bound));
953 }
954 out
955 }
956 m => return Err(ApkError::UnsupportedMethod(m)),
957 };
958 if let Some(c) = cancel
959 && c.load(Ordering::Relaxed)
960 {
961 return Err(ApkError::Cancelled);
962 }
963 if entry.uncomp_size != 0 && out.len() as u64 != entry.uncomp_size {
964 return Err(ApkError::SizeMismatch { name: entry.name.clone(), expected: entry.uncomp_size as usize, actual: out.len() });
965 }
966 Ok(Cow::Owned(out))
967 }
968
969 fn check_stored_size(&self, entry: &Entry, len: usize) -> Result<()> {
972 if entry.uncomp_size != 0 && len as u64 != entry.uncomp_size {
973 return Err(ApkError::SizeMismatch { name: entry.name.clone(), expected: entry.uncomp_size as usize, actual: len });
974 }
975 Ok(())
976 }
977
978 fn check_declared_size(&self, entry: &Entry) -> Result<u64> {
983 let bound = entry.comp_size.saturating_mul(MAX_DEFLATE_RATIO).saturating_add(1024);
984 if entry.method == METHOD_DEFLATE && entry.uncomp_size > bound {
985 return Err(self.implausible(entry));
986 }
987 let Limits { max_entry_bytes, max_ratio, .. } = self.limits;
988 if entry.uncomp_size > max_entry_bytes {
989 return Err(self.over_limit(entry, format!("declares {} bytes, limit {max_entry_bytes}", entry.uncomp_size)));
990 }
991 let ratio_bound = entry.comp_size.saturating_mul(max_ratio).saturating_add(1024);
992 if entry.method == METHOD_DEFLATE && entry.uncomp_size > ratio_bound {
993 return Err(self.over_limit(
994 entry,
995 format!("declares {} bytes from {} compressed, ratio limit {max_ratio}", entry.uncomp_size, entry.comp_size),
996 ));
997 }
998 Ok(bound.min(ratio_bound).min(max_entry_bytes))
999 }
1000
1001 fn past_bound(&self, entry: &Entry, bound: u64) -> ApkError {
1003 if bound < entry.comp_size.saturating_mul(MAX_DEFLATE_RATIO).saturating_add(1024) {
1004 self.over_limit(entry, format!("inflates past {bound} bytes"))
1005 } else {
1006 self.implausible(entry)
1007 }
1008 }
1009
1010 fn over_limit(&self, entry: &Entry, reason: String) -> ApkError {
1011 ApkError::LimitExceeded { name: entry.name.clone(), reason }
1012 }
1013
1014 fn implausible(&self, entry: &Entry) -> ApkError {
1015 ApkError::ImplausibleSize { name: entry.name.clone(), comp_size: entry.comp_size, uncomp_size: entry.uncomp_size }
1016 }
1017
1018 pub fn read(&self, name: &str) -> Result<Vec<u8>> {
1025 let entry = self.find_entry(name)?.ok_or_else(|| ApkError::NotFound(name.to_string()))?;
1026 Ok(self.read_entry(&entry)?.into_owned())
1027 }
1028}
1029
1030pub struct EntryBytes(Data);
1034
1035impl EntryBytes {
1036 pub fn is_mapped(&self) -> bool {
1039 matches!(self.0, Data::Window { .. })
1040 }
1041}
1042
1043impl std::ops::Deref for EntryBytes {
1044 type Target = [u8];
1045
1046 fn deref(&self) -> &[u8] {
1047 &self.0
1048 }
1049}
1050
1051impl AsRef<[u8]> for EntryBytes {
1052 fn as_ref(&self) -> &[u8] {
1053 &self.0
1054 }
1055}
1056
1057impl std::fmt::Debug for EntryBytes {
1058 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1059 f.debug_struct("EntryBytes").field("len", &self.len()).field("mapped", &self.is_mapped()).finish()
1060 }
1061}
1062
1063#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1065pub enum PrefixStep {
1066 Continue(usize),
1069 Abort,
1071}
1072
1073#[derive(Debug, Clone, PartialEq, Eq)]
1075pub enum Prefix {
1076 Complete(Vec<u8>),
1078 Partial(Vec<u8>),
1080 Aborted(Vec<u8>),
1082}
1083
1084impl Apk {
1085 pub fn inflate_until(&self, entry: &Entry, mut decide: impl FnMut(&[u8]) -> PrefixStep) -> Result<Prefix> {
1094 const FIRST_CHUNK: usize = 1 << 14;
1096 const CHUNK: usize = 1 << 18;
1097 let mut chunk = FIRST_CHUNK;
1098 if entry.method != METHOD_DEFLATE {
1099 return Ok(Prefix::Complete(self.read_entry(entry)?.into_owned()));
1100 }
1101 let compressed = self.raw_payload(entry)?;
1102 let bound = self.check_declared_size(entry)?;
1103 let mut d = flate2::Decompress::new(false);
1104 let mut out: Vec<u8> = Vec::with_capacity((entry.uncomp_size as usize).min(4 * CHUNK));
1105 let mut target: Option<usize> = None;
1106 loop {
1107 if out.len() as u64 > bound {
1108 return Err(self.past_bound(entry, bound));
1109 }
1110 let base = out.len();
1111 out.resize(base + chunk, 0);
1112 chunk = (chunk * 2).min(CHUNK);
1113 let consumed = d.total_in() as usize;
1114 let status = d
1115 .decompress(&compressed[consumed.min(compressed.len())..], &mut out[base..], flate2::FlushDecompress::None)
1116 .map_err(|e| ApkError::Inflate { name: entry.name.clone(), source: std::io::Error::other(e) })?;
1117 let written = d.total_out() as usize - base;
1118 out.truncate(base + written);
1119 if target.is_none() {
1120 match decide(&out) {
1121 PrefixStep::Continue(0) => {}
1122 PrefixStep::Continue(limit) => {
1123 target = Some(if entry.uncomp_size == 0 { limit } else { limit.min(entry.uncomp_size as usize) })
1125 }
1126 PrefixStep::Abort => return Ok(Prefix::Aborted(out)),
1127 }
1128 }
1129 if status == flate2::Status::StreamEnd {
1130 return Ok(Prefix::Complete(out));
1131 }
1132 if let Some(limit) = target
1133 && out.len() >= limit
1134 {
1135 return Ok(Prefix::Partial(out));
1136 }
1137 if written == 0 && d.total_in() as usize == consumed {
1138 return Err(ApkError::Inflate { name: entry.name.clone(), source: std::io::Error::other("deflate made no progress") });
1139 }
1140 }
1141 }
1142}
1143
1144struct CancelReader<'a> {
1146 data: &'a [u8],
1147 pos: usize,
1148 cancel: Option<&'a AtomicBool>,
1149}
1150
1151impl Read for CancelReader<'_> {
1152 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1153 if let Some(c) = self.cancel
1154 && c.load(Ordering::Relaxed)
1155 {
1156 return Err(std::io::Error::other("cancelled"));
1157 }
1158 let remaining = &self.data[self.pos..];
1159 let n = remaining.len().min(buf.len()).min(DEFLATE_CHUNK);
1160 buf[..n].copy_from_slice(&remaining[..n]);
1161 self.pos += n;
1162 Ok(n)
1163 }
1164}
1165
1166#[cfg(test)]
1167mod tests {
1168 use super::*;
1169 use std::io::Write;
1170
1171 fn write_temp(bytes: &[u8]) -> tempfile::NamedTempFile {
1172 let mut f = tempfile::NamedTempFile::new().unwrap();
1173 f.write_all(bytes).unwrap();
1174 f.flush().unwrap();
1175 f
1176 }
1177
1178 #[test]
1179 fn limits_reject_bombs_before_inflating() {
1180 let bomb = vec![0u8; 4 << 20];
1182 let f = write_temp(&asc_fixture::make_apk(&[("classes.dex", &bomb)], true));
1183 let apk = Apk::open(f.path()).unwrap();
1184 let entry = apk.dex_entries().unwrap().remove(0);
1185 assert_eq!(apk.read_entry(&entry).unwrap().len(), bomb.len());
1186 let apk = Apk::open(f.path()).unwrap().with_limits(Limits::guarded());
1187 let err = apk.read_entry(&entry).unwrap_err();
1188 assert!(matches!(&err, ApkError::LimitExceeded { reason, .. } if reason.contains("ratio limit 100")), "{err}");
1189 let mut undeclared = entry.clone();
1191 undeclared.uncomp_size = 0;
1192 assert!(matches!(apk.read_entry(&undeclared), Err(ApkError::LimitExceeded { .. })));
1193 let small = Limits { max_entry_bytes: 1 << 20, ..Limits::default() };
1194 let apk = Apk::open(f.path()).unwrap().with_limits(small);
1195 assert!(matches!(apk.read_entry(&entry), Err(ApkError::LimitExceeded { .. })));
1196 let total = Limits { max_dex_total_bytes: 1 << 20, ..Limits::default() };
1197 let apk = Apk::open(f.path()).unwrap().with_limits(total);
1198 assert!(matches!(apk.dex_entries(), Err(ApkError::LimitExceeded { .. })));
1199 }
1200
1201 #[test]
1202 fn enumerates_and_inflates_dex_entries() {
1203 let dex = asc_fixture::make_dex();
1204 for deflate in [false, true] {
1205 let apk_bytes = asc_fixture::make_apk(
1206 &[("classes.dex", &dex), ("lib/x/classes.dex", &dex), ("classes2.dex", &dex), ("AndroidManifest.xml", b"xml")],
1207 deflate,
1208 );
1209 let f = write_temp(&apk_bytes);
1210 let apk = Apk::open(f.path()).unwrap();
1211 let entries = apk.dex_entries().unwrap();
1212 let names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
1213 assert_eq!(names.len(), 2);
1214 assert!(names.contains(&"classes.dex") && names.contains(&"classes2.dex"));
1215 for e in &entries {
1216 assert_eq!(apk.read_entry(e).unwrap().as_ref(), dex.as_slice());
1217 }
1218 assert_eq!(apk.read("AndroidManifest.xml").unwrap(), b"xml");
1219 let cancel = AtomicBool::new(true);
1220 let r = apk.read_entry_cancellable(&entries[0], Some(&cancel));
1221 assert!(matches!(r, Err(ApkError::Cancelled)));
1222 if deflate {
1223 let mut lying = entries[0].clone();
1225 lying.uncomp_size = 1 << 40;
1226 assert!(matches!(apk.read_entry(&lying), Err(ApkError::ImplausibleSize { .. })));
1227 assert!(matches!(apk.inflate_until(&lying, |_| PrefixStep::Continue(0)), Err(ApkError::ImplausibleSize { .. })));
1228 let mut truncated = entries[0].clone();
1229 truncated.comp_size = u64::MAX;
1230 assert!(matches!(apk.read_entry(&truncated), Err(ApkError::BadLocalHeader(_))));
1231 }
1232 }
1233 }
1234
1235 #[test]
1236 fn entries_outlive_their_apk_in_place_or_inflated() {
1237 fn assert_send_sync<T: Send + Sync>() {}
1238 assert_send_sync::<EntryBytes>();
1239 let dex = asc_fixture::make_dex();
1240 for deflate in [false, true] {
1241 let f = write_temp(&asc_fixture::make_apk(&[("classes.dex", &dex), ("a.txt", b"text")], deflate));
1242 let apk = Apk::open(f.path()).unwrap();
1243 let entry = apk.find_entry("classes.dex").unwrap().unwrap();
1244 let range = apk.entry_range(&entry).unwrap();
1245 assert_eq!(range.is_some(), !deflate);
1246 if let Some(range) = &range {
1247 let Cow::Borrowed(read) = apk.read_entry(&entry).unwrap() else { panic!("a stored entry is borrowed") };
1248 assert_eq!(&apk.archive_data(&entry)[range.clone()], read);
1249 assert_eq!(apk.archive_data(&entry)[range.clone()].as_ptr(), read.as_ptr());
1250 }
1251 let owned = apk.read_entry_owned(&entry).unwrap();
1252 assert_eq!(owned.is_mapped(), !deflate);
1253 if let Some(range) = range {
1254 assert_eq!(owned.as_ptr(), apk.archive_data(&entry)[range].as_ptr());
1255 }
1256 drop(apk);
1257 assert_eq!(&*owned, dex.as_slice());
1258 assert_eq!(owned.as_ref(), dex.as_slice());
1259 assert!(format!("{owned:?}").contains(&format!("len: {}", dex.len())));
1260 }
1261 let f = write_temp(&asc_fixture::make_apk(&[("classes.dex", &dex)], false));
1263 let apk = Apk::open(f.path()).unwrap();
1264 let mut lying = apk.find_entry("classes.dex").unwrap().unwrap();
1265 lying.uncomp_size += 1;
1266 assert!(matches!(apk.entry_range(&lying), Err(ApkError::SizeMismatch { .. })));
1267 assert!(matches!(apk.read_entry_owned(&lying), Err(ApkError::SizeMismatch { .. })));
1268 lying.local_header_off += 1;
1269 assert!(matches!(apk.entry_range(&lying), Err(ApkError::BadLocalHeader(_))));
1270 let small = apk.clone().with_limits(Limits::default().max_entry_bytes(16));
1271 let entry = small.find_entry("classes.dex").unwrap().unwrap();
1272 assert!(matches!(small.entry_range(&entry), Err(ApkError::LimitExceeded { .. })));
1273 }
1274
1275 #[test]
1276 fn all_dex_entries_reach_any_depth() {
1277 let dex = asc_fixture::make_dex();
1278 let apk_bytes = asc_fixture::make_apk(
1279 &[
1280 ("classes2.dex", &dex),
1281 ("assets/payload.dex", b"x"),
1282 ("classes.dex", &dex),
1283 ("lib/x/classes.dex", &dex),
1284 ("assets/notdex.dex.bak", b"y"),
1285 ("AndroidManifest.xml", b"xml"),
1286 ],
1287 true,
1288 );
1289 let f = write_temp(&apk_bytes);
1290 let apk = Apk::open(f.path()).unwrap();
1291 let names = |entries: Vec<Entry>| entries.into_iter().map(|e| e.name).collect::<Vec<_>>();
1292 assert_eq!(names(apk.all_dex_entries().unwrap()), ["classes2.dex", "assets/payload.dex", "classes.dex", "lib/x/classes.dex"]);
1293 assert_eq!(names(apk.dex_entries_in_order().unwrap()), ["classes2.dex", "classes.dex"]);
1295 let payload = apk.all_dex_entries().unwrap().remove(1);
1296 assert_eq!(apk.read_entry(&payload).unwrap().as_ref(), b"x");
1297 let f = write_temp(&dex);
1299 assert_eq!(names(Apk::open(f.path()).unwrap().all_dex_entries().unwrap()), ["classes.dex"]);
1300 }
1301
1302 #[test]
1303 fn multidex_entries_follow_the_runtime_load_order() {
1304 let dex = asc_fixture::make_dex();
1305 let apk_bytes = asc_fixture::make_apk(
1306 &[
1307 ("classes10.dex", &dex),
1308 ("extra.dex", &dex),
1309 ("classes2.dex", &dex),
1310 ("classes.dex", &dex),
1311 ("classes02.dex", &dex),
1312 ("classes3.dex", &dex),
1313 ("assets/classes4.dex", &dex),
1314 ],
1315 true,
1316 );
1317 let f = write_temp(&apk_bytes);
1318 let apk = Apk::open(f.path()).unwrap();
1319 let names: Vec<String> = apk.dex_entries_multidex().unwrap().into_iter().map(|e| e.name).collect();
1320 assert_eq!(names, ["classes.dex", "classes2.dex", "classes3.dex", "classes10.dex", "extra.dex", "classes02.dex"]);
1321 assert_eq!(multidex_rank("services.jar/classes2.dex"), 2);
1322 }
1323
1324 fn zeros_deflated(n: usize) -> Vec<u8> {
1326 let mut e = flate2::write::DeflateEncoder::new(Vec::new(), flate2::Compression::best());
1327 e.write_all(&vec![0u8; n]).unwrap();
1328 e.finish().unwrap()
1329 }
1330
1331 fn raw_zip(entries: &[(&str, u16, &[u8], u32, u32)]) -> Vec<u8> {
1334 let mut z = Vec::new();
1335 let mut cd = Vec::new();
1336 for (name, method, payload, comp_size, uncomp_size) in entries {
1337 let lh = z.len() as u32;
1338 z.extend_from_slice(LH_SIG);
1339 z.extend_from_slice(&[20, 0, 0, 0]);
1340 z.extend_from_slice(&method.to_le_bytes());
1341 z.extend_from_slice(&[0; 8]);
1342 z.extend_from_slice(&comp_size.to_le_bytes());
1343 z.extend_from_slice(&uncomp_size.to_le_bytes());
1344 z.extend_from_slice(&(name.len() as u16).to_le_bytes());
1345 z.extend_from_slice(&0u16.to_le_bytes());
1346 z.extend_from_slice(name.as_bytes());
1347 z.extend_from_slice(payload);
1348 cd.extend_from_slice(CD_SIG);
1349 cd.extend_from_slice(&[20, 0, 20, 0, 0, 0]);
1350 cd.extend_from_slice(&method.to_le_bytes());
1351 cd.extend_from_slice(&[0; 8]);
1352 cd.extend_from_slice(&comp_size.to_le_bytes());
1353 cd.extend_from_slice(&uncomp_size.to_le_bytes());
1354 cd.extend_from_slice(&(name.len() as u16).to_le_bytes());
1355 cd.extend_from_slice(&[0; 12]);
1356 cd.extend_from_slice(&lh.to_le_bytes());
1357 cd.extend_from_slice(name.as_bytes());
1358 }
1359 let cd_off = z.len() as u32;
1360 z.extend_from_slice(&cd);
1361 z.extend_from_slice(EOCD_SIG);
1362 z.extend_from_slice(&[0, 0, 0, 0]);
1363 z.extend_from_slice(&(entries.len() as u16).to_le_bytes());
1364 z.extend_from_slice(&(entries.len() as u16).to_le_bytes());
1365 z.extend_from_slice(&(cd.len() as u32).to_le_bytes());
1366 z.extend_from_slice(&cd_off.to_le_bytes());
1367 z.extend_from_slice(&[0, 0]);
1368 z
1369 }
1370
1371 #[test]
1372 fn undeclared_size_is_still_bounded() {
1373 let ok = zeros_deflated(1 << 20);
1375 let f = write_temp(&raw_zip(&[("classes.dex", METHOD_DEFLATE, &ok, ok.len() as u32, 0)]));
1376 let apk = Apk::open(f.path()).unwrap();
1377 let e = apk.dex_entries().unwrap().remove(0);
1378 assert_eq!(e.uncomp_size, 0);
1379 assert_eq!(apk.read_entry(&e).unwrap().len(), 1 << 20);
1380 match apk.inflate_until(&e, |_| PrefixStep::Continue(4096)).unwrap() {
1381 Prefix::Partial(v) => assert!(v.len() >= 4096),
1382 Prefix::Complete(v) => assert_eq!(v.len(), 1 << 20),
1383 Prefix::Aborted(_) => panic!("unexpected abort"),
1384 }
1385 let mut lying = e.clone();
1388 lying.comp_size = 200;
1389 assert!(apk.read_entry(&lying).is_err());
1390 assert!(apk.inflate_until(&lying, |_| PrefixStep::Continue(0)).is_err());
1391 }
1392
1393 #[test]
1394 fn hostile_offsets_do_not_overflow() {
1395 let dex = asc_fixture::make_dex();
1396 let f = write_temp(&asc_fixture::make_apk(&[("classes.dex", &dex)], true));
1397 let apk = Apk::open(f.path()).unwrap();
1398 let mut e = apk.dex_entries().unwrap().remove(0);
1399 for off in [u64::MAX, u64::MAX - 3, u64::MAX - 40, u32::MAX as u64] {
1400 e.local_header_off = off;
1401 assert!(matches!(apk.read_entry(&e), Err(ApkError::BadLocalHeader(_))));
1402 assert!(matches!(apk.inflate_until(&e, |_| PrefixStep::Continue(0)), Err(ApkError::BadLocalHeader(_))));
1403 }
1404 let mut bytes = asc_fixture::make_apk(&[("classes.dex", &dex)], false);
1406 let eocd = bytes.len() - 22;
1407 bytes[eocd + 16..eocd + 20].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
1408 let mut locator = Vec::new();
1409 locator.extend_from_slice(ZIP64_LOCATOR_SIG);
1410 locator.extend_from_slice(&0u32.to_le_bytes());
1411 locator.extend_from_slice(&(u64::MAX - 2).to_le_bytes());
1412 locator.extend_from_slice(&1u32.to_le_bytes());
1413 let tail = bytes.split_off(eocd);
1414 bytes.extend_from_slice(&locator);
1415 bytes.extend_from_slice(&tail);
1416 let f = write_temp(&bytes);
1417 let apk = Apk::open(f.path()).unwrap();
1418 assert!(matches!(apk.dex_entries(), Err(ApkError::NoEocd | ApkError::BadCentralDirectory)));
1419 }
1420
1421 #[test]
1422 fn duplicate_root_dex_names_keep_the_last() {
1423 let a = asc_fixture::make_dex();
1424 let mut b = a.clone();
1425 b.push(0); let (la, lb) = (a.len() as u32, b.len() as u32);
1427 let f = write_temp(&raw_zip(&[
1428 ("classes.dex", METHOD_STORED, &a, la, la),
1429 ("classes2.dex", METHOD_STORED, &a, la, la),
1430 ("classes.dex", METHOD_STORED, &b, lb, lb),
1431 ]));
1432 let apk = Apk::open(f.path()).unwrap();
1433 let entries = apk.dex_entries().unwrap();
1434 let mut names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
1435 names.sort();
1436 assert_eq!(names, ["classes.dex", "classes2.dex"]);
1437 let dup = entries.iter().find(|e| e.name == "classes.dex").unwrap();
1438 assert_eq!(apk.read_entry(dup).unwrap().as_ref(), b.as_slice());
1439 assert_eq!(apk.find_entry("classes.dex").unwrap().unwrap(), *dup);
1440 let ordered: Vec<String> = apk.dex_entries_in_order().unwrap().into_iter().map(|e| e.name).collect();
1442 assert_eq!(ordered, ["classes2.dex", "classes.dex"]);
1443 }
1444
1445 #[test]
1446 fn programs_name_entries_after_their_archive() {
1447 let dex = asc_fixture::make_dex();
1448 let dir = tempfile::tempdir().unwrap();
1449 let root = dir.path().join("system");
1450 std::fs::create_dir_all(root.join("app/Tele")).unwrap();
1451 let both = [("classes.dex", &dex[..]), ("classes2.dex", &dex[..]), ("AndroidManifest.xml", b"first")];
1452 std::fs::write(root.join("services.jar"), asc_fixture::make_apk(&both, true)).unwrap();
1453 let apk = [("classes.dex", &dex[..]), ("AndroidManifest.xml", b"second")];
1454 std::fs::write(root.join("app/Tele/Tele.apk"), asc_fixture::make_apk(&apk, false)).unwrap();
1455 std::fs::write(root.join("odexed.jar"), asc_fixture::make_apk(&[("META-INF/MANIFEST.MF", b"m")], false)).unwrap();
1456 std::fs::write(root.join("bare.dex"), &dex).unwrap();
1457 std::fs::write(root.join("broken.zip"), b"not a zip").unwrap();
1458
1459 let program = Apk::open(&root).unwrap();
1460 assert_eq!(program.archive_count(), 3);
1461 let names: Vec<String> = program.dex_entries_in_order().unwrap().into_iter().map(|e| e.name).collect();
1462 assert_eq!(
1463 names,
1464 ["app/Tele/Tele.apk/classes.dex", "bare.dex/classes.dex", "services.jar/classes.dex", "services.jar/classes2.dex"]
1465 );
1466 for e in program.dex_entries().unwrap() {
1467 assert_eq!(program.read_entry(&e).unwrap().as_ref(), dex.as_slice(), "{}", e.name);
1468 }
1469 let notes = program.notes().join("\n");
1470 assert!(notes.contains("broken.zip") && notes.contains("odexed.jar: no classes*.dex"), "{notes}");
1471 assert_eq!(program.read("AndroidManifest.xml").unwrap(), b"second");
1473 assert_eq!(program.read("services.jar/AndroidManifest.xml").unwrap(), b"first");
1474 let bare = program.find_entry("bare.dex/classes.dex").unwrap().unwrap();
1475 assert_eq!(program.archive_path(&bare), root.join("bare.dex"));
1476 assert_eq!(archive_of("app/Tele/Tele.apk/classes.dex"), Some("app/Tele/Tele.apk"));
1477 assert_eq!(archive_of("services.jar/classes5.dex!classes1.dex"), Some("services.jar"));
1478 assert_eq!(archive_of("classes5.dex!classes1.dex"), None);
1479 assert_eq!(program.stem(), "Tele-and-2");
1480 assert!(program.stamp().is_ok() && program.directories().unwrap().len() == 3);
1481
1482 let lone = Apk::open(root.join("bare.dex")).unwrap();
1484 assert_eq!(lone.dex_entries().unwrap()[0].name, "classes.dex");
1485 let spec = format!("{},{}", root.join("services.jar").display(), root.join("odexed.jar").display());
1486 let one = Apk::open(&spec).unwrap();
1487 assert_eq!(one.archive_count(), 1);
1488 assert_eq!(one.dex_entries_in_order().unwrap()[0].name, "classes.dex");
1489 assert_eq!(one.notes().len(), 1);
1490 let spec = format!("{},{}", root.join("odexed.jar").display(), root.join("broken.zip").display());
1491 assert!(matches!(Apk::open(&spec), Err(ApkError::NoCode { .. })));
1492 }
1493
1494 #[test]
1495 fn inflate_until_streams_prefixes() {
1496 let dex = asc_fixture::make_dex();
1497 let apk_bytes = asc_fixture::make_apk(&[("classes.dex", &dex)], true);
1498 let f = write_temp(&apk_bytes);
1499 let apk = Apk::open(f.path()).unwrap();
1500 let entry = apk.dex_entries().unwrap().remove(0);
1501 match apk.inflate_until(&entry, |_| PrefixStep::Continue(8)).unwrap() {
1502 Prefix::Complete(v) | Prefix::Partial(v) => assert!(v.starts_with(b"dex\n") && v.len() >= 8),
1503 Prefix::Aborted(_) => panic!("unexpected abort"),
1504 }
1505 assert!(matches!(apk.inflate_until(&entry, |_| PrefixStep::Abort).unwrap(), Prefix::Aborted(_)));
1506 }
1507
1508 #[test]
1509 fn nested_archives_open_in_place_or_inflated() {
1510 let inner = asc_fixture::make_apk(&[("ets/modules.abc", b"PANDA body"), ("module.json", b"{}")], false);
1511 for deflate in [false, true] {
1512 let file = write_temp(&asc_fixture::make_apk(&[("entry.hap", &inner), ("pack.info", b"{}")], deflate));
1513 let outer = Apk::open(file.path()).unwrap();
1514 assert!(outer.lone_data().is_some_and(|data| data.starts_with(LH_SIG)));
1515 let entry = outer.find_entry("entry.hap").unwrap().unwrap();
1516 assert_eq!(entry.method, if deflate { METHOD_DEFLATE } else { METHOD_STORED });
1517 let nested = outer.open_nested(&entry).unwrap();
1518 assert_eq!(nested.lone_data(), Some(&inner[..]));
1519 let outer_range = outer.lone_data().unwrap().as_ptr_range();
1521 assert_eq!(outer_range.contains(&nested.lone_data().unwrap().as_ptr()), !deflate);
1522 let names: Vec<String> = nested.entries().unwrap().into_iter().map(|e| e.name).collect();
1523 assert_eq!(names, ["ets/modules.abc", "module.json"]);
1524 let abc = nested.find_entry("ets/modules.abc").unwrap().unwrap();
1525 assert!(matches!(nested.read_entry(&abc).unwrap(), Cow::Borrowed(b"PANDA body")));
1526 drop(outer);
1528 assert_eq!(nested.read("module.json").unwrap(), b"{}");
1529 }
1530 let not_zip = write_temp(&asc_fixture::make_apk(&[("x.hap", b"not a zip")], false));
1531 let outer = Apk::open(not_zip.path()).unwrap();
1532 let nested = outer.open_nested(&outer.find_entry("x.hap").unwrap().unwrap()).unwrap();
1533 assert!(matches!(nested.entries(), Err(ApkError::NoEocd)));
1534 }
1535}