chiark / gitweb /
implement common.get_apk_id() using androguard
[fdroidserver.git] / tests / common.TestCase
1 #!/usr/bin/env python3
2
3 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
4
5 import inspect
6 import logging
7 import optparse
8 import os
9 import re
10 import shutil
11 import sys
12 import tempfile
13 import unittest
14 import textwrap
15 import yaml
16 from zipfile import ZipFile
17
18
19 localmodule = os.path.realpath(
20     os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
21 print('localmodule: ' + localmodule)
22 if localmodule not in sys.path:
23     sys.path.insert(0, localmodule)
24
25 import fdroidserver.index
26 import fdroidserver.signindex
27 import fdroidserver.common
28 import fdroidserver.metadata
29 from testcommon import TmpCwd
30 from fdroidserver.exception import FDroidException
31
32
33 class CommonTest(unittest.TestCase):
34     '''fdroidserver/common.py'''
35
36     def setUp(self):
37         logging.basicConfig(level=logging.DEBUG)
38         self.basedir = os.path.join(localmodule, 'tests')
39         self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
40         if not os.path.exists(self.tmpdir):
41             os.makedirs(self.tmpdir)
42         os.chdir(self.basedir)
43
44     def test_assert_config_keystore(self):
45         with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
46             with self.assertRaises(FDroidException):
47                 fdroidserver.common.assert_config_keystore({})
48
49         with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
50             c = {'repo_keyalias': 'localhost',
51                  'keystore': 'keystore.jks',
52                  'keystorepass': '12345',
53                  'keypass': '12345'}
54             with open('keystore.jks', 'w'):
55                 pass
56             fdroidserver.common.assert_config_keystore(c)
57
58     def _set_build_tools(self):
59         build_tools = os.path.join(fdroidserver.common.config['sdk_path'], 'build-tools')
60         if os.path.exists(build_tools):
61             fdroidserver.common.config['build_tools'] = ''
62             for f in sorted(os.listdir(build_tools), reverse=True):
63                 versioned = os.path.join(build_tools, f)
64                 if os.path.isdir(versioned) \
65                         and os.path.isfile(os.path.join(versioned, 'aapt')):
66                     fdroidserver.common.config['build_tools'] = versioned
67                     break
68             return True
69         else:
70             print('no build-tools found: ' + build_tools)
71             return False
72
73     def _find_all(self):
74         tools = ['aapt', 'adb', 'zipalign']
75         if os.path.exists(os.path.join(os.getenv('ANDROID_HOME'), 'tools', 'android')):
76             tools.append('android')
77         for cmd in tools:
78             path = fdroidserver.common.find_sdk_tools_cmd(cmd)
79             if path is not None:
80                 self.assertTrue(os.path.exists(path))
81                 self.assertTrue(os.path.isfile(path))
82
83     def test_find_sdk_tools_cmd(self):
84         fdroidserver.common.config = dict()
85         # TODO add this once everything works without sdk_path set in config
86         # self._find_all()
87         sdk_path = os.getenv('ANDROID_HOME')
88         if os.path.exists(sdk_path):
89             fdroidserver.common.config['sdk_path'] = sdk_path
90             build_tools = os.path.join(sdk_path, 'build-tools')
91             if self._set_build_tools() or os.path.exists('/usr/bin/aapt'):
92                 self._find_all()
93             else:
94                 print('no build-tools found: ' + build_tools)
95
96     def test_find_java_root_path(self):
97         testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
98         os.chdir(testdir)
99
100         all_pathlists = [
101             ([  # Debian
102                 '/usr/lib/jvm/java-1.5.0-gcj-5-amd64',
103                 '/usr/lib/jvm/java-8-openjdk-amd64',
104                 '/usr/lib/jvm/java-1.8.0-openjdk-amd64',
105             ], '/usr/lib/jvm/java-8-openjdk-amd64'),
106             ([  # OSX
107                 '/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk',
108                 '/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk',
109                 '/System/Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk',
110             ], '/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk'),
111         ]
112
113         for pathlist, choice in all_pathlists:
114             # strip leading / to make relative paths to test without root
115             pathlist = [p[1:] for p in pathlist]
116
117             # create test file used in common._add_java_paths_to_config()
118             for p in pathlist:
119                 if p.startswith('/System') or p.startswith('/Library'):
120                     basedir = os.path.join(p, 'Contents', 'Home', 'bin')
121                 else:
122                     basedir = os.path.join(p, 'bin')
123                 os.makedirs(basedir)
124                 open(os.path.join(basedir, 'javac'), 'w').close()
125
126             config = dict()
127             config['java_paths'] = dict()
128             fdroidserver.common._add_java_paths_to_config(pathlist, config)
129             self.assertEqual(config['java_paths']['8'], choice[1:])
130
131     def testIsApkDebuggable(self):
132         config = dict()
133         fdroidserver.common.fill_config_defaults(config)
134         fdroidserver.common.config = config
135         self._set_build_tools()
136         config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt')
137         # these are set debuggable
138         testfiles = []
139         testfiles.append(os.path.join(self.basedir, 'urzip.apk'))
140         testfiles.append(os.path.join(self.basedir, 'urzip-badsig.apk'))
141         testfiles.append(os.path.join(self.basedir, 'urzip-badcert.apk'))
142         for apkfile in testfiles:
143             debuggable = fdroidserver.common.is_apk_and_debuggable(apkfile)
144             self.assertTrue(debuggable,
145                             "debuggable APK state was not properly parsed!")
146         # these are set NOT debuggable
147         testfiles = []
148         testfiles.append(os.path.join(self.basedir, 'urzip-release.apk'))
149         testfiles.append(os.path.join(self.basedir, 'urzip-release-unsigned.apk'))
150         for apkfile in testfiles:
151             debuggable = fdroidserver.common.is_apk_and_debuggable(apkfile)
152             self.assertFalse(debuggable,
153                              "debuggable APK state was not properly parsed!")
154
155     def testPackageNameValidity(self):
156         for name in ["org.fdroid.fdroid",
157                      "org.f_droid.fdr0ID"]:
158             self.assertTrue(fdroidserver.common.is_valid_package_name(name),
159                             "{0} should be a valid package name".format(name))
160         for name in ["0rg.fdroid.fdroid",
161                      ".f_droid.fdr0ID",
162                      "org.fdroid/fdroid",
163                      "/org.fdroid.fdroid"]:
164             self.assertFalse(fdroidserver.common.is_valid_package_name(name),
165                              "{0} should not be a valid package name".format(name))
166
167     def test_prepare_sources(self):
168         testint = 99999999
169         teststr = 'FAKE_STR_FOR_TESTING'
170
171         testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
172         shutil.copytree(os.path.join(self.basedir, 'source-files'),
173                         os.path.join(testdir, 'source-files'))
174
175         fdroidclient_testdir = os.path.join(testdir, 'source-files', 'fdroid', 'fdroidclient')
176
177         config = dict()
178         config['sdk_path'] = os.getenv('ANDROID_HOME')
179         config['ndk_paths'] = {'r10d': os.getenv('ANDROID_NDK_HOME')}
180         config['build_tools'] = 'FAKE_BUILD_TOOLS_VERSION'
181         fdroidserver.common.config = config
182         app = fdroidserver.metadata.App()
183         app.id = 'org.fdroid.froid'
184         build = fdroidserver.metadata.Build()
185         build.commit = 'master'
186         build.forceversion = True
187         build.forcevercode = True
188         build.gradle = ['yes']
189         build.target = 'android-' + str(testint)
190         build.versionName = teststr
191         build.versionCode = testint
192
193         class FakeVcs():
194             # no need to change to the correct commit here
195             def gotorevision(self, rev, refresh=True):
196                 pass
197
198             # no srclib info needed, but it could be added...
199             def getsrclib(self):
200                 return None
201
202         fdroidserver.common.prepare_source(FakeVcs(), app, build,
203                                            fdroidclient_testdir, fdroidclient_testdir, fdroidclient_testdir)
204
205         with open(os.path.join(fdroidclient_testdir, 'build.gradle'), 'r') as f:
206             filedata = f.read()
207         self.assertIsNotNone(re.search("\s+compileSdkVersion %s\s+" % testint, filedata))
208
209         with open(os.path.join(fdroidclient_testdir, 'AndroidManifest.xml')) as f:
210             filedata = f.read()
211         self.assertIsNone(re.search('android:debuggable', filedata))
212         self.assertIsNotNone(re.search('android:versionName="%s"' % build.versionName, filedata))
213         self.assertIsNotNone(re.search('android:versionCode="%s"' % build.versionCode, filedata))
214
215     def test_prepare_sources_refresh(self):
216         packageName = 'org.fdroid.ci.test.app'
217         testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
218         print('testdir', testdir)
219         os.chdir(testdir)
220         os.mkdir('build')
221         os.mkdir('metadata')
222
223         # use a local copy if available to avoid hitting the network
224         tmprepo = os.path.join(self.basedir, 'tmp', 'importer')
225         if os.path.exists(tmprepo):
226             git_url = tmprepo
227         else:
228             git_url = 'https://gitlab.com/fdroid/ci-test-app.git'
229
230         metadata = dict()
231         metadata['Description'] = 'This is just a test app'
232         metadata['RepoType'] = 'git'
233         metadata['Repo'] = git_url
234         with open(os.path.join('metadata', packageName + '.yml'), 'w') as fp:
235             yaml.dump(metadata, fp)
236
237         gitrepo = os.path.join(testdir, 'build', packageName)
238         vcs0 = fdroidserver.common.getvcs('git', git_url, gitrepo)
239         vcs0.gotorevision('0.3', refresh=True)
240         vcs1 = fdroidserver.common.getvcs('git', git_url, gitrepo)
241         vcs1.gotorevision('0.3', refresh=False)
242
243     def test_fdroid_popen_stderr_redirect(self):
244         config = dict()
245         fdroidserver.common.fill_config_defaults(config)
246         fdroidserver.common.config = config
247
248         commands = ['sh', '-c', 'echo stdout message && echo stderr message 1>&2']
249
250         p = fdroidserver.common.FDroidPopen(commands)
251         self.assertEqual(p.output, 'stdout message\nstderr message\n')
252
253         p = fdroidserver.common.FDroidPopen(commands, stderr_to_stdout=False)
254         self.assertEqual(p.output, 'stdout message\n')
255
256     def test_signjar(self):
257         fdroidserver.common.config = None
258         config = fdroidserver.common.read_config(fdroidserver.common.options)
259         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
260         fdroidserver.common.config = config
261         fdroidserver.signindex.config = config
262
263         sourcedir = os.path.join(self.basedir, 'signindex')
264         testsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
265         for f in ('testy.jar', 'guardianproject.jar',):
266             sourcefile = os.path.join(sourcedir, f)
267             testfile = os.path.join(testsdir, f)
268             shutil.copy(sourcefile, testsdir)
269             fdroidserver.signindex.sign_jar(testfile)
270             # these should be resigned, and therefore different
271             self.assertNotEqual(open(sourcefile, 'rb').read(), open(testfile, 'rb').read())
272
273     def test_verify_apk_signature(self):
274         fdroidserver.common.config = None
275         config = fdroidserver.common.read_config(fdroidserver.common.options)
276         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
277         fdroidserver.common.config = config
278
279         self.assertTrue(fdroidserver.common.verify_apk_signature('bad-unicode-πÇÇ现代通用字-български-عربي1.apk'))
280         self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_1.apk'))
281         self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_2.apk'))
282         self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_3.apk'))
283         self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_4.apk'))
284         self.assertTrue(fdroidserver.common.verify_apk_signature('org.dyndns.fules.ck_20.apk'))
285         self.assertTrue(fdroidserver.common.verify_apk_signature('urzip.apk'))
286         self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-badcert.apk'))
287         self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-badsig.apk'))
288         self.assertTrue(fdroidserver.common.verify_apk_signature('urzip-release.apk'))
289         self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-release-unsigned.apk'))
290
291     def test_verify_old_apk_signature(self):
292         fdroidserver.common.config = None
293         config = fdroidserver.common.read_config(fdroidserver.common.options)
294         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
295         fdroidserver.common.config = config
296
297         self.assertTrue(fdroidserver.common.verify_old_apk_signature('bad-unicode-πÇÇ现代通用字-български-عربي1.apk'))
298         self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_1.apk'))
299         self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_2.apk'))
300         self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_3.apk'))
301         self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_4.apk'))
302         self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.dyndns.fules.ck_20.apk'))
303         self.assertTrue(fdroidserver.common.verify_old_apk_signature('urzip.apk'))
304         self.assertFalse(fdroidserver.common.verify_old_apk_signature('urzip-badcert.apk'))
305         self.assertFalse(fdroidserver.common.verify_old_apk_signature('urzip-badsig.apk'))
306         self.assertTrue(fdroidserver.common.verify_old_apk_signature('urzip-release.apk'))
307         self.assertFalse(fdroidserver.common.verify_old_apk_signature('urzip-release-unsigned.apk'))
308
309     def test_verify_jar_signature_succeeds(self):
310         fdroidserver.common.config = None
311         config = fdroidserver.common.read_config(fdroidserver.common.options)
312         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
313         fdroidserver.common.config = config
314         source_dir = os.path.join(self.basedir, 'signindex')
315         for f in ('testy.jar', 'guardianproject.jar'):
316             testfile = os.path.join(source_dir, f)
317             fdroidserver.common.verify_jar_signature(testfile)
318
319     def test_verify_jar_signature_fails(self):
320         fdroidserver.common.config = None
321         config = fdroidserver.common.read_config(fdroidserver.common.options)
322         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
323         fdroidserver.common.config = config
324         source_dir = os.path.join(self.basedir, 'signindex')
325         testfile = os.path.join(source_dir, 'unsigned.jar')
326         with self.assertRaises(fdroidserver.index.VerificationException):
327             fdroidserver.common.verify_jar_signature(testfile)
328
329     def test_verify_apks(self):
330         fdroidserver.common.config = None
331         config = fdroidserver.common.read_config(fdroidserver.common.options)
332         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
333         fdroidserver.common.config = config
334
335         sourceapk = os.path.join(self.basedir, 'urzip.apk')
336
337         testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
338         print('testdir', testdir)
339
340         copyapk = os.path.join(testdir, 'urzip-copy.apk')
341         shutil.copy(sourceapk, copyapk)
342         self.assertTrue(fdroidserver.common.verify_apk_signature(copyapk))
343         self.assertIsNone(fdroidserver.common.verify_apks(sourceapk, copyapk, self.tmpdir))
344
345         unsignedapk = os.path.join(testdir, 'urzip-unsigned.apk')
346         with ZipFile(sourceapk, 'r') as apk:
347             with ZipFile(unsignedapk, 'w') as testapk:
348                 for info in apk.infolist():
349                     if not info.filename.startswith('META-INF/'):
350                         testapk.writestr(info, apk.read(info.filename))
351         self.assertIsNone(fdroidserver.common.verify_apks(sourceapk, unsignedapk, self.tmpdir))
352
353         twosigapk = os.path.join(testdir, 'urzip-twosig.apk')
354         otherapk = ZipFile(os.path.join(self.basedir, 'urzip-release.apk'), 'r')
355         with ZipFile(sourceapk, 'r') as apk:
356             with ZipFile(twosigapk, 'w') as testapk:
357                 for info in apk.infolist():
358                     testapk.writestr(info, apk.read(info.filename))
359                     if info.filename.startswith('META-INF/'):
360                         testapk.writestr(info, otherapk.read(info.filename))
361         otherapk.close()
362         self.assertFalse(fdroidserver.common.verify_apk_signature(twosigapk))
363         self.assertIsNone(fdroidserver.common.verify_apks(sourceapk, twosigapk, self.tmpdir))
364
365     def test_write_to_config(self):
366         with tempfile.TemporaryDirectory() as tmpPath:
367             cfgPath = os.path.join(tmpPath, 'config.py')
368             with open(cfgPath, 'w', encoding='utf-8') as f:
369                 f.write(textwrap.dedent("""\
370                     # abc
371                     # test = 'example value'
372                     default_me= '%%%'
373
374                     # comment
375                     do_not_touch = "good value"
376                     default_me="!!!"
377
378                     key="123"    # inline"""))
379
380             cfg = {'key': '111', 'default_me_orig': 'orig'}
381             fdroidserver.common.write_to_config(cfg, 'key', config_file=cfgPath)
382             fdroidserver.common.write_to_config(cfg, 'default_me', config_file=cfgPath)
383             fdroidserver.common.write_to_config(cfg, 'test', value='test value', config_file=cfgPath)
384             fdroidserver.common.write_to_config(cfg, 'new_key', value='new', config_file=cfgPath)
385
386             with open(cfgPath, 'r', encoding='utf-8') as f:
387                 self.assertEqual(f.read(), textwrap.dedent("""\
388                     # abc
389                     test = 'test value'
390                     default_me = 'orig'
391
392                     # comment
393                     do_not_touch = "good value"
394
395                     key = "111"    # inline
396
397                     new_key = "new"
398                     """))
399
400     def test_write_to_config_when_empty(self):
401         with tempfile.TemporaryDirectory() as tmpPath:
402             cfgPath = os.path.join(tmpPath, 'config.py')
403             with open(cfgPath, 'w') as f:
404                 pass
405             fdroidserver.common.write_to_config({}, 'key', 'val', cfgPath)
406             with open(cfgPath, 'r', encoding='utf-8') as f:
407                 self.assertEqual(f.read(), textwrap.dedent("""\
408
409                 key = "val"
410                 """))
411
412     def test_apk_name_regex(self):
413         good = [
414             'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_-123456.apk',
415             'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_123456_abcdef0.apk',
416             'urzip_-123456.apk',
417             'a0_0.apk',
418             'Z0_0.apk',
419             'a0_0_abcdef0.apk',
420             'a_a_a_a_0_abcdef0.apk',
421             'a_____0.apk',
422             'a_____123456_abcdef0.apk',
423             'org.fdroid.fdroid_123456.apk',
424             # valid, but "_99999" is part of packageName rather than versionCode
425             'org.fdroid.fdroid_99999_123456.apk',
426             # should be valid, but I can't figure out the regex since \w includes digits
427             # 'πÇÇπÇÇ现代汉语通用字българскиعربي1234ö_0_123bafd.apk',
428         ]
429         for name in good:
430             m = fdroidserver.common.APK_NAME_REGEX.match(name)
431             self.assertIsNotNone(m)
432             self.assertIn(m.group(2), ('-123456', '0', '123456'))
433             self.assertIn(m.group(3), ('abcdef0', None))
434
435         bad = [
436             'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_123456_abcdefg.apk',
437             'urzip-_-198274.apk',
438             'urzip-_0_123bafd.apk',
439             'no spaces allowed_123.apk',
440             '0_0.apk',
441             '0_0_abcdef0.apk',
442         ]
443         for name in bad:
444             self.assertIsNone(fdroidserver.common.APK_NAME_REGEX.match(name))
445
446     def test_standard_file_name_regex(self):
447         good = [
448             'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_-123456.mp3',
449             'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_123456.mov',
450             'Document_-123456.pdf',
451             'WTF_0.MOV',
452             'Z0_0.ebk',
453             'a_a_a_a_0.txt',
454             'org.fdroid.fdroid.privileged.ota_123456.zip',
455             'πÇÇπÇÇ现代汉语通用字българскиعربي1234ö_0.jpeg',
456             'a_____0.PNG',
457             # valid, but "_99999" is part of packageName rather than versionCode
458             'a_____99999_123456.zip',
459             'org.fdroid.fdroid_99999_123456.zip',
460         ]
461         for name in good:
462             m = fdroidserver.common.STANDARD_FILE_NAME_REGEX.match(name)
463             self.assertIsNotNone(m)
464             self.assertIn(m.group(2), ('-123456', '0', '123456'))
465
466         bad = [
467             'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_abcdefg.JPEG',
468             'urzip-_-198274.zip',
469             'urzip-_123bafd.pdf',
470             'no spaces allowed_123.foobar',
471             'a_____0.',
472         ]
473         for name in bad:
474             self.assertIsNone(fdroidserver.common.STANDARD_FILE_NAME_REGEX.match(name))
475
476     def test_apk_signer_fingerprint(self):
477
478         # fingerprints fetched with: keytool -printcert -file ____.RSA
479         testapks = (('repo/obb.main.oldversion_1444412523.apk',
480                      '818e469465f96b704e27be2fee4c63ab9f83ddf30e7a34c7371a4728d83b0bc1'),
481                     ('repo/obb.main.twoversions_1101613.apk',
482                      '32a23624c201b949f085996ba5ed53d40f703aca4989476949cae891022e0ed6'),
483                     ('repo/obb.main.twoversions_1101617.apk',
484                      '32a23624c201b949f085996ba5ed53d40f703aca4989476949cae891022e0ed6'))
485
486         for apkfile, keytoolcertfingerprint in testapks:
487             self.assertEqual(keytoolcertfingerprint,
488                              fdroidserver.common.apk_signer_fingerprint(apkfile))
489
490     def test_apk_signer_fingerprint_short(self):
491
492         # fingerprints fetched with: keytool -printcert -file ____.RSA
493         testapks = (('repo/obb.main.oldversion_1444412523.apk', '818e469'),
494                     ('repo/obb.main.twoversions_1101613.apk', '32a2362'),
495                     ('repo/obb.main.twoversions_1101617.apk', '32a2362'))
496
497         for apkfile, keytoolcertfingerprint in testapks:
498             self.assertEqual(keytoolcertfingerprint,
499                              fdroidserver.common.apk_signer_fingerprint_short(apkfile))
500
501     def test_sign_apk(self):
502         fdroidserver.common.config = None
503         config = fdroidserver.common.read_config(fdroidserver.common.options)
504         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
505         config['keyalias'] = 'sova'
506         config['keystorepass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
507         config['keypass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
508         config['keystore'] = os.path.join(self.basedir, 'keystore.jks')
509         fdroidserver.common.config = config
510         fdroidserver.signindex.config = config
511
512         testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
513         unsigned = os.path.join(testdir, 'urzip-release-unsigned.apk')
514         signed = os.path.join(testdir, 'urzip-release.apk')
515
516         self.assertFalse(fdroidserver.common.verify_apk_signature(unsigned))
517
518         shutil.copy(os.path.join(self.basedir, 'urzip-release-unsigned.apk'), testdir)
519         fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
520         self.assertTrue(os.path.isfile(signed))
521         self.assertFalse(os.path.isfile(unsigned))
522         self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
523
524         # now sign an APK with minSdkVersion >= 18
525         unsigned = os.path.join(testdir, 'duplicate.permisssions_9999999-unsigned.apk')
526         signed = os.path.join(testdir, 'duplicate.permisssions_9999999.apk')
527         shutil.copy(os.path.join(self.basedir, 'repo', 'duplicate.permisssions_9999999.apk'),
528                     os.path.join(unsigned))
529         fdroidserver.common.apk_strip_signatures(unsigned, strip_manifest=True)
530         fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
531         self.assertTrue(os.path.isfile(signed))
532         self.assertFalse(os.path.isfile(unsigned))
533         self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
534         self.assertEqual(18, fdroidserver.common.get_minSdkVersion_aapt(signed))
535
536     def test_get_api_id(self):
537
538         config = dict()
539         fdroidserver.common.fill_config_defaults(config)
540         fdroidserver.common.config = config
541         self._set_build_tools()
542         config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt')
543
544         testcases = [
545             ('repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', '1101613', '0.1'),
546             ('org.bitbucket.tickytacky.mirrormirror_1.apk', 'org.bitbucket.tickytacky.mirrormirror', '1', '1.0'),
547             ('org.bitbucket.tickytacky.mirrormirror_2.apk', 'org.bitbucket.tickytacky.mirrormirror', '2', '1.0.1'),
548             ('org.bitbucket.tickytacky.mirrormirror_3.apk', 'org.bitbucket.tickytacky.mirrormirror', '3', '1.0.2'),
549             ('org.bitbucket.tickytacky.mirrormirror_4.apk', 'org.bitbucket.tickytacky.mirrormirror', '4', '1.0.3'),
550             ('org.dyndns.fules.ck_20.apk', 'org.dyndns.fules.ck', '20', 'v1.6pre2'),
551             ('urzip.apk', 'info.guardianproject.urzip', '100', '0.1'),
552             ('urzip-badcert.apk', 'info.guardianproject.urzip', '100', '0.1'),
553             ('urzip-badsig.apk', 'info.guardianproject.urzip', '100', '0.1'),
554             ('urzip-release.apk', 'info.guardianproject.urzip', '100', '0.1'),
555             ('urzip-release-unsigned.apk', 'info.guardianproject.urzip', '100', '0.1'),
556             ('repo/com.politedroid_3.apk', 'com.politedroid', '3', '1.2'),
557             ('repo/com.politedroid_4.apk', 'com.politedroid', '4', '1.3'),
558             ('repo/com.politedroid_5.apk', 'com.politedroid', '5', '1.4'),
559             ('repo/com.politedroid_6.apk', 'com.politedroid', '6', '1.5'),
560             ('repo/duplicate.permisssions_9999999.apk', 'duplicate.permisssions', '9999999', ''),
561             ('repo/info.zwanenburg.caffeinetile_4.apk', 'info.zwanenburg.caffeinetile', '4', '1.3'),
562             ('repo/obb.main.oldversion_1444412523.apk', 'obb.main.oldversion', '1444412523', '0.1'),
563             ('repo/obb.mainpatch.current_1619_another-release-key.apk', 'obb.mainpatch.current', '1619', '0.1'),
564             ('repo/obb.mainpatch.current_1619.apk', 'obb.mainpatch.current', '1619', '0.1'),
565             ('repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', '1101613', '0.1'),
566             ('repo/obb.main.twoversions_1101615.apk', 'obb.main.twoversions', '1101615', '0.1'),
567             ('repo/obb.main.twoversions_1101617.apk', 'obb.main.twoversions', '1101617', '0.1'),
568             ('repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk', 'info.guardianproject.urzip', '100', '0.1'),
569         ]
570         for apkfilename, appid, versionCode, versionName in testcases:
571             print('\n\nAPKFILENAME\n', apkfilename)
572             if 'aapt' in config:
573                 a, vc, vn = fdroidserver.common.get_apk_id_aapt(apkfilename)
574                 self.assertEqual(appid, a)
575                 self.assertEqual(versionCode, vc)
576                 self.assertEqual(versionName, vn)
577             if fdroidserver.common.use_androguard():
578                 a, vc, vn = fdroidserver.common.get_apk_id_androguard(apkfilename)
579                 self.assertEqual(appid, a)
580                 self.assertEqual(versionCode, vc)
581                 self.assertEqual(versionName, vn)
582
583         with self.assertRaises(FDroidException):
584             fdroidserver.common.get_apk_id('nope')
585
586     def test_get_minSdkVersion_aapt(self):
587
588         config = dict()
589         fdroidserver.common.fill_config_defaults(config)
590         fdroidserver.common.config = config
591         self._set_build_tools()
592         config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt')
593
594         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('bad-unicode-πÇÇ现代通用字-български-عربي1.apk')
595         self.assertEqual(4, minSdkVersion)
596         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_1.apk')
597         self.assertEqual(14, minSdkVersion)
598         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_2.apk')
599         self.assertEqual(14, minSdkVersion)
600         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_3.apk')
601         self.assertEqual(14, minSdkVersion)
602         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.bitbucket.tickytacky.mirrormirror_4.apk')
603         self.assertEqual(14, minSdkVersion)
604         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('org.dyndns.fules.ck_20.apk')
605         self.assertEqual(7, minSdkVersion)
606         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip.apk')
607         self.assertEqual(4, minSdkVersion)
608         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-badcert.apk')
609         self.assertEqual(4, minSdkVersion)
610         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-badsig.apk')
611         self.assertEqual(4, minSdkVersion)
612         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-release.apk')
613         self.assertEqual(4, minSdkVersion)
614         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('urzip-release-unsigned.apk')
615         self.assertEqual(4, minSdkVersion)
616         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_3.apk')
617         self.assertEqual(3, minSdkVersion)
618         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_4.apk')
619         self.assertEqual(3, minSdkVersion)
620         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_5.apk')
621         self.assertEqual(3, minSdkVersion)
622         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/com.politedroid_6.apk')
623         self.assertEqual(14, minSdkVersion)
624         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.oldversion_1444412523.apk')
625         self.assertEqual(4, minSdkVersion)
626         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.mainpatch.current_1619_another-release-key.apk')
627         self.assertEqual(4, minSdkVersion)
628         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.mainpatch.current_1619.apk')
629         self.assertEqual(4, minSdkVersion)
630         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.twoversions_1101613.apk')
631         self.assertEqual(4, minSdkVersion)
632         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.twoversions_1101615.apk')
633         self.assertEqual(4, minSdkVersion)
634         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/obb.main.twoversions_1101617.apk')
635         self.assertEqual(4, minSdkVersion)
636         minSdkVersion = fdroidserver.common.get_minSdkVersion_aapt('repo/urzip-; Рахма́нинов, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢尔盖·.apk')
637
638         with self.assertRaises(FDroidException):
639             fdroidserver.common.get_minSdkVersion_aapt('nope')
640
641     def test_apk_release_name(self):
642         appid, vercode, sigfp = fdroidserver.common.apk_parse_release_filename('com.serwylo.lexica_905.apk')
643         self.assertEqual(appid, 'com.serwylo.lexica')
644         self.assertEqual(vercode, '905')
645         self.assertEqual(sigfp, None)
646
647         appid, vercode, sigfp = fdroidserver.common.apk_parse_release_filename('com.serwylo.lexica_905_c82e0f6.apk')
648         self.assertEqual(appid, 'com.serwylo.lexica')
649         self.assertEqual(vercode, '905')
650         self.assertEqual(sigfp, 'c82e0f6')
651
652         appid, vercode, sigfp = fdroidserver.common.apk_parse_release_filename('beverly_hills-90210.apk')
653         self.assertEqual(appid, None)
654         self.assertEqual(vercode, None)
655         self.assertEqual(sigfp, None)
656
657     def test_metadata_find_developer_signature(self):
658         sig = fdroidserver.common.metadata_find_developer_signature('org.smssecure.smssecure')
659         self.assertEqual('b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868', sig)
660
661     def test_parse_androidmanifests(self):
662         source_files_dir = os.path.join(os.path.dirname(__file__), 'source-files')
663         app = fdroidserver.metadata.App()
664         app.id = 'org.fdroid.fdroid'
665         paths = [
666             os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'AndroidManifest.xml'),
667             os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'build.gradle'),
668         ]
669         for path in paths:
670             self.assertTrue(os.path.isfile(path))
671         self.assertEqual(('0.94-test', '940', 'org.fdroid.fdroid'),
672                          fdroidserver.common.parse_androidmanifests(paths, app))
673
674     def test_parse_androidmanifests_with_flavor(self):
675         source_files_dir = os.path.join(os.path.dirname(__file__), 'source-files')
676
677         app = fdroidserver.metadata.App()
678         build = fdroidserver.metadata.Build()
679         build.gradle = ['devVersion']
680         app.builds = [build]
681         app.id = 'org.fdroid.fdroid.dev'
682         paths = [
683             os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'AndroidManifest.xml'),
684             os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'build.gradle'),
685         ]
686         for path in paths:
687             self.assertTrue(os.path.isfile(path))
688         self.assertEqual(('0.95-dev', '949', 'org.fdroid.fdroid.dev'),
689                          fdroidserver.common.parse_androidmanifests(paths, app))
690
691         app = fdroidserver.metadata.App()
692         build = fdroidserver.metadata.Build()
693         build.gradle = ['free']
694         app.builds = [build]
695         app.id = 'eu.siacs.conversations'
696         paths = [
697             os.path.join(source_files_dir, 'eu.siacs.conversations', 'build.gradle'),
698         ]
699         for path in paths:
700             self.assertTrue(os.path.isfile(path))
701         self.assertEqual(('1.23.1', '245', 'eu.siacs.conversations'),
702                          fdroidserver.common.parse_androidmanifests(paths, app))
703
704         app = fdroidserver.metadata.App()
705         build = fdroidserver.metadata.Build()
706         build.gradle = ['generic']
707         app.builds = [build]
708         app.id = 'com.nextcloud.client'
709         paths = [
710             os.path.join(source_files_dir, 'com.nextcloud.client', 'build.gradle'),
711         ]
712         for path in paths:
713             self.assertTrue(os.path.isfile(path))
714         self.assertEqual(('2.0.0', '20000099', 'com.nextcloud.client'),
715                          fdroidserver.common.parse_androidmanifests(paths, app))
716
717         app = fdroidserver.metadata.App()
718         build = fdroidserver.metadata.Build()
719         build.gradle = ['versionDev']
720         app.builds = [build]
721         app.id = 'com.nextcloud.android.beta'
722         paths = [
723             os.path.join(source_files_dir, 'com.nextcloud.client', 'build.gradle'),
724         ]
725         for path in paths:
726             self.assertTrue(os.path.isfile(path))
727         self.assertEqual(('20171223', '20171223', 'com.nextcloud.android.beta'),
728                          fdroidserver.common.parse_androidmanifests(paths, app))
729
730         app = fdroidserver.metadata.App()
731         build = fdroidserver.metadata.Build()
732         build.gradle = ['standard']
733         app.builds = [build]
734         app.id = 'at.bitfire.davdroid'
735         paths = [
736             os.path.join(source_files_dir, 'at.bitfire.davdroid', 'build.gradle'),
737         ]
738         for path in paths:
739             self.assertTrue(os.path.isfile(path))
740         self.assertEqual(('1.9.8.1-ose', '197', 'at.bitfire.davdroid'),
741                          fdroidserver.common.parse_androidmanifests(paths, app))
742
743
744 if __name__ == "__main__":
745     parser = optparse.OptionParser()
746     parser.add_option("-v", "--verbose", action="store_true", default=False,
747                       help="Spew out even more information than normal")
748     (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
749
750     newSuite = unittest.TestSuite()
751     newSuite.addTest(unittest.makeSuite(CommonTest))
752     unittest.main(failfast=False)