# has to be manually set in test_aapt_version()
MINIMUM_AAPT_VERSION = '26.0.0'
+VERCODE_OPERATION_RE = re.compile(r'^([ 0-9/*+-]|%c)+$')
+
# A signature block file with a .DSA, .RSA, or .EC extension
CERT_PATH_REGEX = re.compile(r'^META-INF/.*\.(DSA|EC|RSA)$')
APK_NAME_REGEX = re.compile(r'^([a-zA-Z][\w.]*)_(-?[0-9]+)_?([0-9a-f]{7})?\.apk')
r'^java-([6-9])-oracle$', # Debian WebUpd8
r'^jdk-([6-9])-oracle-.*$', # Debian make-jpkg
r'^java-([6-9])-openjdk-[^c][^o][^m].*$', # Debian
+ r'^oracle-jdk-bin-1\.([7-9]).*$', # Gentoo (oracle)
+ r'^icedtea-bin-([7-9]).*$', # Gentoo (openjdk)
]:
m = re.match(regex, j)
if not m:
pathlist += glob.glob('/usr/java/jdk1.[6-9]*')
pathlist += glob.glob('/System/Library/Java/JavaVirtualMachines/1.[6-9].0.jdk')
pathlist += glob.glob('/Library/Java/JavaVirtualMachines/*jdk*[6-9]*')
+ pathlist += glob.glob('/opt/oracle-jdk-*1.[7-9]*')
+ pathlist += glob.glob('/opt/icedtea-*[7-9]*')
if os.getenv('JAVA_HOME') is not None:
pathlist.append(os.getenv('JAVA_HOME'))
if os.getenv('PROGRAMFILES') is not None:
import requests
r = requests.head(remote)
r.raise_for_status()
+ location = r.headers.get('location')
+ if location and not location.startswith('https://'):
+ raise VCSException(_('Invalid redirect to non-HTTPS: {before} -> {after} ')
+ .format(before=remote, after=location))
gitsvn_args.extend(['--', remote, self.local])
p = self.git(gitsvn_args)
def gotorevisionx(self, rev):
if not os.path.exists(self.local):
- p = FDroidPopen(['hg', 'clone', '--ssh', 'false', '--', self.remote, self.local],
+ p = FDroidPopen(['hg', 'clone', '--ssh', '/bin/false', '--', self.remote, self.local],
output=False)
if p.returncode != 0:
self.clone_failed = True
raise VCSException("Unexpected output from hg status -uS: " + line)
FDroidPopen(['rm', '-rf', '--', line[2:]], cwd=self.local, output=False)
if not self.refreshed:
- p = FDroidPopen(['hg', 'pull', '--ssh', 'false'], cwd=self.local, output=False)
+ p = FDroidPopen(['hg', 'pull', '--ssh', '/bin/false'], cwd=self.local, output=False)
if p.returncode != 0:
raise VCSException("Hg pull failed", p.output)
self.refreshed = True
os.path.join(root, 'AndroidManifest.xml'))
-vcsearch_g = re.compile(r'''.*[Vv]ersionCode[ =]+["']*([0-9]+)["']*''').search
-vnsearch_g = re.compile(r'.*[Vv]ersionName *=* *(["\'])((?:(?=(\\?))\3.)*?)\1.*').search
-psearch_g = re.compile(r'.*(packageName|applicationId) *=* *["\']([^"]+)["\'].*').search
+vcsearch_g = re.compile(r'''.*[Vv]ersionCode\s*=?\s*["']*([0-9]+)["']*''').search
+vnsearch_g = re.compile(r'''.*[Vv]ersionName\s*=?\s*(["'])((?:(?=(\\?))\3.)*?)\1.*''').search
+vnssearch_g = re.compile(r'''.*[Vv]ersionNameSuffix\s*=?\s*(["'])((?:(?=(\\?))\3.)*?)\1.*''').search
+psearch_g = re.compile(r'''.*(packageName|applicationId)\s*=*\s*["']([^"']+)["'].*''').search
+fsearch_g = re.compile(r'''.*(applicationIdSuffix)\s*=*\s*["']([^"']+)["'].*''').search
def app_matches_packagename(app, package):
package = None
flavour = None
+ temp_app_id = None
+ temp_version_name = None
if app.builds and 'gradle' in app.builds[-1] and app.builds[-1].gradle:
flavour = app.builds[-1].gradle[-1]
if gradle_comment.match(line):
continue
+ if "applicationId" in line and not temp_app_id:
+ matches = psearch_g(line)
+ if matches:
+ temp_app_id = matches.group(2)
+
+ if "versionName" in line and not temp_version_name:
+ matches = vnsearch_g(line)
+ if matches:
+ temp_version_name = matches.group(2)
+
if inside_flavour_group > 0:
if inside_required_flavour > 0:
matches = psearch_g(line)
s = matches.group(2)
if app_matches_packagename(app, s):
package = s
+ else:
+ # If build.gradle contains applicationIdSuffix add it to the end of package name
+ matches = fsearch_g(line)
+ if matches and temp_app_id:
+ suffix = matches.group(2)
+ temp_app_id = temp_app_id + suffix
+ if app_matches_packagename(app, temp_app_id):
+ package = temp_app_id
matches = vnsearch_g(line)
if matches:
version = matches.group(2)
+ else:
+ # If build.gradle contains applicationNameSuffix add it to the end of version name
+ matches = vnssearch_g(line)
+ if matches and temp_version_name:
+ name_suffix = matches.group(2)
+ version = temp_version_name + name_suffix
matches = vcsearch_g(line)
if matches:
return os.path.splitext(filename)[1].lower()[1:]
-def get_apk_debuggable_aapt(apkfile):
+def use_androguard():
+ """Report if androguard is available, and config its debug 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)
+ return True
+ except ImportError:
+ return False
+
+
+use_androguard.show_path = True
+
+
+def _get_androguard_APK(apkfile):
+ try:
+ from androguard.core.bytecodes.apk import APK
+ except ImportError:
+ raise FDroidException("androguard library is not installed and aapt not present")
+
+ return APK(apkfile)
+
+
+def ensure_final_value(packageName, arsc, value):
+ """Ensure incoming value is always the value, not the resid
+
+ androguard will sometimes return the Android "resId" aka
+ Resource ID instead of the actual value. This checks whether
+ the value is actually a resId, then performs the Android
+ Resource lookup as needed.
+
+ """
+ if value:
+ returnValue = value
+ if value[0] == '@':
+ try: # can be a literal value or a resId
+ res_id = int('0x' + value[1:], 16)
+ res_id = arsc.get_id(packageName, res_id)[1]
+ returnValue = arsc.get_string(packageName, res_id)[1]
+ except ValueError:
+ pass
+ return returnValue
+
+
+def is_apk_and_debuggable_aapt(apkfile):
p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'],
output=False)
if p.returncode != 0:
return False
-def get_apk_debuggable_androguard(apkfile):
- try:
- from androguard.core.bytecodes.apk import APK
- except ImportError:
- raise FDroidException("androguard library is not installed and aapt not present")
-
- apkobject = APK(apkfile)
+def is_apk_and_debuggable_androguard(apkfile):
+ apkobject = _get_androguard_APK(apkfile)
if apkobject.is_valid_APK():
debuggable = apkobject.get_element("application", "debuggable")
if debuggable is not None:
return False
-def isApkAndDebuggable(apkfile):
+def is_apk_and_debuggable(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
- if SdkToolsPopen(['aapt', 'version'], output=False):
- return get_apk_debuggable_aapt(apkfile)
+ if use_androguard():
+ return is_apk_and_debuggable_androguard(apkfile)
else:
- return get_apk_debuggable_androguard(apkfile)
+ return is_apk_and_debuggable_aapt(apkfile)
-def get_apk_id_aapt(apkfile):
- """Extrat identification information from APK using aapt.
+def get_apk_id(apkfile):
+ """Extract identification information from APK using aapt.
:param apkfile: path to an APK file.
:returns: triplet (appid, version code, version name)
"""
- r = re.compile("package: name='(?P<appid>.*)' versionCode='(?P<vercode>.*)' versionName='(?P<vername>.*)' platformBuildVersionName='.*'")
+ if use_androguard():
+ return get_apk_id_androguard(apkfile)
+ else:
+ return get_apk_id_aapt(apkfile)
+
+
+def get_apk_id_androguard(apkfile):
+ if not os.path.exists(apkfile):
+ raise FDroidException(_("Reading packageName/versionCode/versionName failed, APK invalid: '{apkfilename}'")
+ .format(apkfilename=apkfile))
+ a = _get_androguard_APK(apkfile)
+ versionName = ensure_final_value(a.package, a.get_android_resources(), a.get_androidversion_name())
+ if not versionName:
+ versionName = '' # versionName is expected to always be a str
+ return a.package, a.get_androidversion_code(), versionName
+
+
+def get_apk_id_aapt(apkfile):
+ r = re.compile("^package: name='(?P<appid>.*)' versionCode='(?P<vercode>.*)' versionName='(?P<vername>.*?)'(?: platformBuildVersionName='.*')?")
p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
for line in p.output.splitlines():
m = r.match(line)
o.write('android.library.reference.%d=%s\n' % (number, relpath))
-apk_sigfile = re.compile(r'META-INF/[0-9A-Za-z]+\.(SF|RSA|DSA|EC)')
+apk_sigfile = re.compile(r'META-INF/[0-9A-Za-z_\-]+\.(SF|RSA|DSA|EC)')
def signer_fingerprint_short(sig):
jarsigner passes unsigned APKs as "verified"! So this has to turn
on -strict then check for result 4.
+ Just to be safe, this never reuses the file, and locks down the
+ file permissions while in use. That should prevent a bad actor
+ from changing the settings during operation.
+
:returns: boolean whether the APK was verified
+
"""
_java_security = os.path.join(os.getcwd(), '.java.security')
+ if os.path.exists(_java_security):
+ os.remove(_java_security)
with open(_java_security, 'w') as fp:
fp.write('jdk.jar.disabledAlgorithms=MD2, RSA keySize < 1024')
+ os.chmod(_java_security, 0o400)
try:
cmd = [
else:
logging.debug(_('JAR signature verified: {path}').format(path=apk))
return True
+ finally:
+ if os.path.exists(_java_security):
+ os.chmod(_java_security, 0o600)
+ os.remove(_java_security)
logging.error(_('Old APK signature failed to verify: {path}').format(path=apk)
+ '\n' + output.decode('utf-8'))
log += '* ' + name + ' (' + version + ')\n'
return log
+
+
+def get_git_describe_link():
+ """Get a link to the current fdroiddata commit, to post to the wiki
+
+ """
+ try:
+ output = subprocess.check_output(['git', 'describe', '--always', '--dirty', '--abbrev=0'],
+ universal_newlines=True).strip()
+ except subprocess.CalledProcessError:
+ pass
+ if output:
+ commit = output.replace('-dirty', '')
+ return ('* fdroiddata: [https://gitlab.com/fdroid/fdroiddata/commit/{commit} {id}]\n'
+ .format(commit=commit, id=output))
+ else:
+ logging.error(_("'{path}' failed to execute!").format(path='git describe'))
+ return ''