use androguard if aapt isn't found
This commit is contained in:
parent
9607cdb621
commit
06598ae406
|
@ -483,15 +483,23 @@ def capitalize_intact(string):
|
||||||
return string[0].upper() + string[1:]
|
return string[0].upper() + string[1:]
|
||||||
|
|
||||||
|
|
||||||
def get_metadata_from_apk(app, build, apkfile):
|
def has_native_code(apkobj):
|
||||||
"""get the required metadata from the built APK"""
|
"""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
|
vercode = None
|
||||||
version = None
|
version = None
|
||||||
foundid = None
|
foundid = None
|
||||||
nativecode = None
|
nativecode = None
|
||||||
|
|
||||||
|
p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
|
||||||
|
|
||||||
for line in p.output.splitlines():
|
for line in p.output.splitlines():
|
||||||
if line.startswith("package:"):
|
if line.startswith("package:"):
|
||||||
pat = re.compile(".*name='([a-zA-Z0-9._]*)'.*")
|
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:"):
|
elif line.startswith("native-code:"):
|
||||||
nativecode = line[12:]
|
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
|
# Ignore empty strings or any kind of space/newline chars that we don't
|
||||||
# care about
|
# care about
|
||||||
if nativecode is not None:
|
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):
|
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."""
|
"""Do a build locally."""
|
||||||
|
|
||||||
ndk_path = build.ndk_path()
|
ndk_path = build.ndk_path()
|
||||||
if build.ndk or (build.buildjni and build.buildjni != ['no']):
|
if build.ndk or (build.buildjni and build.buildjni != ['no']):
|
||||||
if not ndk_path:
|
if not ndk_path:
|
||||||
|
|
|
@ -46,6 +46,8 @@ from pyasn1.codec.der import decoder, encoder
|
||||||
from pyasn1_modules import rfc2315
|
from pyasn1_modules import rfc2315
|
||||||
from pyasn1.error import PyAsn1Error
|
from pyasn1.error import PyAsn1Error
|
||||||
|
|
||||||
|
from distutils.util import strtobool
|
||||||
|
|
||||||
import fdroidserver.metadata
|
import fdroidserver.metadata
|
||||||
from .asynchronousfilereader import AsynchronousFileReader
|
from .asynchronousfilereader import AsynchronousFileReader
|
||||||
|
|
||||||
|
@ -1690,14 +1692,7 @@ def get_file_extension(filename):
|
||||||
return os.path.splitext(filename)[1].lower()[1:]
|
return os.path.splitext(filename)[1].lower()[1:]
|
||||||
|
|
||||||
|
|
||||||
def isApkAndDebuggable(apkfile, config):
|
def get_apk_debuggable_aapt(apkfile):
|
||||||
"""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
|
|
||||||
|
|
||||||
p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'],
|
p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'],
|
||||||
output=False)
|
output=False)
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
|
@ -1709,6 +1704,35 @@ def isApkAndDebuggable(apkfile, config):
|
||||||
return False
|
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:
|
class PopenResult:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.returncode = None
|
self.returncode = None
|
||||||
|
|
|
@ -41,7 +41,7 @@ from . import btlog
|
||||||
from . import common
|
from . import common
|
||||||
from . import index
|
from . import index
|
||||||
from . import metadata
|
from . import metadata
|
||||||
from .common import SdkToolsPopen
|
from .common import BuildException, SdkToolsPopen
|
||||||
|
|
||||||
METADATA_VERSION = 18
|
METADATA_VERSION = 18
|
||||||
|
|
||||||
|
@ -60,6 +60,17 @@ APK_PERMISSION_PAT = \
|
||||||
APK_FEATURE_PAT = re.compile(".*name='([^']*)'.*")
|
APK_FEATURE_PAT = re.compile(".*name='([^']*)'.*")
|
||||||
|
|
||||||
screen_densities = ['640', '480', '320', '240', '160', '120']
|
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
|
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
|
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):
|
def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk):
|
||||||
"""Scan the apk with the given filename in the given repo directory.
|
"""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:
|
if ' ' in apkfilename:
|
||||||
logging.critical("Spaces in filenames are not allowed.")
|
logging.critical("Spaces in filenames are not allowed.")
|
||||||
sys.exit(1)
|
return True, None, False
|
||||||
|
|
||||||
apkfile = os.path.join(repodir, apkfilename)
|
apkfile = os.path.join(repodir, apkfilename)
|
||||||
shasum = sha256sum(apkfile)
|
shasum = sha256sum(apkfile)
|
||||||
|
@ -921,100 +1122,16 @@ def scan_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk):
|
||||||
apk['antiFeatures'] = set()
|
apk['antiFeatures'] = set()
|
||||||
if has_old_openssl(apkfile):
|
if has_old_openssl(apkfile):
|
||||||
apk['antiFeatures'].add('KnownVuln')
|
apk['antiFeatures'].add('KnownVuln')
|
||||||
p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
|
|
||||||
if p.returncode != 0:
|
try:
|
||||||
if options.delete_unknown:
|
if common.set_command_in_config('aapt'):
|
||||||
if os.path.exists(apkfile):
|
logging.warning("Using AAPT for metadata")
|
||||||
logging.error("Failed to get apk information, deleting " + apkfile)
|
scan_apk_aapt(apk, apkfile)
|
||||||
os.remove(apkfile)
|
|
||||||
else:
|
|
||||||
logging.error("Could not find {0} to remove it".format(apkfile))
|
|
||||||
else:
|
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
|
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:
|
if 'minSdkVersion' not in apk:
|
||||||
logging.warn("No SDK version information found in {0}".format(apkfile))
|
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))
|
apk['sig'] = getsig(os.path.join(os.getcwd(), apkfile))
|
||||||
if not apk['sig']:
|
if not apk['sig']:
|
||||||
logging.critical("Failed to get apk signature")
|
logging.critical("Failed to get apk signature")
|
||||||
sys.exit(1)
|
return True, None, False
|
||||||
|
|
||||||
apkzip = zipfile.ZipFile(apkfile, 'r')
|
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:
|
with open(icondest, 'wb') as f:
|
||||||
f.write(get_icon_bytes(apkzip, iconsrc))
|
f.write(get_icon_bytes(apkzip, iconsrc))
|
||||||
apk['icons'][density] = iconfilename
|
apk['icons'][density] = iconfilename
|
||||||
|
except (zipfile.BadZipFile, ValueError, KeyError) as e:
|
||||||
except Exception as e:
|
logging.warning("Error retrieving icon file: %s" % (icondest))
|
||||||
logging.warn("Error retrieving icon file: %s" % (e))
|
|
||||||
del apk['icons'][density]
|
|
||||||
del apk['icons_src'][density]
|
del apk['icons_src'][density]
|
||||||
empty_densities.append(density)
|
empty_densities.append(density)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -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'
|
|
@ -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
|
Binary file not shown.
|
@ -224,6 +224,54 @@ class UpdateTest(unittest.TestCase):
|
||||||
self.assertIsNone(apk.get('obbMainFile'))
|
self.assertIsNone(apk.get('obbMainFile'))
|
||||||
self.assertIsNone(apk.get('obbPatchFile'))
|
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):
|
def test_scan_invalid_apk(self):
|
||||||
os.chdir(os.path.join(localmodule, 'tests'))
|
os.chdir(os.path.join(localmodule, 'tests'))
|
||||||
if os.path.basename(os.getcwd()) != 'tests':
|
if os.path.basename(os.getcwd()) != 'tests':
|
||||||
|
|
Loading…
Reference in New Issue