From 807bf3d26b0734e06fbc0abbdb4fbda06bf2886f Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 17 Sep 2018 22:44:53 +0200 Subject: [PATCH] build: reuse common methods for getting metadata from APKs This splits out the code that gets the list of native ABIs supported, then uses the standard methods for the rest. --- fdroidserver/build.py | 94 +++++++----------------------------------- fdroidserver/common.py | 13 ++++++ tests/build.TestCase | 50 ++++++++++++++++++++++ tests/common.TestCase | 31 ++++++++++++++ 4 files changed, 109 insertions(+), 79 deletions(-) diff --git a/fdroidserver/build.py b/fdroidserver/build.py index c2854126..a4d1c6d2 100644 --- a/fdroidserver/build.py +++ b/fdroidserver/build.py @@ -42,7 +42,7 @@ from . import net from . import metadata from . import scanner from . import vmtools -from .common import FDroidPopen, SdkToolsPopen +from .common import FDroidPopen from .exception import FDroidException, BuildException, VCSException try: @@ -323,92 +323,28 @@ def transform_first_char(string, method): return method(string[0]) + string[1:] -def has_native_code(apkobj): - """aapt checks if there are architecture folders under the lib/ folder - so we are simulating the same behaviour""" - arch_re = re.compile("^lib/(.*)/.*$") - arch = [file for file in apkobj.get_files() if arch_re.match(file)] - return False if not arch else True - - -def get_apk_metadata_aapt(apkfile): - """aapt function to extract versionCode, versionName, packageName and nativecode""" - vercode = None - version = None - foundid = None - nativecode = None - - p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False) - - for line in p.output.splitlines(): - if line.startswith("package:"): - pat = re.compile(".*name='([a-zA-Z0-9._]*)'.*") - m = pat.match(line) - if m: - foundid = m.group(1) - pat = re.compile(".*versionCode='([0-9]*)'.*") - m = pat.match(line) - if m: - vercode = m.group(1) - pat = re.compile(".*versionName='([^']*)'.*") - m = pat.match(line) - if m: - version = m.group(1) - elif line.startswith("native-code:"): - nativecode = line[12:] - - return vercode, version, foundid, nativecode - - -def get_apk_metadata_androguard(apkfile): - """androguard function to extract versionCode, versionName, packageName and nativecode""" - try: - from androguard.core.bytecodes.apk import APK - apkobject = APK(apkfile) - except ImportError: - raise BuildException("androguard library is not installed and aapt binary not found") - except FileNotFoundError: - raise BuildException("Could not open apk file for metadata analysis") - - if not apkobject.is_valid_APK(): - raise BuildException("Invalid APK provided") - - foundid = apkobject.get_package() - vercode = apkobject.get_androidversion_code() - version = apkobject.get_androidversion_name() - nativecode = has_native_code(apkobject) - - return vercode, version, foundid, nativecode - - def get_metadata_from_apk(app, build, apkfile): - """get the required metadata from the built APK""" + """get the required metadata from the built APK - if common.SdkToolsPopen(['aapt', 'version'], output=False): - vercode, version, foundid, nativecode = get_apk_metadata_aapt(apkfile) - else: - vercode, version, foundid, nativecode = get_apk_metadata_androguard(apkfile) + versionName is allowed to be a blank string, i.e. '' + """ - # Ignore empty strings or any kind of space/newline chars that we don't - # care about - if nativecode is not None: - nativecode = nativecode.strip() - nativecode = None if not nativecode else nativecode + appid, versionCode, versionName = common.get_apk_id(apkfile) + native_code = common.get_native_code(apkfile) - if build.buildjni and build.buildjni != ['no']: - if nativecode is None: - raise BuildException("Native code should have been built but none was packaged") + if build.buildjni and build.buildjni != ['no'] and not native_code: + raise BuildException("Native code should have been built but none was packaged") if build.novcheck: - vercode = build.versionCode - version = build.versionName - if not version or not vercode: + versionCode = build.versionCode + versionName = build.versionName + if not versionCode or versionName is None: raise BuildException("Could not find version information in build in output") - if not foundid: + if not appid: raise BuildException("Could not find package ID in output") - if foundid != app.id: - raise BuildException("Wrong package ID - build " + foundid + " but expected " + app.id) + if appid != app.id: + raise BuildException("Wrong package ID - build " + appid + " but expected " + app.id) - return vercode, version + return versionCode, versionName def build_local(app, build, vcs, build_dir, output_dir, log_dir, srclib_dir, extlib_dir, tmp_dir, force, onserver, refresh): diff --git a/fdroidserver/common.py b/fdroidserver/common.py index c30fb548..cedc8c5a 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -2128,6 +2128,19 @@ def get_apk_id_aapt(apkfile): .format(apkfilename=apkfile)) +def get_native_code(apkfile): + """aapt checks if there are architecture folders under the lib/ folder + so we are simulating the same behaviour""" + arch_re = re.compile("^lib/(.*)/.*$") + archset = set() + with ZipFile(apkfile) as apk: + for filename in apk.namelist(): + m = arch_re.match(filename) + if m: + archset.add(m.group(1)) + return sorted(list(archset)) + + def get_minSdkVersion_aapt(apkfile): """Extract the minimum supported Android SDK from an APK using aapt diff --git a/tests/build.TestCase b/tests/build.TestCase index 9e0edd68..6b872ebb 100755 --- a/tests/build.TestCase +++ b/tests/build.TestCase @@ -20,6 +20,7 @@ if localmodule not in sys.path: import fdroidserver.build import fdroidserver.common +import fdroidserver.metadata class BuildTest(unittest.TestCase): @@ -71,6 +72,55 @@ class BuildTest(unittest.TestCase): filedata = f.read() self.assertIsNotNone(pattern.search(filedata)) + def test_get_apk_metadata(self): + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.common.config = config + fdroidserver.build.config = config + self._set_build_tools() + try: + config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt') + except fdroidserver.exception.FDroidException: + pass # aapt is not required if androguard is present + + testcases = [ + ('repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', '1101613', '0.1', None), + ('org.bitbucket.tickytacky.mirrormirror_1.apk', 'org.bitbucket.tickytacky.mirrormirror', '1', '1.0', None), + ('org.bitbucket.tickytacky.mirrormirror_2.apk', 'org.bitbucket.tickytacky.mirrormirror', '2', '1.0.1', None), + ('org.bitbucket.tickytacky.mirrormirror_3.apk', 'org.bitbucket.tickytacky.mirrormirror', '3', '1.0.2', None), + ('org.bitbucket.tickytacky.mirrormirror_4.apk', 'org.bitbucket.tickytacky.mirrormirror', '4', '1.0.3', None), + ('org.dyndns.fules.ck_20.apk', 'org.dyndns.fules.ck', '20', 'v1.6pre2', + ['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64']), + ('urzip.apk', 'info.guardianproject.urzip', '100', '0.1', None), + ('urzip-badcert.apk', 'info.guardianproject.urzip', '100', '0.1', None), + ('urzip-badsig.apk', 'info.guardianproject.urzip', '100', '0.1', None), + ('urzip-release.apk', 'info.guardianproject.urzip', '100', '0.1', None), + ('urzip-release-unsigned.apk', 'info.guardianproject.urzip', '100', '0.1', None), + ('repo/com.politedroid_3.apk', 'com.politedroid', '3', '1.2', None), + ('repo/com.politedroid_4.apk', 'com.politedroid', '4', '1.3', None), + ('repo/com.politedroid_5.apk', 'com.politedroid', '5', '1.4', None), + ('repo/com.politedroid_6.apk', 'com.politedroid', '6', '1.5', None), + ('repo/duplicate.permisssions_9999999.apk', 'duplicate.permisssions', '9999999', '', None), + ('repo/info.zwanenburg.caffeinetile_4.apk', 'info.zwanenburg.caffeinetile', '4', '1.3', None), + ('repo/obb.main.oldversion_1444412523.apk', 'obb.main.oldversion', '1444412523', '0.1', None), + ('repo/obb.mainpatch.current_1619_another-release-key.apk', 'obb.mainpatch.current', '1619', '0.1', None), + ('repo/obb.mainpatch.current_1619.apk', 'obb.mainpatch.current', '1619', '0.1', None), + ('repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', '1101613', '0.1', None), + ('repo/obb.main.twoversions_1101615.apk', 'obb.main.twoversions', '1101615', '0.1', None), + ('repo/obb.main.twoversions_1101617.apk', 'obb.main.twoversions', '1101617', '0.1', None), + ('repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk', 'info.guardianproject.urzip', '100', '0.1', None), + ] + for apkfilename, appid, versionCode, versionName, nativecode in testcases: + app = fdroidserver.metadata.App() + app.id = appid + build = fdroidserver.metadata.Build() + build.buildjni = ['yes'] if nativecode else build.buildjni + build.versionCode = versionCode + build.versionName = versionName + vc, vn = fdroidserver.build.get_metadata_from_apk(app, build, apkfilename) + self.assertEqual(versionCode, vc) + self.assertEqual(versionName, vn) + if __name__ == "__main__": os.chdir(os.path.dirname(__file__)) diff --git a/tests/common.TestCase b/tests/common.TestCase index 21929dbf..f1449392 100755 --- a/tests/common.TestCase +++ b/tests/common.TestCase @@ -636,6 +636,37 @@ class CommonTest(unittest.TestCase): else: self.fail('could not parse aapt output: {}'.format(f)) + def test_get_native_code(self): + testcases = [ + ('repo/obb.main.twoversions_1101613.apk', []), + ('org.bitbucket.tickytacky.mirrormirror_1.apk', []), + ('org.bitbucket.tickytacky.mirrormirror_2.apk', []), + ('org.bitbucket.tickytacky.mirrormirror_3.apk', []), + ('org.bitbucket.tickytacky.mirrormirror_4.apk', []), + ('org.dyndns.fules.ck_20.apk', ['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64']), + ('urzip.apk', []), + ('urzip-badcert.apk', []), + ('urzip-badsig.apk', []), + ('urzip-release.apk', []), + ('urzip-release-unsigned.apk', []), + ('repo/com.politedroid_3.apk', []), + ('repo/com.politedroid_4.apk', []), + ('repo/com.politedroid_5.apk', []), + ('repo/com.politedroid_6.apk', []), + ('repo/duplicate.permisssions_9999999.apk', []), + ('repo/info.zwanenburg.caffeinetile_4.apk', []), + ('repo/obb.main.oldversion_1444412523.apk', []), + ('repo/obb.mainpatch.current_1619_another-release-key.apk', []), + ('repo/obb.mainpatch.current_1619.apk', []), + ('repo/obb.main.twoversions_1101613.apk', []), + ('repo/obb.main.twoversions_1101615.apk', []), + ('repo/obb.main.twoversions_1101617.apk', []), + ('repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk', []), + ] + for apkfilename, native_code in testcases: + nc = fdroidserver.common.get_native_code(apkfilename) + self.assertEqual(native_code, nc) + def test_get_minSdkVersion_aapt(self): config = dict() fdroidserver.common.fill_config_defaults(config)