mirror of
				https://github.com/f-droid/fdroidserver.git
				synced 2025-11-04 06:30:27 +03:00 
			
		
		
		
	use androguard if aapt isn't found
This commit is contained in:
		
							parent
							
								
									9607cdb621
								
							
						
					
					
						commit
						06598ae406
					
				
					 8 changed files with 490 additions and 111 deletions
				
			
		| 
						 | 
				
			
			@ -483,15 +483,23 @@ def capitalize_intact(string):
 | 
			
		|||
    return string[0].upper() + string[1:]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_metadata_from_apk(app, build, apkfile):
 | 
			
		||||
    """get the required metadata from the built APK"""
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
    p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
 | 
			
		||||
 | 
			
		||||
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._]*)'.*")
 | 
			
		||||
| 
						 | 
				
			
			@ -509,6 +517,38 @@ def get_metadata_from_apk(app, build, apkfile):
 | 
			
		|||
        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"""
 | 
			
		||||
 | 
			
		||||
    if common.set_command_in_config('aapt'):
 | 
			
		||||
        vercode, version, foundid, nativecode = get_apk_metadata_aapt(apkfile)
 | 
			
		||||
    else:
 | 
			
		||||
        vercode, version, foundid, nativecode = get_apk_metadata_androguard(apkfile)
 | 
			
		||||
 | 
			
		||||
    # Ignore empty strings or any kind of space/newline chars that we don't
 | 
			
		||||
    # care about
 | 
			
		||||
    if nativecode is not None:
 | 
			
		||||
| 
						 | 
				
			
			@ -533,7 +573,6 @@ def get_metadata_from_apk(app, build, apkfile):
 | 
			
		|||
 | 
			
		||||
def build_local(app, build, vcs, build_dir, output_dir, log_dir, srclib_dir, extlib_dir, tmp_dir, force, onserver, refresh):
 | 
			
		||||
    """Do a build locally."""
 | 
			
		||||
 | 
			
		||||
    ndk_path = build.ndk_path()
 | 
			
		||||
    if build.ndk or (build.buildjni and build.buildjni != ['no']):
 | 
			
		||||
        if not ndk_path:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,6 +46,8 @@ from pyasn1.codec.der import decoder, encoder
 | 
			
		|||
from pyasn1_modules import rfc2315
 | 
			
		||||
from pyasn1.error import PyAsn1Error
 | 
			
		||||
 | 
			
		||||
from distutils.util import strtobool
 | 
			
		||||
 | 
			
		||||
import fdroidserver.metadata
 | 
			
		||||
from .asynchronousfilereader import AsynchronousFileReader
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1690,14 +1692,7 @@ def get_file_extension(filename):
 | 
			
		|||
    return os.path.splitext(filename)[1].lower()[1:]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def isApkAndDebuggable(apkfile, config):
 | 
			
		||||
    """Returns True if the given file is an APK and is debuggable
 | 
			
		||||
 | 
			
		||||
    :param apkfile: full path to the apk to check"""
 | 
			
		||||
 | 
			
		||||
    if get_file_extension(apkfile) != 'apk':
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
def get_apk_debuggable_aapt(apkfile):
 | 
			
		||||
    p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'],
 | 
			
		||||
                      output=False)
 | 
			
		||||
    if p.returncode != 0:
 | 
			
		||||
| 
						 | 
				
			
			@ -1709,6 +1704,35 @@ def isApkAndDebuggable(apkfile, config):
 | 
			
		|||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_apk_debuggable_androguard(apkfile):
 | 
			
		||||
    try:
 | 
			
		||||
        from androguard.core.bytecodes.apk import APK
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        logging.critical("androguard library is not installed and aapt not present")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    apkobject = APK(apkfile)
 | 
			
		||||
    if apkobject.is_valid_APK():
 | 
			
		||||
        debuggable = apkobject.get_element("application", "debuggable")
 | 
			
		||||
        if debuggable is not None:
 | 
			
		||||
            return bool(strtobool(debuggable))
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def isApkAndDebuggable(apkfile, config):
 | 
			
		||||
    """Returns True if the given file is an APK and is debuggable
 | 
			
		||||
 | 
			
		||||
    :param apkfile: full path to the apk to check"""
 | 
			
		||||
 | 
			
		||||
    if get_file_extension(apkfile) != 'apk':
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    if set_command_in_config('aapt'):
 | 
			
		||||
        return get_apk_debuggable_aapt(apkfile)
 | 
			
		||||
    else:
 | 
			
		||||
        return get_apk_debuggable_androguard(apkfile)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PopenResult:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.returncode = None
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,7 +41,7 @@ from . import btlog
 | 
			
		|||
from . import common
 | 
			
		||||
from . import index
 | 
			
		||||
from . import metadata
 | 
			
		||||
from .common import SdkToolsPopen
 | 
			
		||||
from .common import BuildException, SdkToolsPopen
 | 
			
		||||
 | 
			
		||||
METADATA_VERSION = 18
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +60,17 @@ APK_PERMISSION_PAT = \
 | 
			
		|||
APK_FEATURE_PAT = re.compile(".*name='([^']*)'.*")
 | 
			
		||||
 | 
			
		||||
screen_densities = ['640', '480', '320', '240', '160', '120']
 | 
			
		||||
screen_resolutions = {
 | 
			
		||||
    "xxxhdpi": '640',
 | 
			
		||||
    "xxhdpi": '480',
 | 
			
		||||
    "xhdpi": '320',
 | 
			
		||||
    "hdpi": '240',
 | 
			
		||||
    "mdpi": '160',
 | 
			
		||||
    "ldpi": '120',
 | 
			
		||||
    "undefined": '-1',
 | 
			
		||||
    "anydpi": '65534',
 | 
			
		||||
    "nodpi": '65535'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
all_screen_densities = ['0'] + screen_densities
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -871,6 +882,196 @@ def scan_repo_files(apkcache, repodir, knownapks, use_date_from_file=False):
 | 
			
		|||
    return repo_files, cachechanged
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def scan_apk_aapt(apk, apkfile):
 | 
			
		||||
    p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
 | 
			
		||||
    if p.returncode != 0:
 | 
			
		||||
        if options.delete_unknown:
 | 
			
		||||
            if os.path.exists(apkfile):
 | 
			
		||||
                logging.error("Failed to get apk information, deleting " + apkfile)
 | 
			
		||||
                os.remove(apkfile)
 | 
			
		||||
            else:
 | 
			
		||||
                logging.error("Could not find {0} to remove it".format(apkfile))
 | 
			
		||||
        else:
 | 
			
		||||
            logging.error("Failed to get apk information, skipping " + apkfile)
 | 
			
		||||
        raise BuildException("Invaild APK")
 | 
			
		||||
    for line in p.output.splitlines():
 | 
			
		||||
        if line.startswith("package:"):
 | 
			
		||||
            try:
 | 
			
		||||
                apk['packageName'] = re.match(APK_NAME_PAT, line).group(1)
 | 
			
		||||
                apk['versionCode'] = int(re.match(APK_VERCODE_PAT, line).group(1))
 | 
			
		||||
                apk['versionName'] = re.match(APK_VERNAME_PAT, line).group(1)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                logging.error("Package matching failed: " + str(e))
 | 
			
		||||
                logging.info("Line was: " + line)
 | 
			
		||||
                sys.exit(1)
 | 
			
		||||
        elif line.startswith("application:"):
 | 
			
		||||
            apk['name'] = re.match(APK_LABEL_PAT, line).group(1)
 | 
			
		||||
            # Keep path to non-dpi icon in case we need it
 | 
			
		||||
            match = re.match(APK_ICON_PAT_NODPI, line)
 | 
			
		||||
            if match:
 | 
			
		||||
                apk['icons_src']['-1'] = match.group(1)
 | 
			
		||||
        elif line.startswith("launchable-activity:"):
 | 
			
		||||
            # Only use launchable-activity as fallback to application
 | 
			
		||||
            if not apk['name']:
 | 
			
		||||
                apk['name'] = re.match(APK_LABEL_PAT, line).group(1)
 | 
			
		||||
            if '-1' not in apk['icons_src']:
 | 
			
		||||
                match = re.match(APK_ICON_PAT_NODPI, line)
 | 
			
		||||
                if match:
 | 
			
		||||
                    apk['icons_src']['-1'] = match.group(1)
 | 
			
		||||
        elif line.startswith("application-icon-"):
 | 
			
		||||
            match = re.match(APK_ICON_PAT, line)
 | 
			
		||||
            if match:
 | 
			
		||||
                density = match.group(1)
 | 
			
		||||
                path = match.group(2)
 | 
			
		||||
                apk['icons_src'][density] = path
 | 
			
		||||
        elif line.startswith("sdkVersion:"):
 | 
			
		||||
            m = re.match(APK_SDK_VERSION_PAT, line)
 | 
			
		||||
            if m is None:
 | 
			
		||||
                logging.error(line.replace('sdkVersion:', '')
 | 
			
		||||
                              + ' is not a valid minSdkVersion!')
 | 
			
		||||
            else:
 | 
			
		||||
                apk['minSdkVersion'] = m.group(1)
 | 
			
		||||
                # if target not set, default to min
 | 
			
		||||
                if 'targetSdkVersion' not in apk:
 | 
			
		||||
                    apk['targetSdkVersion'] = m.group(1)
 | 
			
		||||
        elif line.startswith("targetSdkVersion:"):
 | 
			
		||||
            m = re.match(APK_SDK_VERSION_PAT, line)
 | 
			
		||||
            if m is None:
 | 
			
		||||
                logging.error(line.replace('targetSdkVersion:', '')
 | 
			
		||||
                              + ' is not a valid targetSdkVersion!')
 | 
			
		||||
            else:
 | 
			
		||||
                apk['targetSdkVersion'] = m.group(1)
 | 
			
		||||
        elif line.startswith("maxSdkVersion:"):
 | 
			
		||||
            apk['maxSdkVersion'] = re.match(APK_SDK_VERSION_PAT, line).group(1)
 | 
			
		||||
        elif line.startswith("native-code:"):
 | 
			
		||||
            apk['nativecode'] = []
 | 
			
		||||
            for arch in line[13:].split(' '):
 | 
			
		||||
                apk['nativecode'].append(arch[1:-1])
 | 
			
		||||
        elif line.startswith('uses-permission:'):
 | 
			
		||||
            perm_match = re.match(APK_PERMISSION_PAT, line).groupdict()
 | 
			
		||||
            if perm_match['maxSdkVersion']:
 | 
			
		||||
                perm_match['maxSdkVersion'] = int(perm_match['maxSdkVersion'])
 | 
			
		||||
            permission = UsesPermission(
 | 
			
		||||
                perm_match['name'],
 | 
			
		||||
                perm_match['maxSdkVersion']
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            apk['uses-permission'].append(permission)
 | 
			
		||||
        elif line.startswith('uses-permission-sdk-23:'):
 | 
			
		||||
            perm_match = re.match(APK_PERMISSION_PAT, line).groupdict()
 | 
			
		||||
            if perm_match['maxSdkVersion']:
 | 
			
		||||
                perm_match['maxSdkVersion'] = int(perm_match['maxSdkVersion'])
 | 
			
		||||
            permission_sdk_23 = UsesPermissionSdk23(
 | 
			
		||||
                perm_match['name'],
 | 
			
		||||
                perm_match['maxSdkVersion']
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            apk['uses-permission-sdk-23'].append(permission_sdk_23)
 | 
			
		||||
 | 
			
		||||
        elif line.startswith('uses-feature:'):
 | 
			
		||||
            feature = re.match(APK_FEATURE_PAT, line).group(1)
 | 
			
		||||
            # Filter out this, it's only added with the latest SDK tools and
 | 
			
		||||
            # causes problems for lots of apps.
 | 
			
		||||
            if feature != "android.hardware.screen.portrait" \
 | 
			
		||||
                    and feature != "android.hardware.screen.landscape":
 | 
			
		||||
                if feature.startswith("android.feature."):
 | 
			
		||||
                    feature = feature[16:]
 | 
			
		||||
                apk['features'].add(feature)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def scan_apk_androguard(apk, apkfile):
 | 
			
		||||
    try:
 | 
			
		||||
        from androguard.core.bytecodes.apk import APK
 | 
			
		||||
        apkobject = APK(apkfile)
 | 
			
		||||
        if apkobject.is_valid_APK():
 | 
			
		||||
            arsc = apkobject.get_android_resources()
 | 
			
		||||
        else:
 | 
			
		||||
            if options.delete_unknown:
 | 
			
		||||
                if os.path.exists(apkfile):
 | 
			
		||||
                    logging.error("Failed to get apk information, deleting " + apkfile)
 | 
			
		||||
                    os.remove(apkfile)
 | 
			
		||||
                else:
 | 
			
		||||
                    logging.error("Could not find {0} to remove it".format(apkfile))
 | 
			
		||||
            else:
 | 
			
		||||
                logging.error("Failed to get apk information, skipping " + apkfile)
 | 
			
		||||
            raise BuildException("Invaild APK")
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        logging.critical("androguard library is not installed and aapt not present")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
    except FileNotFoundError:
 | 
			
		||||
        logging.error("Could not open apk file for analysis")
 | 
			
		||||
        raise BuildException("Invaild APK")
 | 
			
		||||
 | 
			
		||||
    apk['packageName'] = apkobject.get_package()
 | 
			
		||||
    apk['versionCode'] = int(apkobject.get_androidversion_code())
 | 
			
		||||
    apk['versionName'] = apkobject.get_androidversion_name()
 | 
			
		||||
    if apk['versionName'][0] == "@":
 | 
			
		||||
        version_id = int(apk['versionName'].replace("@", "0x"), 16)
 | 
			
		||||
        version_id = arsc.get_id(apk['packageName'], version_id)[1]
 | 
			
		||||
        apk['versionName'] = arsc.get_string(apk['packageName'], version_id)[1]
 | 
			
		||||
    apk['name'] = apkobject.get_app_name()
 | 
			
		||||
 | 
			
		||||
    if apkobject.get_max_sdk_version() is not None:
 | 
			
		||||
        apk['maxSdkVersion'] = apkobject.get_max_sdk_version()
 | 
			
		||||
    apk['minSdkVersion'] = apkobject.get_min_sdk_version()
 | 
			
		||||
    apk['targetSdkVersion'] = apkobject.get_target_sdk_version()
 | 
			
		||||
 | 
			
		||||
    icon_id = int(apkobject.get_element("application", "icon").replace("@", "0x"), 16)
 | 
			
		||||
    icon_name = arsc.get_id(apk['packageName'], icon_id)[1]
 | 
			
		||||
 | 
			
		||||
    density_re = re.compile("^res/(.*)/" + icon_name + ".*$")
 | 
			
		||||
 | 
			
		||||
    for file in apkobject.get_files():
 | 
			
		||||
        d_re = density_re.match(file)
 | 
			
		||||
        if d_re:
 | 
			
		||||
            folder = d_re.group(1).split('-')
 | 
			
		||||
            if len(folder) > 1:
 | 
			
		||||
                resolution = folder[1]
 | 
			
		||||
            else:
 | 
			
		||||
                resolution = 'mdpi'
 | 
			
		||||
            density = screen_resolutions[resolution]
 | 
			
		||||
            apk['icons_src'][density] = d_re.group(0)
 | 
			
		||||
 | 
			
		||||
    if apk['icons_src'].get('-1') is None:
 | 
			
		||||
        apk['icons_src']['-1'] = apk['icons_src']['160']
 | 
			
		||||
 | 
			
		||||
    arch_re = re.compile("^lib/(.*)/.*$")
 | 
			
		||||
    arch = set([arch_re.match(file).group(1) for file in apkobject.get_files() if arch_re.match(file)])
 | 
			
		||||
    if len(arch) >= 1:
 | 
			
		||||
        apk['nativecode'] = []
 | 
			
		||||
        apk['nativecode'].extend(sorted(list(arch)))
 | 
			
		||||
 | 
			
		||||
    xml = apkobject.get_android_manifest_xml()
 | 
			
		||||
 | 
			
		||||
    for item in xml.getElementsByTagName('uses-permission'):
 | 
			
		||||
        name = str(item.getAttribute("android:name"))
 | 
			
		||||
        maxSdkVersion = item.getAttribute("android:maxSdkVersion")
 | 
			
		||||
        maxSdkVersion = None if maxSdkVersion is '' else int(maxSdkVersion)
 | 
			
		||||
        permission = UsesPermission(
 | 
			
		||||
            name,
 | 
			
		||||
            maxSdkVersion
 | 
			
		||||
        )
 | 
			
		||||
        apk['uses-permission'].append(permission)
 | 
			
		||||
 | 
			
		||||
    for item in xml.getElementsByTagName('uses-permission-sdk-23'):
 | 
			
		||||
        name = str(item.getAttribute("android:name"))
 | 
			
		||||
        maxSdkVersion = item.getAttribute("android:maxSdkVersion")
 | 
			
		||||
        maxSdkVersion = None if maxSdkVersion is '' else int(maxSdkVersion)
 | 
			
		||||
        permission_sdk_23 = UsesPermissionSdk23(
 | 
			
		||||
            name,
 | 
			
		||||
            maxSdkVersion
 | 
			
		||||
        )
 | 
			
		||||
        apk['uses-permission-sdk-23'].append(permission_sdk_23)
 | 
			
		||||
 | 
			
		||||
    for item in xml.getElementsByTagName('uses-feature'):
 | 
			
		||||
        feature = str(item.getAttribute("android:name"))
 | 
			
		||||
        if feature != "android.hardware.screen.portrait" \
 | 
			
		||||
                and feature != "android.hardware.screen.landscape":
 | 
			
		||||
            if feature.startswith("android.feature."):
 | 
			
		||||
                feature = feature[16:]
 | 
			
		||||
        apk['features'].append(feature)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk):
 | 
			
		||||
    """Scan the apk with the given filename in the given repo directory.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -888,7 +1089,7 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk):
 | 
			
		|||
 | 
			
		||||
    if ' ' in apkfilename:
 | 
			
		||||
        logging.critical("Spaces in filenames are not allowed.")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
        return True, None, False
 | 
			
		||||
 | 
			
		||||
    apkfile = os.path.join(repodir, apkfilename)
 | 
			
		||||
    shasum = sha256sum(apkfile)
 | 
			
		||||
