"""frame_apk against a small APK built here: binary manifest plus resource table."""
import sandbox # noqa: F401 (first: keeps tests off real data and services)
import io
import os
import struct
import sys
import tempfile
import tracemalloc
import unittest
import zipfile
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ui'))
import frame_apk # noqa: E402
from apk_fixtures import apk, manifest, pool, resources # noqa: E402,F401
class ApkInfo(unittest.TestCase):
def read(self, data):
with tempfile.TemporaryDirectory() as d:
p = os.path.join(d, 'app.apk')
with open(p, 'wb') as f:
f.write(data)
return frame_apk.apk_info(p)
def test_resolves_references(self):
# string type 1: label (entry 0), version (entry 1); mipmap type 2: icon at three densities.
arsc = resources({(1, 'fr', 0): {0: 0}, (1, '', 0): {0: 1, 1: 2},
(2, '', 160): {0: 3}, (2, '', 640): {0: 4}, (2, '', 0xfffe): {0: 5}})
info = self.read(apk({
'AndroidManifest.xml': manifest('com.example.demo', 0x7f010000, 0x7f010001, 26),
'resources.arsc': arsc,
'res/icon_lo.png': b'lo', 'res/icon_hi.png': b'hi', 'res/icon.xml': b'',
'lib/arm64-v8a/libx.so': b'', 'lib/x86_64/libx.so': b'',
}))
self.assertEqual(info['package'], 'com.example.demo')
self.assertEqual(info['label'], 'App label') # the default, not French
self.assertEqual(info['version'], '2.1')
self.assertEqual(info['version_code'], 210)
self.assertEqual(info['min_sdk'], 26)
self.assertEqual(info['abis'], ['arm64-v8a', 'x86_64'])
self.assertEqual(info['icon_png'], b'hi') # largest-density PNG, skipping the XML icon
def test_missing_label_falls_back_to_package(self):
info = self.read(apk({'AndroidManifest.xml': manifest('com.example.bare', 0x7f010000, 0x7f010001, 21)}))
self.assertEqual(info['label'], 'com.example.bare')
self.assertEqual(info['version'], '')
self.assertEqual(info['abis'], [])
def test_repacked_manifest(self):
# Package kept only as a typed value; a foreign `label` mustn't beat android:label.
arsc = resources({(1, '', 0): {0: 1, 1: 2}})
info = self.read(apk({
'AndroidManifest.xml': manifest('com.example.repacked', 0x7f010000, 0x7f010001, 24,
package_raw=False, foreign_label=True),
'resources.arsc': arsc,
}))
self.assertEqual(info['package'], 'com.example.repacked')
self.assertEqual(info['label'], 'App label')
self.assertIsNone(info['icon_png'])
def test_rejects_non_apks(self):
for data in (b'not a zip', apk({'classes.dex': b''}), apk({'AndroidManifest.xml': b''})):
with self.assertRaises(frame_apk.ApkError):
self.read(data)
def test_refuses_oversized_members(self):
# An APK from a website mustn't make the server inflate gigabytes.
data = apk({'AndroidManifest.xml': manifest('com.example.big', 0x7f010000, 0x7f010001, 21)})
limit, frame_apk.MAX_MANIFEST = frame_apk.MAX_MANIFEST, 16
try:
with self.assertRaises(frame_apk.ApkError):
self.read(data)
finally:
frame_apk.MAX_MANIFEST = limit
def test_forged_sizes_dont_inflate_everything(self):
# The central directory claims 1 byte; the deflated data holds 16 MB of zeros.
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as z:
z.writestr('AndroidManifest.xml', bytes(16 * 1024**2))
data = bytearray(buf.getvalue())
for sig, field in ((b'PK\x01\x02', 24), (b'PK\x03\x04', 22)):
at = data.index(sig)
data[at + field:at + field + 4] = struct.pack('