chiark / gitweb /
handle bad SDK Version values in APKs
[fdroidserver.git] / tests / update.TestCase
index bb79a35f93d57447d1970b7c9330c0b479049acc..6616669feff431bcbd8e40ab1932522aba950ad9 100755 (executable)
@@ -8,11 +8,13 @@ import logging
 import optparse
 import os
 import shutil
+import subprocess
 import sys
 import tempfile
 import unittest
 import yaml
 from binascii import unhexlify
+from distutils.version import LooseVersion
 
 localmodule = os.path.realpath(
     os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
@@ -30,6 +32,14 @@ from fdroidserver.common import FDroidPopen
 class UpdateTest(unittest.TestCase):
     '''fdroid update'''
 
+    def setUp(self):
+        logging.basicConfig(level=logging.INFO)
+        self.basedir = os.path.join(localmodule, 'tests')
+        self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
+        if not os.path.exists(self.tmpdir):
+            os.makedirs(self.tmpdir)
+        os.chdir(self.basedir)
+
     def testInsertStoreMetadata(self):
         config = dict()
         fdroidserver.common.fill_config_defaults(config)
@@ -40,19 +50,47 @@ class UpdateTest(unittest.TestCase):
 
         shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True)
 
+        shutil.rmtree(os.path.join('build', 'com.nextcloud.client'), ignore_errors=True)
+        shutil.copytree(os.path.join('source-files', 'com.nextcloud.client'),
+                        os.path.join('build', 'com.nextcloud.client'))
+
+        shutil.rmtree(os.path.join('build', 'com.nextcloud.client.dev'), ignore_errors=True)
+        shutil.copytree(os.path.join('source-files', 'com.nextcloud.client.dev'),
+                        os.path.join('build', 'com.nextcloud.client.dev'))
+
+        shutil.rmtree(os.path.join('build', 'eu.siacs.conversations'), ignore_errors=True)
+        shutil.copytree(os.path.join('source-files', 'eu.siacs.conversations'),
+                        os.path.join('build', 'eu.siacs.conversations'))
+
         apps = dict()
-        for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current'):
-            apps[packageName] = dict()
+        for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current',
+                            'com.nextcloud.client', 'com.nextcloud.client.dev',
+                            'eu.siacs.conversations'):
+            apps[packageName] = fdroidserver.metadata.App()
             apps[packageName]['id'] = packageName
             apps[packageName]['CurrentVersionCode'] = 0xcafebeef
+
         apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100
+
+        buildnextcloudclient = fdroidserver.metadata.Build()
+        buildnextcloudclient.gradle = ['generic']
+        apps['com.nextcloud.client']['builds'] = [buildnextcloudclient]
+
+        buildnextclouddevclient = fdroidserver.metadata.Build()
+        buildnextclouddevclient.gradle = ['versionDev']
+        apps['com.nextcloud.client.dev']['builds'] = [buildnextclouddevclient]
+
+        build_conversations = fdroidserver.metadata.Build()
+        build_conversations.gradle = ['free']
+        apps['eu.siacs.conversations']['builds'] = [build_conversations]
+
         fdroidserver.update.insert_localized_app_metadata(apps)
 
         appdir = os.path.join('repo', 'info.guardianproject.urzip', 'en-US')
         self.assertTrue(os.path.isfile(os.path.join(appdir, 'icon.png')))
         self.assertTrue(os.path.isfile(os.path.join(appdir, 'featureGraphic.png')))
 
-        self.assertEqual(3, len(apps))
+        self.assertEqual(6, len(apps))
         for packageName, app in apps.items():
             self.assertTrue('localized' in app)
             self.assertTrue('en-US' in app['localized'])
@@ -75,17 +113,25 @@ class UpdateTest(unittest.TestCase):
                 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
                 self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots']))
                 self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots']))
+            elif packageName == 'com.nextcloud.client':
+                self.assertEqual('Nextcloud', app['localized']['en-US']['name'])
+                self.assertEqual(1073, len(app['localized']['en-US']['description']))
+                self.assertEqual(78, len(app['localized']['en-US']['summary']))
+            elif packageName == 'com.nextcloud.client.dev':
+                self.assertEqual('Nextcloud Dev', app['localized']['en-US']['name'])
+                self.assertEqual(586, len(app['localized']['en-US']['description']))
+                self.assertEqual(79, len(app['localized']['en-US']['summary']))
+            elif packageName == 'eu.siacs.conversations':
+                self.assertEqual('Conversations', app['localized']['en-US']['name'])
 
     def test_insert_triple_t_metadata(self):