| 
						 | 
				
			
			@ -921,100 +1122,16 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk):
 | 
			
		|||
        apk['antiFeatures'] = set()
 | 
			
		||||
        if has_old_openssl(apkfile):
 | 
			
		||||
            apk['antiFeatures'].add('KnownVuln')
 | 
			
		||||
        p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
 | 
			
		||||
        if p.returncode != 0:
 | 
			
		||||
            if options.delete_unknown:
 | 
			
		||||
                if os.path.exists(apkfile):
 | 
			
		||||
                    logging.error("Failed to get apk information, deleting " + apkfile)
 | 
			
		||||
                    os.remove(apkfile)
 | 
			
		||||
                else:
 | 
			
		||||
                    logging.error("Could not find {0} to remove it".format(apkfile))
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if common.set_command_in_config('aapt'):
 | 
			
		||||
                logging.warning("Using AAPT for metadata")
 | 
			
		||||
                scan_apk_aapt(apk, apkfile)
 | 
			
		||||
            else:
 | 
			
		||||
                logging.error("Failed to get apk information, skipping " + apkfile)
 | 
			
		||||
                logging.warning("Using androguard for metadata")
 | 
			
		||||
                scan_apk_androguard(apk, apkfile)
 | 
			
		||||
        except BuildException:
 | 
			
		||||
            return True, None, False
 | 
			
		||||
        for line in p.output.splitlines():
 | 
			
		||||
            if line.startswith("package:"):
 | 
			
		||||
                try:
 | 
			
		||||
                    apk['packageName'] = re.match(APK_NAME_PAT, line).group(1)
 | 
			
		||||
                    apk['versionCode'] = int(re.match(APK_VERCODE_PAT, line).group(1))
 | 
			
		||||
                    apk['versionName'] = re.match(APK_VERNAME_PAT, line).group(1)
 | 
			
		||||
                except Exception as e:
 | 
			
		||||
                    logging.error("Package matching failed: " + str(e))
 | 
			
		||||
                    logging.info("Line was: " + line)
 | 
			
		||||
                    sys.exit(1)
 | 
			
		||||
            elif line.startswith("application:"):
 | 
			
		||||
                apk['name'] = re.match(APK_LABEL_PAT, line).group(1)
 | 
			
		||||
                # Keep path to non-dpi icon in case we need it
 | 
			
		||||
                match = re.match(APK_ICON_PAT_NODPI, line)
 | 
			
		||||
                if match:
 | 
			
		||||
                    apk['icons_src']['-1'] = match.group(1)
 | 
			
		||||
            elif line.startswith("launchable-activity:"):
 | 
			
		||||
                # Only use launchable-activity as fallback to application
 | 
			
		||||
                if not apk['name']:
 | 
			
		||||
                    apk['name'] = re.match(APK_LABEL_PAT, line).group(1)
 | 
			
		||||
                if '-1' not in apk['icons_src']:
 | 
			
		||||
                    match = re.match(APK_ICON_PAT_NODPI, line)
 | 
			
		||||
                    if match:
 | 
			
		||||
                        apk['icons_src']['-1'] = match.group(1)
 | 
			
		||||
            elif line.startswith("application-icon-"):
 | 
			
		||||
                match = re.match(APK_ICON_PAT, line)
 | 
			
		||||
                if match:
 | 
			
		||||
                    density = match.group(1)
 | 
			
		||||
                    path = match.group(2)
 | 
			
		||||
                    apk['icons_src'][density] = path
 | 
			
		||||
            elif line.startswith("sdkVersion:"):
 | 
			
		||||
                m = re.match(APK_SDK_VERSION_PAT, line)
 | 
			
		||||
                if m is None:
 | 
			
		||||
                    logging.error(line.replace('sdkVersion:', '')
 | 
			
		||||
                                  + ' is not a valid minSdkVersion!')
 | 
			
		||||
                else:
 | 
			
		||||
                    apk['minSdkVersion'] = m.group(1)
 | 
			
		||||
                    # if target not set, default to min
 | 
			
		||||
                    if 'targetSdkVersion' not in apk:
 | 
			
		||||
                        apk['targetSdkVersion'] = m.group(1)
 | 
			
		||||
            elif line.startswith("targetSdkVersion:"):
 | 
			
		||||
                m = re.match(APK_SDK_VERSION_PAT, line)
 | 
			
		||||
                if m is None:
 | 
			
		||||
                    logging.error(line.replace('targetSdkVersion:', '')
 | 
			
		||||
                                  + ' is not a valid targetSdkVersion!')
 | 
			
		||||
                else:
 | 
			
		||||
                    apk['targetSdkVersion'] = m.group(1)
 | 
			
		||||
            elif line.startswith("maxSdkVersion:"):
 | 
			
		||||
                apk['maxSdkVersion'] = re.match(APK_SDK_VERSION_PAT, line).group(1)
 | 
			
		||||
            elif line.startswith("native-code:"):
 | 
			
		||||
                apk['nativecode'] = []
 | 
			
		||||
                for arch in line[13:].split(' '):
 | 
			
		||||
                    apk['nativecode'].append(arch[1:-1])
 | 
			
		||||
            elif line.startswith('uses-permission:'):
 | 
			
		||||
                perm_match = re.match(APK_PERMISSION_PAT, line).groupdict()
 | 
			
		||||
                if perm_match['maxSdkVersion']:
 | 
			
		||||
                    perm_match['maxSdkVersion'] = int(perm_match['maxSdkVersion'])
 | 
			
		||||
                permission = UsesPermission(
 | 
			
		||||
                    perm_match['name'],
 | 
			
		||||
                    perm_match['maxSdkVersion']
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                apk['uses-permission'].append(permission)
 | 
			
		||||
            elif line.startswith('uses-permission-sdk-23:'):
 | 
			
		||||
                perm_match = re.match(APK_PERMISSION_PAT, line).groupdict()
 | 
			
		||||
                if perm_match['maxSdkVersion']:
 | 
			
		||||
                    perm_match['maxSdkVersion'] = int(perm_match['maxSdkVersion'])
 | 
			
		||||
                permission_sdk_23 = UsesPermissionSdk23(
 | 
			
		||||
                    perm_match['name'],
 | 
			
		||||
                    perm_match['maxSdkVersion']
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                apk['uses-permission-sdk-23'].append(permission_sdk_23)
 | 
			
		||||
 | 
			
		||||
            elif line.startswith('uses-feature:'):
 | 
			
		||||
                feature = re.match(APK_FEATURE_PAT, line).group(1)
 | 
			
		||||
                # Filter out this, it's only added with the latest SDK tools and
 | 
			
		||||
                # causes problems for lots of apps.
 | 
			
		||||
                if feature != "android.hardware.screen.portrait" \
 | 
			
		||||
                        and feature != "android.hardware.screen.landscape":
 | 
			
		||||
                    if feature.startswith("android.feature."):
 | 
			
		||||
                        feature = feature[16:]
 | 
			
		||||
                    apk['features'].add(feature)
 | 
			
		||||
 | 
			
		||||
        if 'minSdkVersion' not in apk:
 | 
			
		||||
            logging.warn("No SDK version information found in {0}".format(apkfile))
 | 
			
		||||
| 
						 | 
				
			
			@ -1029,7 +1146,7 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk):
 | 
			
		|||
        apk['sig'] = getsig(os.path.join(os.getcwd(), apkfile))
 | 
			
		||||
        if not apk['sig']:
 | 
			
		||||
            logging.critical("Failed to get apk signature")
 | 
			
		||||
            sys.exit(1)
 | 
			
		||||
            return True, None, False
 | 
			
		||||
 | 
			
		||||
        apkzip = zipfile.ZipFile(apkfile, 'r')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1068,10 +1185,8 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk):
 | 
			
		|||
                with open(icondest, 'wb') as f:
 | 
			
		||||
                    f.write(get_icon_bytes(apkzip, iconsrc))
 | 
			
		||||
                apk['icons'][density] = iconfilename
 | 
			
		||||
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                logging.warn("Error retrieving icon file: %s" % (e))
 | 
			
		||||
                del apk['icons'][density]
 | 
			
		||||
            except (zipfile.BadZipFile, ValueError, KeyError) as e:
 | 
			
		||||
                logging.warning("Error retrieving icon file: %s" % (icondest))
 | 
			
		||||
                del apk['icons_src'][density]
 | 
			
		||||
                empty_densities.append(density)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										92
									
								
								tests/androguard_test.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								tests/androguard_test.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import logging
 | 
			
		||||
