implement common.get_apk_id() using androguard

This commit is contained in:
Hans-Christoph Steiner 2018-05-03 13:33:37 +02:00
parent 98a2f70e38
commit 27a5cce832
4 changed files with 66 additions and 37 deletions

View File

@ -1955,6 +1955,36 @@ def use_androguard():
use_androguard.show_path = True
def _get_androguard_APK(apkfile):
try:
from androguard.core.bytecodes.apk import APK
except ImportError:
raise FDroidException("androguard library is not installed and aapt not present")
return APK(apkfile)
def ensure_final_value(packageName, arsc, value):
"""Ensure incoming value is always the value, not the resid
androguard will sometimes return the Android "resId" aka
Resource ID instead of the actual value. This checks whether
the value is actually a resId, then performs the Android
Resource lookup as needed.
"""
if value:
returnValue = value
if value[0] == '@':
try: # can be a literal value or a resId
res_id = int('0x' + value[1:], 16)
res_id = arsc.get_id(packageName, res_id)[1]
returnValue = arsc.get_string(packageName, res_id)[1]
except ValueError:
pass
return returnValue
def is_apk_and_debuggable_aapt(apkfile):
p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'],
output=False)
@ -1967,12 +1997,7 @@ def is_apk_and_debuggable_aapt(apkfile):
def is_apk_and_debuggable_androguard(apkfile):
try:
from androguard.core.bytecodes.apk import APK
except ImportError:
raise FDroidException("androguard library is not installed and aapt not present")
apkobject = APK(apkfile)
apkobject = _get_androguard_APK(apkfile)
if apkobject.is_valid_APK():
debuggable = apkobject.get_element("application", "debuggable")
if debuggable is not None:
@ -1994,12 +2019,30 @@ def is_apk_and_debuggable(apkfile):
return is_apk_and_debuggable_aapt(apkfile)
def get_apk_id_aapt(apkfile):
def get_apk_id(apkfile):
"""Extract identification information from APK using aapt.
:param apkfile: path to an APK file.
:returns: triplet (appid, version code, version name)
"""
if use_androguard():
return get_apk_id_androguard(apkfile)
else:
return get_apk_id_aapt(apkfile)
def get_apk_id_androguard(apkfile):
if not os.path.exists(apkfile):
raise FDroidException(_("Reading packageName/versionCode/versionName failed, APK invalid: '{apkfilename}'")
.format(apkfilename=apkfile))
a = _get_androguard_APK(apkfile)
versionName = ensure_final_value(a.package, a.get_android_resources(), a.get_androidversion_name())
if not versionName:
versionName = '' # versionName is expected to always be a str
return a.package, a.get_androidversion_code(), versionName
def get_apk_id_aapt(apkfile):
r = re.compile("^package: name='(?P<appid>.*)' versionCode='(?P<vercode>.*)' versionName='(?P<vername>.*?)'(?: platformBuildVersionName='.*')?")
p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
for line in p.output.splitlines():

View File

@ -36,7 +36,7 @@ def extract_signature(apkpath):
raise FDroidException("no valid signature in '{}'".format(apkpath))
logging.debug('signature okay: %s', apkpath)
appid, vercode, _ignored = common.get_apk_id_aapt(apkpath)
appid, vercode, _ignored = common.get_apk_id(apkpath)
sigdir = common.metadata_get_sigdir(appid, vercode)
if not os.path.exists(sigdir):
os.makedirs(sigdir)

View File

@ -1181,27 +1181,6 @@ def scan_apk_aapt(apk, apkfile):
apk['icons_src'] = _get_apk_icons_src(apkfile, icon_name)
def _ensure_final_value(packageName, arsc, value):
"""Ensure incoming value is always the value, not the resid
androguard will sometimes return the Android "resId" aka
Resource ID instead of the actual value. This checks whether
the value is actually a resId, then performs the Android
Resource lookup as needed.
"""
if value:
returnValue = value
if value[0] == '@':
try: # can be a literal value or a resId
res_id = int(value.replace("@", "0x"), 16)
res_id = arsc.get_id(packageName, res_id)[1]
returnValue = arsc.get_string(packageName, res_id)[1]
except ValueError:
pass
return returnValue
def _sanitize_sdk_version(value):
"""Sanitize the raw values from androguard to handle bad values
@ -1250,8 +1229,8 @@ def scan_apk_androguard(apk, apkfile):
apk['versionCode'] = int(apkobject.get_androidversion_code())
apk['name'] = apkobject.get_app_name()
apk['versionName'] = _ensure_final_value(apk['packageName'], arsc,
apkobject.get_androidversion_name())
apk['versionName'] = common.ensure_final_value(apk['packageName'], arsc,
apkobject.get_androidversion_name())
minSdkVersion = _sanitize_sdk_version(apkobject.get_min_sdk_version())
if minSdkVersion is not None:

View File

@ -533,7 +533,7 @@ class CommonTest(unittest.TestCase):
self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
self.assertEqual(18, fdroidserver.common.get_minSdkVersion_aapt(signed))
def test_get_api_id_aapt(self):
def test_get_api_id(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)
@ -568,13 +568,20 @@ class CommonTest(unittest.TestCase):
('repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk', 'info.guardianproject.urzip', '100', '0.1'),
]
for apkfilename, appid, versionCode, versionName in testcases:
a, vc, vn = fdroidserver.common.get_apk_id_aapt(apkfilename)
self.assertEqual(appid, a)
self.assertEqual(versionCode, vc)
self.assertEqual(versionName, vn)
print('\n\nAPKFILENAME\n', apkfilename)
if 'aapt' in config:
a, vc, vn = fdroidserver.common.get_apk_id_aapt(apkfilename)
self.assertEqual(appid, a)
self.assertEqual(versionCode, vc)
self.assertEqual(versionName, vn)
if fdroidserver.common.use_androguard():
a, vc, vn = fdroidserver.common.get_apk_id_androguard(apkfilename)
self.assertEqual(appid, a)
self.assertEqual(versionCode, vc)
self.assertEqual(versionName, vn)
with self.assertRaises(FDroidException):
fdroidserver.common.get_apk_id_aapt('nope')
fdroidserver.common.get_apk_id('nope')
def test_get_minSdkVersion_aapt(self):