-        importer = os.path.join(localmodule, 'tests', 'tmp', 'importer')
+        importer = os.path.join(self.basedir, 'tmp', 'importer')
         packageName = 'org.fdroid.ci.test.app'
         if not os.path.isdir(importer):
             logging.warning('skipping test_insert_triple_t_metadata, import.TestCase must run first!')
             return
-        tmpdir = os.path.join(localmodule, '.testfiles')
-        if not os.path.exists(tmpdir):
-            os.makedirs(tmpdir)
-        tmptestsdir = tempfile.mkdtemp(prefix='test_insert_triple_t_metadata-', dir=tmpdir)
+        tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
+                                       dir=self.tmpdir)
         packageDir = os.path.join(tmptestsdir, 'build', packageName)
         shutil.copytree(importer, packageDir)
 
@@ -207,14 +253,14 @@ class UpdateTest(unittest.TestCase):
         apps = fdroidserver.metadata.read_metadata(xref=True)
         knownapks = fdroidserver.common.KnownApks()
         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
-        self.assertEqual(len(apks), 11)
+        self.assertEqual(len(apks), 14)
         apk = apks[0]
         self.assertEqual(apk['packageName'], 'com.politedroid')
         self.assertEqual(apk['versionCode'], 3)
         self.assertEqual(apk['minSdkVersion'], '3')
         self.assertEqual(apk['targetSdkVersion'], '3')
         self.assertFalse('maxSdkVersion' in apk)
-        apk = apks[4]
+        apk = apks[6]
         self.assertEqual(apk['packageName'], 'obb.main.oldversion')
         self.assertEqual(apk['versionCode'], 1444412523)
         self.assertEqual(apk['minSdkVersion'], '4')
@@ -246,13 +292,21 @@ class UpdateTest(unittest.TestCase):
     def test_scan_apk(self):
         config = dict()
         fdroidserver.common.fill_config_defaults(config)
+        fdroidserver.common.config = config
         fdroidserver.update.config = config
         os.chdir(os.path.join(localmodule, 'tests'))
         if os.path.basename(os.getcwd()) != 'tests':
             raise Exception('This test must be run in the "tests/" subdir')
 
-        apk_info = fdroidserver.update.scan_apk('org.dyndns.fules.ck_20.apk')
+        apk_info = fdroidserver.update.scan_apk('repo/souch.smsbypass_9.apk')
+        self.assertIsNone(apk_info.get('maxSdkVersion'))
+        self.assertEqual(apk_info.get('versionName'), '0.9')
+
+        apk_info = fdroidserver.update.scan_apk('repo/duplicate.permisssions_9999999.apk')
+        self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable/ic_launcher.png',
+                                                 '-1': 'res/drawable/ic_launcher.png'})
 
+        apk_info = fdroidserver.update.scan_apk('org.dyndns.fules.ck_20.apk')
         self.assertEqual(apk_info['icons_src'], {'240': 'res/drawable-hdpi-v4/icon_launcher.png',
                                                  '120': 'res/drawable-ldpi-v4/icon_launcher.png',
                                                  '160': 'res/drawable-mdpi-v4/icon_launcher.png',
@@ -273,6 +327,24 @@ class UpdateTest(unittest.TestCase):
         self.assertEqual(apk_info['hashType'], 'sha256')
         self.assertEqual(apk_info['targetSdkVersion'], '8')
 
+        apk_info = fdroidserver.update.scan_apk('org.bitbucket.tickytacky.mirrormirror_4.apk')
+        self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable-mdpi/mirror.png',
+                                                 '-1': 'res/drawable-mdpi/mirror.png'})
+
+        apk_info = fdroidserver.update.scan_apk('repo/info.zwanenburg.caffeinetile_4.apk')
+        self.assertEqual(apk_info['icons_src'], {'160': 'res/drawable/ic_coffee_on.xml',
+                                                 '-1': 'res/drawable/ic_coffee_on.xml'})
+
+        apk_info = fdroidserver.update.scan_apk('repo/com.politedroid_6.apk')
+        self.assertEqual(apk_info['icons_src'], {'120': 'res/drawable-ldpi-v4/icon.png',
+                                                 '160': 'res/drawable-mdpi-v4/icon.png',
+                                                 '240': 'res/drawable-hdpi-v4/icon.png',
+                                                 '320': 'res/drawable-xhdpi-v4/icon.png',
+                                                 '-1': 'res/drawable-mdpi-v4/icon.png'})
+
+        apk_info = fdroidserver.update.scan_apk('SpeedoMeterApp.main_1.apk')
+        self.assertEqual(apk_info['icons_src'], {})
+
     def test_scan_apk_no_sig(self):
         config = dict()
         fdroidserver.common.fill_config_defaults(config)
@@ -347,10 +419,6 @@ class UpdateTest(unittest.TestCase):
             self.assertEqual(apk, frompickle)
 
     def test_process_apk_signed_by_disabled_algorithms(self):
-        os.chdir(os.path.join(localmodule, 'tests'))
-        if os.path.basename(os.getcwd()) != 'tests':
-            raise Exception('This test must be run in the "tests/" subdir')
-
         config = dict()
         fdroidserver.common.fill_config_defaults(config)
         fdroidserver.update.config = config
@@ -368,12 +436,9 @@ class UpdateTest(unittest.TestCase):
         fdroidserver.update.options.allow_disabled_algorithms = False
 
         knownapks = fdroidserver.common.KnownApks()
-        apksourcedir = os.getcwd()
-        tmpdir = os.path.join(localmodule, '.testfiles')
-        if not os.path.exists(tmpdir):
-            os.makedirs(tmpdir)
-        tmptestsdir = tempfile.mkdtemp(prefix='test_process_apk_signed_by_disabled_algorithms-',
-                                       dir=tmpdir)
+
+        tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
+                                       dir=self.tmpdir)
         print('tmptestsdir', tmptestsdir)
         os.chdir(tmptestsdir)
         os.mkdir('repo')
@@ -384,7 +449,7 @@ class UpdateTest(unittest.TestCase):
 
         disabledsigs = ['org.bitbucket.tickytacky.mirrormirror_2.apk', ]
         for apkName in disabledsigs:
-            shutil.copy(os.path.join(apksourcedir, apkName),
+            shutil.copy(os.path.join(self.basedir, apkName),
                         os.path.join(tmptestsdir, 'repo'))
 
             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
@@ -397,8 +462,15 @@ class UpdateTest(unittest.TestCase):
             self.assertFalse(os.path.exists(os.path.join('archive', apkName)))
             self.assertTrue(os.path.exists(os.path.join('repo', apkName)))
 
+            javac = config['jarsigner'].replace('jarsigner', 'javac')
+            v = subprocess.check_output([javac, '-version'], stderr=subprocess.STDOUT)[6:-1].decode('utf-8')
+            if LooseVersion(v) < LooseVersion('1.8.0_132'):
+                print('SKIPPING: running tests with old Java (' + v + ')')
+                return
+
             # this test only works on systems with fully updated Java/jarsigner
             # that has MD5 listed in jdk.jar.disabledAlgorithms in java.security
+            # https://blogs.oracle.com/java-platform-group/oracle-jre-will-no-longer-trust-md5-signed-code-by-default
             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
                                                                       knownapks,
                                                                       allow_disabled_algorithms=False,
@@ -428,7 +500,7 @@ class UpdateTest(unittest.TestCase):
 
         badsigs = ['urzip-badcert.apk', 'urzip-badsig.apk', 'urzip-release-unsigned.apk', ]
         for apkName in badsigs:
-            shutil.copy(os.path.join(apksourcedir, apkName),
+            shutil.copy(os.path.join(self.basedir, apkName),
                         os.path.join(tmptestsdir, 'repo'))
 
             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
@@ -459,6 +531,141 @@ class UpdateTest(unittest.TestCase):
         self.assertIsNone(apk)
         self.assertFalse(cachechanged)
 
+    def test_translate_per_build_anti_features(self):
+        os.chdir(os.path.join(localmodule, 'tests'))
+        if os.path.basename(os.getcwd()) != 'tests':
+            raise Exception('This test must be run in the "tests/" subdir')
+
+        config = dict()
+        fdroidserver.common.fill_config_defaults(config)
+        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
+        fdroidserver.update.options.rename_apks = False
+        fdroidserver.update.options.allow_disabled_algorithms = False
+
+        apps = fdroidserver.metadata.read_metadata(xref=True)
+        knownapks = fdroidserver.common.KnownApks()
+        apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
+        fdroidserver.update.translate_per_build_anti_features(apps, apks)
+        self.assertEqual(len(apks), 14)
+        foundtest = False
+        for apk in apks:
+            if apk['packageName'] == 'com.politedroid' and apk['versionCode'] == 3:
+                antiFeatures = apk.get('antiFeatures')
+                self.assertTrue('KnownVuln' in antiFeatures)
+                self.assertEqual(3, len(antiFeatures))
+                foundtest = True
+        self.assertTrue(foundtest)
+
+    def test_create_metadata_from_template(self):
+        tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
+                                       dir=self.tmpdir)
+        print('tmptestsdir', tmptestsdir)
+        os.chdir(tmptestsdir)
+        os.mkdir('repo')
+        os.mkdir('metadata')
+        shutil.copy(os.path.join(localmodule, 'tests', 'urzip.apk'), 'repo')
+
+        config = dict()
+        fdroidserver.common.fill_config_defaults(config)
+        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 = False
+        fdroidserver.update.options.rename_apks = False
+        fdroidserver.update.options.allow_disabled_algorithms = False
+
+        knownapks = fdroidserver.common.KnownApks()
+        apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
+        self.assertEqual(1, len(apks))
+        apk = apks[0]
+
+        testfile = 'metadata/info.guardianproject.urzip.yml'
+        # create empty 0 byte .yml file, run read_metadata, it should work
+        open(testfile, 'a').close()
+        apps = fdroidserver.metadata.read_metadata(xref=True)
+        self.assertEqual(1, len(apps))
+        os.remove(testfile)
+
+        # test using internal template
+        apps = fdroidserver.metadata.read_metadata(xref=True)
+        self.assertEqual(0, len(apps))
+        fdroidserver.update.create_metadata_from_template(apk)
+        self.assertTrue(os.path.exists(testfile))
+        apps = fdroidserver.metadata.read_metadata(xref=True)
+        self.assertEqual(1, len(apps))
+        for app in apps.values():
+            self.assertEqual('urzip', app['Name'])
+            self.assertEqual(1, len(app['Categories']))
+            break
+
+        # test using external template.yml
+        os.remove(testfile)
+        self.assertFalse(os.path.exists(testfile))
+        shutil.copy(os.path.join(localmodule, 'examples', 'template.yml'), tmptestsdir)
+        fdroidserver.update.create_metadata_from_template(apk)
+        self.assertTrue(os.path.exists(testfile))
+        apps = fdroidserver.metadata.read_metadata(xref=True)
+        self.assertEqual(1, len(apps))
+        for app in apps.values():
+            self.assertEqual('urzip', app['Name'])
+            self.assertEqual(1, len(app['Categories']))
+            self.assertEqual('Internet', app['Categories'][0])
+            break
+        with open(testfile) as fp:
+            data = yaml.load(fp)
+        self.assertEqual('urzip', data['Name'])
+        self.assertEqual('urzip', data['Summary'])
+
+    def test_has_known_vulnerability(self):
+        good = [
+            'org.bitbucket.tickytacky.mirrormirror_1.apk',
+            'org.bitbucket.tickytacky.mirrormirror_2.apk',
+            'org.bitbucket.tickytacky.mirrormirror_3.apk',
+            'org.bitbucket.tickytacky.mirrormirror_4.apk',
+            'org.dyndns.fules.ck_20.apk',
+            'urzip.apk',
+            'urzip-badcert.apk',
+            'urzip-badsig.apk',
+            'urzip-release.apk',
+            'urzip-release-unsigned.apk',
+            'repo/com.politedroid_3.apk',
+            'repo/com.politedroid_4.apk',
+            'repo/com.politedroid_5.apk',
+            'repo/com.politedroid_6.apk',
+            'repo/obb.main.oldversion_1444412523.apk',
+            'repo/obb.mainpatch.current_1619_another-release-key.apk',
+            'repo/obb.mainpatch.current_1619.apk',
+            'repo/obb.main.twoversions_1101613.apk',
+            'repo/obb.main.twoversions_1101615.apk',
+            'repo/obb.main.twoversions_1101617.apk',
+            'repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk',
+        ]
+        for f in good:
+            self.assertFalse(fdroidserver.update.has_known_vulnerability(f))
+        with self.assertRaises(fdroidserver.exception.FDroidException):
+            fdroidserver.update.has_known_vulnerability('janus.apk')
+
+    def test_get_apk_icon_when_src_is_none(self):
+        config = dict()
+        fdroidserver.common.fill_config_defaults(config)
+        fdroidserver.common.config = config
+        fdroidserver.update.config = config
+
+        # pylint: disable=protected-access
+        icons_src = fdroidserver.update._get_apk_icons_src('urzip-release.apk', None)
+        assert icons_src == {}
+
 
 if __name__ == "__main__":
     parser = optparse.OptionParser()
@@ -468,4 +675,4 @@ if __name__ == "__main__":
 
     newSuite = unittest.TestSuite()
     newSuite.addTest(unittest.makeSuite(UpdateTest))
-    unittest.main()
+    unittest.main(failfast=False)