diff --git a/fdroidserver/common.py b/fdroidserver/common.py index c7c4c5c2..c90db883 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -2629,29 +2629,46 @@ def get_file_extension(filename): return os.path.splitext(filename)[1].lower()[1:] -def use_androguard(): - """Report if androguard is available, and config its debug logging.""" +def _androguard_logging_level(level=logging.ERROR): + """Tames androguard's default debug output. + + There should be no debug output when the functions are being used + via the API. Otherwise, the output is controlled by the --verbose + flag. + + To get coverage across the full range of androguard >= 3.3.5, this + includes all known logger names that are relevant. So some of + these names might not be present in the version of androguard + currently in use. + + """ + if options and options.verbose: + level = logging.WARNING + + for name in ( + 'androguard.apk', + 'androguard.axml', + 'androguard.core.api_specific_resources', + 'androguard.core.apk', + 'androguard.core.axml', + ): + logging.getLogger(name).setLevel(level) + + # some parts of androguard 4.x use loguru instead of logging try: - import androguard - if use_androguard.show_path: - logging.debug(_('Using androguard from "{path}"').format(path=androguard.__file__)) - use_androguard.show_path = False - if options and options.verbose: - logging.getLogger("androguard.axml").setLevel(logging.INFO) - logging.getLogger("androguard.core.api_specific_resources").setLevel(logging.ERROR) - return True + from loguru import logger + logger.remove() except ImportError: - return False + pass -use_androguard.show_path = True # type: ignore - - -def _get_androguard_APK(apkfile): +def get_androguard_APK(apkfile): try: + # these were moved in androguard 4.0 + from androguard.core.apk import APK + except ImportError: from androguard.core.bytecodes.apk import APK - except ImportError as exc: - raise FDroidException("androguard library is not installed") from exc + _androguard_logging_level() return APK(apkfile) @@ -2693,7 +2710,13 @@ def is_debuggable_or_testOnly(apkfile): """ if get_file_extension(apkfile) != 'apk': return False - from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG + try: + # these were moved in androguard 4.0 + from androguard.core.axml import AXMLParser, format_value, START_TAG + except ImportError: + from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG + _androguard_logging_level() + with ZipFile(apkfile) as apk: with apk.open('AndroidManifest.xml') as manifest: axml = AXMLParser(manifest.read()) @@ -2753,12 +2776,20 @@ def get_apk_id_androguard(apkfile): versionName is set to a Android String Resource (e.g. an integer hex value that starts with @). + This function is part of androguard as get_apkid(), so this + vendored and modified to return versionCode as an integer. + """ if not os.path.exists(apkfile): raise FDroidException(_("Reading packageName/versionCode/versionName failed, APK invalid: '{apkfilename}'") .format(apkfilename=apkfile)) - from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG, END_TAG, TEXT, END_DOCUMENT + try: + # these were moved in androguard 4.0 + from androguard.core.axml import AXMLParser, format_value, START_TAG, END_TAG, TEXT, END_DOCUMENT + except ImportError: + from androguard.core.bytecodes.axml import AXMLParser, format_value, START_TAG, END_TAG, TEXT, END_DOCUMENT + _androguard_logging_level() appid = None versionCode = None @@ -2793,7 +2824,7 @@ def get_apk_id_androguard(apkfile): .format(path=apkfile)) if not versionName or versionName[0] == '@': - a = _get_androguard_APK(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 @@ -3159,8 +3190,8 @@ def get_first_signer_certificate(apkpath): elif len(cert_files) == 1: cert_encoded = get_certificate(apk.read(cert_files[0])) - if not cert_encoded and use_androguard(): - apkobject = _get_androguard_APK(apkpath) + if not cert_encoded: + apkobject = get_androguard_APK(apkpath) certs = apkobject.get_certificates_der_v2() if len(certs) > 0: logging.debug(_('Using APK Signature v2')) diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 23e3d604..2ce07fa5 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -1722,8 +1722,7 @@ def _sanitize_sdk_version(value): def scan_apk_androguard(apk, apkfile): try: - from androguard.core.bytecodes.apk import APK - apkobject = APK(apkfile) + apkobject = common.get_androguard_APK(apkfile) if apkobject.is_valid_APK(): arsc = apkobject.get_android_resources() else: @@ -1739,7 +1738,7 @@ def scan_apk_androguard(apk, apkfile): logging.error(_("Failed to get APK information, skipping {path}") .format(path=apkfile)) raise BuildException(_("Invalid APK")) - except (FileNotFoundError, zipfile.BadZipFile) as e: + except (FileNotFoundError, ValueError, zipfile.BadZipFile) as e: logging.error(_("Could not open APK {path} for analysis: ").format(path=apkfile) + str(e)) raise BuildException(_("Invalid APK")) from e @@ -1779,7 +1778,7 @@ def scan_apk_androguard(apk, apkfile): if maxSdkVersion is not None: apk['maxSdkVersion'] = maxSdkVersion - icon_id_str = apkobject.get_element("application", "icon") + icon_id_str = apkobject.get_attribute_value("application", "icon") if icon_id_str: try: icon_id = int(icon_id_str.replace("@", "0x"), 16) @@ -2581,7 +2580,6 @@ def main(): config = common.read_config(options) common.setup_status_output(start_timestamp) - common.use_androguard() if not (('jarsigner' in config or 'apksigner' in config) and 'keytool' in config): raise FDroidException(_('Java JDK not found! Install in standard location or set java_paths!')) diff --git a/setup.py b/setup.py index 49548f78..8e4e2452 100755 --- a/setup.py +++ b/setup.py @@ -92,7 +92,7 @@ setup( ], install_requires=[ 'appdirs', - 'androguard >= 3.1.0, != 3.3.0, != 3.3.1, != 3.3.2, <4', + 'androguard >= 3.3.5', 'clint', 'defusedxml', 'GitPython', diff --git a/tests/build.TestCase b/tests/build.TestCase index 6a4ddb02..3ad4cdd0 100755 --- a/tests/build.TestCase +++ b/tests/build.TestCase @@ -49,8 +49,6 @@ class BuildTest(unittest.TestCase): def setUp(self): logging.basicConfig(level=logging.DEBUG) - logger = logging.getLogger('androguard.axml') - logger.setLevel(logging.INFO) # tame the axml debug messages self.basedir = os.path.join(localmodule, 'tests') os.chdir(self.basedir) fdroidserver.common.config = None diff --git a/tests/common.TestCase b/tests/common.TestCase index 543e8eb6..925d61e0 100755 --- a/tests/common.TestCase +++ b/tests/common.TestCase @@ -902,7 +902,7 @@ class CommonTest(unittest.TestCase): self.assertTrue(os.path.isfile(signed)) self.assertFalse(os.path.isfile(unsigned)) self.assertTrue(fdroidserver.common.verify_apk_signature(signed)) - self.assertEqual('18', fdroidserver.common._get_androguard_APK(signed).get_min_sdk_version()) + self.assertEqual('18', fdroidserver.common.get_androguard_APK(signed).get_min_sdk_version()) shutil.copy(os.path.join(self.basedir, 'minimal_targetsdk_30_unsigned.apk'), self.testdir) unsigned = os.path.join(self.testdir, 'minimal_targetsdk_30_unsigned.apk') @@ -915,7 +915,7 @@ class CommonTest(unittest.TestCase): self.assertFalse(os.path.isfile(unsigned)) self.assertTrue(fdroidserver.common.verify_apk_signature(signed)) # verify it has a v2 signature - self.assertTrue(fdroidserver.common._get_androguard_APK(signed).is_signed_v2()) + self.assertTrue(fdroidserver.common.get_androguard_APK(signed).is_signed_v2()) shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk30_unsigned.apk'), self.testdir) unsigned = os.path.join(self.testdir, 'no_targetsdk_minsdk30_unsigned.apk') @@ -923,7 +923,7 @@ class CommonTest(unittest.TestCase): fdroidserver.common.sign_apk(unsigned, signed, config['keyalias']) self.assertTrue(fdroidserver.common.verify_apk_signature(signed)) - self.assertTrue(fdroidserver.common._get_androguard_APK(signed).is_signed_v2()) + self.assertTrue(fdroidserver.common.get_androguard_APK(signed).is_signed_v2()) shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk1_unsigned.apk'), self.testdir) unsigned = os.path.join(self.testdir, 'no_targetsdk_minsdk1_unsigned.apk') @@ -1146,11 +1146,11 @@ class CommonTest(unittest.TestCase): """This is a sanity test that androguard isn't broken""" def get_minSdkVersion(apkfile): - apk = fdroidserver.common._get_androguard_APK(apkfile) + apk = fdroidserver.common.get_androguard_APK(apkfile) return fdroidserver.common.get_min_sdk_version(apk) def get_targetSdkVersion(apkfile): - apk = fdroidserver.common._get_androguard_APK(apkfile) + apk = fdroidserver.common.get_androguard_APK(apkfile) return apk.get_effective_target_sdk_version() self.assertEqual(4, get_minSdkVersion('bad-unicode-πÇÇ现代通用字-български-عربي1.apk')) diff --git a/tests/run-tests b/tests/run-tests index ca9aa951..31cb4939 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -8,7 +8,9 @@ echo_header() { get_fdroid_apk_filename() { if [ -z $aapt ]; then - python3 -c "from androguard.core.bytecodes.apk import APK; a=APK('$1'); print(a.package+'_'+a.get_androidversion_code()+'.apk')" + appid=$(androguard apkid "$1" | sed -En 's/ +"([a-z][^"]+)",$/\1/ip') + versionCode=$(androguard apkid "$1" | sed -En 's/ +"([0-9]+)",$/\1/p') + echo "${appid}_${versionCode}.apk" else $aapt dump badging "$1" | sed -n "s,^package: name='\(.*\)' versionCode='\([0-9][0-9]*\)' .*,\1_\2.apk,p" fi diff --git a/tests/update.TestCase b/tests/update.TestCase index 2ab19a3a..0c692aa8 100755 --- a/tests/update.TestCase +++ b/tests/update.TestCase @@ -5,6 +5,7 @@ import copy import git import glob +import hashlib import inspect import json import logging @@ -20,11 +21,18 @@ import unittest import yaml import zipfile import textwrap +from binascii import hexlify from datetime import datetime from pathlib import Path from testcommon import TmpCwd, mkdtemp from unittest import mock +try: + # these were moved in androguard 4.0 + from androguard.core.apk import APK +except ImportError: + from androguard.core.bytecodes.apk import APK + try: from yaml import CSafeLoader as SafeLoader except ImportError: @@ -74,9 +82,6 @@ class UpdateTest(unittest.TestCase): def setUp(self): logging.basicConfig(level=logging.INFO) - logging.getLogger('androguard.apk').setLevel(logging.WARNING) - logging.getLogger('androguard.axml').setLevel(logging.INFO) - logging.getLogger('androguard.core.api_specific_resources').setLevel(logging.INFO) from PIL import PngImagePlugin logging.getLogger(PngImagePlugin.__name__).setLevel(logging.INFO) @@ -581,13 +586,6 @@ class UpdateTest(unittest.TestCase): self.assertEqual(good_fingerprint, sig, 'python sig was: ' + str(sig)) # check that v1 and v2 have the same certificate - try: - import hashlib - from binascii import hexlify - from androguard.core.bytecodes.apk import APK - except ImportError: - print('WARNING: skipping rest of test since androguard is missing!') - return apkobject = APK(apkpath) cert_encoded = apkobject.get_certificates_der_v2()[0] self.assertEqual(good_fingerprint, sig,