import optparse
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import sys
 | 
			
		||||
import tempfile
 | 
			
		||||
import unittest
 | 
			
		||||
import yaml
 | 
			
		||||
from binascii import unhexlify
 | 
			
		||||
 | 
			
		||||
localmodule = os.path.realpath(
 | 
			
		||||
    os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
 | 
			
		||||
print('localmodule: ' + localmodule)
 | 
			
		||||
if localmodule not in sys.path:
 | 
			
		||||
    sys.path.insert(0, localmodule)
 | 
			
		||||
 | 
			
		||||
import fdroidserver.common
 | 
			
		||||
import fdroidserver.metadata
 | 
			
		||||
import fdroidserver.update
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UpdateTest(unittest.TestCase):
 | 
			
		||||
    '''fdroid androguard manual tests'''
 | 
			
		||||
 | 
			
		||||
    def testScanMetadataAndroguardAAPT(self):
 | 
			
		||||
 | 
			
		||||
        def _create_apkmetadata_object(apkName):
 | 
			
		||||
            '''Create an empty apk metadata object'''
 | 
			
		||||
            apk = {}
 | 
			
		||||
            apk['apkName'] = apkName
 | 
			
		||||
            apk['uses-permission'] = []
 | 
			
		||||
            apk['uses-permission-sdk-23'] = []
 | 
			
		||||
            apk['features'] = []
 | 
			
		||||
            apk['icons_src'] = {}
 | 
			
		||||
            return apk
 | 
			
		||||
        
 | 
			
		||||
        config = dict()
 | 
			
		||||
        fdroidserver.common.fill_config_defaults(config)
 | 
			
		||||
        fdroidserver.update.config = config
 | 
			
		||||
        os.chdir(os.path.dirname(__file__))
 | 
			
		||||
        if os.path.basename(os.getcwd()) != 'tests':
 | 
			
		||||
            raise Exception('This test must be run in the "tests/" subdir')
 | 
			
		||||
 | 
			
		||||
        config['ndk_paths'] = dict()
 | 
			
		||||
        config['accepted_formats'] = ['json', 'txt', 'yml']
 | 
			
		||||
        fdroidserver.common.config = config
 | 
			
		||||
        fdroidserver.update.config = config
 | 
			
		||||
 | 
			
		||||
        fdroidserver.update.options = type('', (), {})()
 | 
			
		||||
        fdroidserver.update.options.clean = True
 | 
			
		||||
        fdroidserver.update.options.delete_unknown = True
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(fdroidserver.common.set_command_in_config('aapt'))
 | 
			
		||||
        try:
 | 
			
		||||
            from androguard.core.bytecodes.apk import APK
 | 
			
		||||
        except ImportError:
 | 
			
		||||
            raise Exception("androguard not installed!")
 | 
			
		||||
 | 
			
		||||
        apkList = ['../info.guardianproject.urzip.apk', '../org.dyndns.fules.ck_20.apk']
 | 
			
		||||
 | 
			
		||||
        for apkName in apkList:
 | 
			
		||||
            logging.debug("Processing " + apkName)
 | 
			
		||||
            apkfile = os.path.join('repo', apkName)
 | 
			
		||||
 | 
			
		||||
            apkaapt = _create_apkmetadata_object(apkName)
 | 
			
		||||
            logging.debug("Using AAPT for metadata")
 | 
			
		||||
            fdroidserver.update.scan_apk_aapt(apkaapt, apkfile)
 | 
			
		||||
            # avoid AAPT application name bug
 | 
			
		||||
            del apkaapt['name']
 | 
			
		||||
 | 
			
		||||
            apkandroguard = _create_apkmetadata_object(apkName)
 | 
			
		||||
            logging.debug("Using androguard for metadata")
 | 
			
		||||
            fdroidserver.update.scan_apk_androguard(apkandroguard, apkfile)
 | 
			
		||||
            # avoid AAPT application name bug
 | 
			
		||||
            del apkandroguard['name']
 | 
			
		||||
 | 
			
		||||
            self.maxDiff = None
 | 
			
		||||
            self.assertEqual(apkaapt, apkandroguard)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    parser = optparse.OptionParser()
 | 
			
		||||
    parser.add_option("-v", "--verbose", action="store_true", default=False,
 | 
			
		||||
                      help="Spew out even more information than normal")
 | 
			
		||||
    (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
 | 
			
		||||
 | 
			
		||||
    newSuite = unittest.TestSuite()
 | 
			
		||||
    newSuite.addTest(unittest.makeSuite(UpdateTest))
 | 
			
		||||
    unittest.main()
 | 
			
		||||
							
								
								
									
										20
									
								
								tests/metadata/apk/info.guardianproject.urzip.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/metadata/apk/info.guardianproject.urzip.yaml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
antiFeatures: !!set {}
 | 
			
		||||
features: []
 | 
			
		||||
hash: abfb3adb7496611749e7abfb014c5c789e3a02489e48a5c3665110d1b1acd931
 | 
			
		||||
hashType: sha256
 | 
			
		||||
icon: info.guardianproject.urzip.100.png
 | 
			
		||||
icons:
 | 
			
		||||
  '0': info.guardianproject.urzip.100.png
 | 
			
		||||
  '160': info.guardianproject.urzip.100.png
 | 
			
		||||
icons_src:
 | 
			
		||||
  '-1': res/drawable/ic_launcher.png
 | 
			
		||||
  '160': res/drawable/ic_launcher.png
 | 
			
		||||
minSdkVersion: '4'
 | 
			
		||||
packageName: info.guardianproject.urzip
 | 
			
		||||
sig: e0ecb5fc2d63088e4a07ae410a127722
 | 
			
		||||
size: 9969
 | 
			
		||||
targetSdkVersion: '18'
 | 
			
		||||
uses-permission: []
 | 
			
		||||
uses-permission-sdk-23: []
 | 
			
		||||
versionCode: 100
 | 
			
		||||
versionName: '0.1'
 | 
			
		||||
							
								
								
									
										41
									
								
								tests/metadata/apk/org.dyndns.fules.ck.yaml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								tests/metadata/apk/org.dyndns.fules.ck.yaml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
antiFeatures: !!set {}
 | 
			
		||||
features: []
 | 
			
		||||
hash: 897486e1f857c6c0ee32ccbad0e1b8cd82f6d0e65a44a23f13f852d2b63a18c8
 | 
			
		||||
hashType: sha256
 | 
			
		||||
icon: org.dyndns.fules.ck.20.png
 | 
			
		||||
icons:
 | 
			
		||||
  '0': org.dyndns.fules.ck.20.png
 | 
			
		||||
  '120': org.dyndns.fules.ck.20.png
 | 
			
		||||
  '160': org.dyndns.fules.ck.20.png
 | 
			
		||||
  '240': org.dyndns.fules.ck.20.png
 | 
			
		||||
icons_src:
 | 
			
		||||
  '-1': res/drawable-mdpi-v4/icon_launcher.png
 | 
			
		||||
  '120': res/drawable-ldpi-v4/icon_launcher.png
 | 
			
		||||
  '160': res/drawable-mdpi-v4/icon_launcher.png
 | 
			
		||||
  '240': res/drawable-hdpi-v4/icon_launcher.png
 | 
			
		||||
minSdkVersion: '7'
 | 
			
		||||
nativecode:
 | 
			
		||||
- arm64-v8a
 | 
			
		||||
- armeabi
 | 
			
		||||
- armeabi-v7a
 | 
			
		||||
- mips
 | 
			
		||||
- mips64
 | 
			
		||||
- x86
 | 
			
		||||
- x86_64
 | 
			
		||||
packageName: org.dyndns.fules.ck
 | 
			
		||||
sig: 9bf7a6a67f95688daec75eab4b1436ac
 | 
			
		||||
size: 132453
 | 
			
		||||
targetSdkVersion: '8'
 | 
			
		||||
uses-permission:
 | 
			
		||||
- !!python/object/new:fdroidserver.update.UsesPermission
 | 
			
		||||
  - android.permission.BIND_INPUT_METHOD
 | 
			
		||||
  - null
 | 
			
		||||
- !!python/object/new:fdroidserver.update.UsesPermission
 | 
			
		||||
  - android.permission.READ_EXTERNAL_STORAGE
 | 
			
		||||
  - null
 | 
			
		||||
- !!python/object/new:fdroidserver.update.UsesPermission
 | 
			
		||||
  - android.permission.VIBRATE
 | 
			
		||||
  - null
 | 
			
		||||
uses-permission-sdk-23: []
 | 
			
		||||
versionCode: 20
 | 
			
		||||
versionName: v1.6pre2
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								tests/org.dyndns.fules.ck_20.apk
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								tests/org.dyndns.fules.ck_20.apk
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -224,6 +224,54 @@ class UpdateTest(unittest.TestCase):
 | 
			
		|||
                self.assertIsNone(apk.get('obbMainFile'))
 | 
			
		||||
                self.assertIsNone(apk.get('obbPatchFile'))
 | 
			
		||||
 | 
			
		||||
    def testScanApkMetadata(self):
 | 
			
		||||
 | 
			
		||||
        def _build_yaml_representer(dumper, data):
 | 
			
		||||
            '''Creates a YAML representation of a Build instance'''
 | 
			
		||||
            return dumper.represent_dict(data)
 | 
			
		||||
 | 
			
		||||
        config = dict()
 | 
			
		||||
        fdroidserver.common.fill_config_defaults(config)
 | 
			
		||||
        fdroidserver.update.config = config
 | 
			
		||||
        os.chdir(os.path.dirname(__file__))
 | 
			
		||||
        if os.path.basename(os.getcwd()) != 'tests':
 | 
			
		||||
            raise Exception('This test must be run in the "tests/" subdir')
 | 
			
		||||
 | 
			
		||||
        config['ndk_paths'] = dict()
 | 
			
		||||
        config['accepted_formats'] = ['json', 'txt', 'yml']
 | 
			
		||||
        fdroidserver.common.config = config
 | 
			
		||||
        fdroidserver.update.config = config
 | 
			
		||||
 | 
			
		||||
        fdroidserver.update.options = type('', (), {})()
 | 
			
		||||
        fdroidserver.update.options.clean = True
 | 
			
		||||
        fdroidserver.update.options.delete_unknown = True
 | 
			
		||||
 | 
			
		||||
        for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
 | 
			
		||||
            if not os.path.exists(icon_dir):
 | 
			
		||||
                os.makedirs(icon_dir)
 | 
			
		||||
 | 
			
		||||
        knownapks = fdroidserver.common.KnownApks()
 | 
			
		||||
        apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']
 | 
			
		||||
 | 
			
		||||
        for apkName in apkList:
 | 
			
		||||
            _, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, False)
 | 
			
		||||
            # Don't care about the date added to the repo and relative apkName
 | 
			
		||||
            del apk['added']
 | 
			
		||||
            del apk['apkName']
 | 
			
		||||
            # avoid AAPT application name bug
 | 
			
		||||
            del apk['name']
 | 
			
		||||
 | 
			
		||||
            savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
 | 
			
		||||
            # Uncomment to save APK metadata
 | 
			
		||||
            # with open(savepath, 'w') as f:
 | 
			
		||||
            #     yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
 | 
			
		||||
            #     yaml.dump(apk, f, default_flow_style=False)
 | 
			
		||||
 | 
			
		||||
            with open(savepath, 'r') as f:
 | 
			
		||||
                frompickle = yaml.load(f)
 | 
			
		||||
            self.maxDiff = None
 | 
			
		||||
            self.assertEqual(apk, frompickle)
 | 
			
		||||
 | 
			
		||||
    def test_scan_invalid_apk(self):
 | 
			
		||||
        os.chdir(os.path.join(localmodule, 'tests'))
 | 
			
		||||
        if os.path.basename(os.getcwd()) != 'tests':
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue