mirror of
https://gitlab.com/fdroid/fdroidserver.git
synced 2024-11-18 20:50:10 +01:00
verify: add --output-json option to generate status reports
This has to do custom dict/encoder/decoder tricks in order to make sure that each run of `fdroid verify` on the same files doesn't add duplicate entries.
This commit is contained in:
parent
e85256e248
commit
13016c5d63
@ -19,19 +19,108 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
import requests
|
import requests
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
import logging
|
from collections import OrderedDict
|
||||||
|
|
||||||
from . import _
|
from . import _
|
||||||
from . import common
|
from . import common
|
||||||
from . import net
|
from . import net
|
||||||
|
from . import update
|
||||||
from .exception import FDroidException
|
from .exception import FDroidException
|
||||||
|
|
||||||
options = None
|
options = None
|
||||||
config = None
|
config = None
|
||||||
|
|
||||||
|
|
||||||
|
class hashabledict(OrderedDict):
|
||||||
|
def __key(self):
|
||||||
|
return tuple((k, self[k]) for k in sorted(self))
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.__key())
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__key() == other.__key()
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.__key() < other.__key()
|
||||||
|
|
||||||
|
def __qt__(self, other):
|
||||||
|
return self.__key() > other.__key()
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder(json.JSONDecoder):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
json.JSONDecoder.__init__(self, **kwargs)
|
||||||
|
self.parse_array = self.JSONArray
|
||||||
|
# Use the python implemenation of the scanner
|
||||||
|
self.scan_once = json.scanner.py_make_scanner(self)
|
||||||
|
|
||||||
|
def JSONArray(self, s_and_end, scan_once, **kwargs):
|
||||||
|
values, end = json.decoder.JSONArray(s_and_end, scan_once, **kwargs)
|
||||||
|
return set(values), end
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
if isinstance(obj, set):
|
||||||
|
return sorted(obj)
|
||||||
|
return super().default(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def write_json_report(url, remote_apk, unsigned_apk, compare_result):
|
||||||
|
"""write out the results of the verify run to JSON
|
||||||
|
|
||||||
|
This builds up reports on the repeated runs of `fdroid verify` on
|
||||||
|
a set of apps. It uses the timestamps on the compared files to
|
||||||
|
ensure that there is only one report per file, even when run
|
||||||
|
repeatedly.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
jsonfile = unsigned_apk + '.json'
|
||||||
|
if os.path.exists(jsonfile):
|
||||||
|
with open(jsonfile) as fp:
|
||||||
|
data = json.load(fp, object_pairs_hook=OrderedDict)
|
||||||
|
else:
|
||||||
|
data = OrderedDict()
|
||||||
|
output = hashabledict()
|
||||||
|
output['url'] = url
|
||||||
|
for key, filename in (('local', unsigned_apk), ('remote', remote_apk)):
|
||||||
|
d = hashabledict()
|
||||||
|
output[key] = d
|
||||||
|
d['file'] = filename
|
||||||
|
d['sha256'] = update.sha256sum(filename)
|
||||||
|
d['timestamp'] = os.stat(filename).st_ctime
|
||||||
|
d['packageName'], d['versionCode'], d['versionName'] = common.get_apk_id(filename)
|
||||||
|
if compare_result:
|
||||||
|
output['verified'] = False
|
||||||
|
output['result'] = compare_result
|
||||||
|
else:
|
||||||
|
output['verified'] = True
|
||||||
|
data[str(output['local']['timestamp'])] = output # str makes better dict keys than float
|
||||||
|
with open(jsonfile, 'w') as fp:
|
||||||
|
json.dump(data, fp, sort_keys=True)
|
||||||
|
|
||||||
|
if output['verified']:
|
||||||
|
jsonfile = 'unsigned/verified.json'
|
||||||
|
if os.path.exists(jsonfile):
|
||||||
|
with open(jsonfile) as fp:
|
||||||
|
data = json.load(fp, cls=Decoder, object_pairs_hook=hashabledict)
|
||||||
|
else:
|
||||||
|
data = OrderedDict()
|
||||||
|
data['packages'] = OrderedDict()
|
||||||
|
packageName = output['local']['packageName']
|
||||||
|
if packageName not in data['packages']:
|
||||||
|
data['packages'][packageName] = set()
|
||||||
|
data['packages'][packageName].add(output)
|
||||||
|
with open(jsonfile, 'w') as fp:
|
||||||
|
json.dump(data, fp, cls=Encoder, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
global options, config
|
global options, config
|
||||||
@ -42,6 +131,8 @@ def main():
|
|||||||
parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]"))
|
parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]"))
|
||||||
parser.add_argument("--reuse-remote-apk", action="store_true", default=False,
|
parser.add_argument("--reuse-remote-apk", action="store_true", default=False,
|
||||||
help=_("Verify against locally cached copy rather than redownloading."))
|
help=_("Verify against locally cached copy rather than redownloading."))
|
||||||
|
parser.add_argument("--output-json", action="store_true", default=False,
|
||||||
|
help=_("Output JSON report to file named after APK."))
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
|
||||||
config = common.read_config(options)
|
config = common.read_config(options)
|
||||||
@ -64,6 +155,7 @@ def main():
|
|||||||
for apkfile in sorted(glob.glob(os.path.join(unsigned_dir, '*.apk'))):
|
for apkfile in sorted(glob.glob(os.path.join(unsigned_dir, '*.apk'))):
|
||||||
|
|
||||||
apkfilename = os.path.basename(apkfile)
|
apkfilename = os.path.basename(apkfile)
|
||||||
|
url = 'https://f-droid.org/repo/' + apkfilename
|
||||||
appid, vercode = common.publishednameinfo(apkfile)
|
appid, vercode = common.publishednameinfo(apkfile)
|
||||||
|
|
||||||
if vercodes and appid not in vercodes:
|
if vercodes and appid not in vercodes:
|
||||||
@ -79,7 +171,6 @@ def main():
|
|||||||
if not options.reuse_remote_apk or not os.path.exists(remote_apk):
|
if not options.reuse_remote_apk or not os.path.exists(remote_apk):
|
||||||
if os.path.exists(remote_apk):
|
if os.path.exists(remote_apk):
|
||||||
os.remove(remote_apk)
|
os.remove(remote_apk)
|
||||||
url = 'https://f-droid.org/repo/' + apkfilename
|
|
||||||
logging.info("...retrieving " + url)
|
logging.info("...retrieving " + url)
|
||||||
try:
|
try:
|
||||||
net.download_file(url, dldir=tmp_dir)
|
net.download_file(url, dldir=tmp_dir)
|
||||||
@ -90,10 +181,10 @@ def main():
|
|||||||
raise FDroidException(_('Downloading {url} failed. {error}')
|
raise FDroidException(_('Downloading {url} failed. {error}')
|
||||||
.format(url=url, error=e))
|
.format(url=url, error=e))
|
||||||
|
|
||||||
compare_result = common.verify_apks(
|
unsigned_apk = os.path.join(unsigned_dir, apkfilename)
|
||||||
remote_apk,
|
compare_result = common.verify_apks(remote_apk, unsigned_apk, tmp_dir)
|
||||||
os.path.join(unsigned_dir, apkfilename),
|
if options.output_json:
|
||||||
tmp_dir)
|
write_json_report(url, remote_apk, unsigned_apk, compare_result)
|
||||||
if compare_result:
|
if compare_result:
|
||||||
raise FDroidException(compare_result)
|
raise FDroidException(compare_result)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user