summaryrefslogtreecommitdiffstatshomepage
path: root/scripts/minimaws/lib/wsgiserve.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/minimaws/lib/wsgiserve.py')
-rw-r--r--scripts/minimaws/lib/wsgiserve.py710
1 files changed, 588 insertions, 122 deletions
diff --git a/scripts/minimaws/lib/wsgiserve.py b/scripts/minimaws/lib/wsgiserve.py
index b30828e01e0..56197407516 100644
--- a/scripts/minimaws/lib/wsgiserve.py
+++ b/scripts/minimaws/lib/wsgiserve.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
##
## license:BSD-3-Clause
## copyright-holders:Vas Crabb
@@ -6,20 +6,22 @@
from . import dbaccess
from . import htmltmpl
-import cgi
+import html
import inspect
import json
import mimetypes
import os.path
import re
import sys
-import wsgiref.simple_server
+import urllib
+import urllib.parse
import wsgiref.util
-if sys.version_info >= (3, ):
- import urllib.parse as urlparse
-else:
- import urlparse
+htmlescape = html.escape
+shiftpath = wsgiref.util.shift_path_info
+urljoin = urllib.parse.urljoin
+urlparsequery = urllib.parse.parse_qs
+urlquote = urllib.parse.quote
class HandlerBase(object):
@@ -37,7 +39,7 @@ class HandlerBase(object):
505: 'HTTP Version Not Supported' }
def __init__(self, app, application_uri, environ, start_response, **kwargs):
- super(HandlerBase, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.app = app
self.js_escape = app.js_escape
self.application_uri = application_uri
@@ -45,12 +47,12 @@ class HandlerBase(object):
self.start_response = start_response
def error_page(self, code):
- yield htmltmpl.ERROR_PAGE.substitute(code=cgi.escape('%d' % code), message=cgi.escape(self.STATUS_MESSAGE[code])).encode('utf-8')
+ yield htmltmpl.ERROR_PAGE.substitute(code=htmlescape('%d' % (code, )), message=htmlescape(self.STATUS_MESSAGE[code])).encode('utf-8')
class ErrorPageHandler(HandlerBase):
def __init__(self, code, app, application_uri, environ, start_response, **kwargs):
- super(ErrorPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.code = code
self.start_response('%d %s' % (self.code, self.STATUS_MESSAGE[code]), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
@@ -59,10 +61,12 @@ class ErrorPageHandler(HandlerBase):
class AssetHandler(HandlerBase):
+ EXTENSIONMAP = { '.js': 'application/javascript', '.svg': 'image/svg+xml' }
+
def __init__(self, directory, app, application_uri, environ, start_response, **kwargs):
- super(AssetHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.directory = directory
- self.asset = wsgiref.util.shift_path_info(environ)
+ self.asset = shiftpath(environ)
def __iter__(self):
if not self.asset:
@@ -82,8 +86,11 @@ class AssetHandler(HandlerBase):
else:
try:
f = open(path, 'rb')
- type, encoding = mimetypes.guess_type(path)
- self.start_response('200 OK', [('Content-type', type or 'application/octet-stream'), ('Cache-Control', 'public, max-age=3600')])
+ base, extension = os.path.splitext(path)
+ mimetype = self.EXTENSIONMAP.get(extension)
+ if mimetype is None:
+ mimetype, encoding = mimetypes.guess_type(path)
+ self.start_response('200 OK', [('Content-type', mimetype or 'application/octet-stream'), ('Cache-Control', 'public, max-age=3600')])
return wsgiref.util.FileWrapper(f)
except:
self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
@@ -92,20 +99,100 @@ class AssetHandler(HandlerBase):
class QueryPageHandler(HandlerBase):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
- super(QueryPageHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
self.dbcurs = app.dbconn.cursor()
def machine_href(self, shortname):
- return cgi.escape(urlparse.urljoin(self.application_uri, 'machine/%s' % (shortname, )), True)
+ return htmlescape(urljoin(self.application_uri, 'machine/%s' % (urlquote(shortname), )), True)
def sourcefile_href(self, sourcefile):
- return cgi.escape(urlparse.urljoin(self.application_uri, 'sourcefile/%s' % (sourcefile, )), True)
+ return htmlescape(urljoin(self.application_uri, 'sourcefile/%s' % (urlquote(sourcefile), )), True)
+
+ def softwarelist_href(self, softwarelist):
+ return htmlescape(urljoin(self.application_uri, 'softwarelist/%s' % (urlquote(softwarelist), )), True)
+
+ def software_href(self, softwarelist, software):
+ return htmlescape(urljoin(self.application_uri, 'softwarelist/%s/%s' % (urlquote(softwarelist), urlquote(software))), True)
+
+ def bios_data(self, machine):
+ result = { }
+ for name, description, isdefault in self.dbcurs.get_biossets(machine):
+ result[name] = { 'description': description, 'isdefault': True if isdefault else False }
+ return result
+
+ def flags_data(self, machine):
+ result = { 'features': { } }
+ for feature, status, overall in self.dbcurs.get_feature_flags(machine):
+ detail = { }
+ if status == 1:
+ detail['status'] = 'imperfect'
+ elif status > 1:
+ detail['status'] = 'unemulated'
+ if overall == 1:
+ detail['overall'] = 'imperfect'
+ elif overall > 1:
+ detail['overall'] = 'unemulated'
+ result['features'][feature] = detail
+ return result
+
+ def slot_data(self, machine):
+ result = { 'defaults': { }, 'slots': { } }
+
+ # get slot options
+ prev = None
+ for slot, option, shortname, description in self.dbcurs.get_slot_options(machine):
+ if slot != prev:
+ if slot in result['slots']:
+ options = result['slots'][slot]
+ else:
+ options = { }
+ result['slots'][slot] = options
+ prev = slot
+ options[option] = { 'device': shortname, 'description': description }
+
+ # if there are any slots, get defaults
+ if result['slots']:
+ for slot, default in self.dbcurs.get_slot_defaults(machine):
+ result['defaults'][slot] = default
+
+ # remove slots that come from default cards in other slots
+ for slot in tuple(result['slots'].keys()):
+ slot += ':'
+ for candidate in tuple(result['slots'].keys()):
+ if candidate.startswith(slot):
+ del result['slots'][candidate]
+
+ return result
+
+ def softwarelist_data(self, machine):
+ result = { }
+
+ # get software lists referenced by machine
+ for softwarelist in self.dbcurs.get_machine_softwarelists(machine):
+ result[softwarelist['tag']] = {
+ 'status': softwarelist['status'],
+ 'shortname': softwarelist['shortname'],
+ 'description': softwarelist['description'],
+ 'total': softwarelist['total'],
+ 'supported': softwarelist['supported'],
+ 'partiallysupported': softwarelist['partiallysupported'],
+ 'unsupported': softwarelist['unsupported'] }
+
+ # remove software lists that come from default cards in slots
+ if result:
+ for slot, default in self.dbcurs.get_slot_defaults(machine):
+ slot += ':'
+ for candidate in tuple(result.keys()):
+ if candidate.startswith(slot):
+ del result[candidate]
+
+ return result
class MachineRpcHandlerBase(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
- super(MachineRpcHandlerBase, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
- self.shortname = wsgiref.util.shift_path_info(environ)
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.shortname = shiftpath(environ)
def __iter__(self):
if not self.shortname:
@@ -129,8 +216,8 @@ class MachineRpcHandlerBase(QueryPageHandler):
class MachineHandler(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
- super(MachineHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
- self.shortname = wsgiref.util.shift_path_info(environ)
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.shortname = shiftpath(environ)
def __iter__(self):
if not self.shortname:
@@ -141,7 +228,7 @@ class MachineHandler(QueryPageHandler):
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
else:
- machine_info = self.dbcurs.get_machine_info(self.shortname).fetchone()
+ machine_info = self.dbcurs.get_machine_details(self.shortname).fetchone()
if not machine_info:
self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
return self.error_page(404)
@@ -156,39 +243,39 @@ class MachineHandler(QueryPageHandler):
id = machine_info['id']
description = machine_info['description']
yield htmltmpl.MACHINE_PROLOGUE.substitute(
- app=self.js_escape(cgi.escape(self.application_uri, True)),
- assets=self.js_escape(cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True)),
+ app=self.js_escape(htmlescape(self.application_uri, True)),
+ assets=self.js_escape(htmlescape(urljoin(self.application_uri, 'static'), True)),
sourcehref=self.sourcefile_href(machine_info['sourcefile']),
- description=cgi.escape(description),
- shortname=cgi.escape(self.shortname),
- isdevice=cgi.escape('Yes' if machine_info['isdevice'] else 'No'),
- runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'),
- sourcefile=cgi.escape(machine_info['sourcefile'])).encode('utf-8')
+ description=htmlescape(description),
+ shortname=htmlescape(self.shortname),
+ isdevice=htmlescape('Yes' if machine_info['isdevice'] else 'No'),
+ runnable=htmlescape('Yes' if machine_info['runnable'] else 'No'),
+ sourcefile=htmlescape(machine_info['sourcefile'])).encode('utf-8')
if machine_info['year'] is not None:
yield (
' <tr><th>Year:</th><td>%s</td></tr>\n' \
' <tr><th>Manufacturer:</th><td>%s</td></tr>\n' %
- (cgi.escape(machine_info['year']), cgi.escape(machine_info['Manufacturer']))).encode('utf-8')
+ (htmlescape(machine_info['year']), htmlescape(machine_info['Manufacturer']))).encode('utf-8')
if machine_info['cloneof'] is not None:
parent = self.dbcurs.listfull(machine_info['cloneof']).fetchone()
if parent:
yield (
- ' <tr><th>Parent Machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' %
- (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['cloneof']))).encode('utf-8')
+ ' <tr><th>Parent machine:</th><td><a href="%s">%s (%s)</a></td></tr>\n' %
+ (self.machine_href(machine_info['cloneof']), htmlescape(parent[1]), htmlescape(machine_info['cloneof']))).encode('utf-8')
else:
yield (
- ' <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' %
- (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['cloneof']), True), cgi.escape(machine_info['cloneof']))).encode('utf-8')
+ ' <tr><th>Parent machine:</th><td><a href="%s">%s</a></td></tr>\n' %
+ (self.machine_href(machine_info['cloneof']), htmlescape(machine_info['cloneof']))).encode('utf-8')
if (machine_info['romof'] is not None) and (machine_info['romof'] != machine_info['cloneof']):
parent = self.dbcurs.listfull(machine_info['romof']).fetchone()
if parent:
yield (
' <tr><th>Parent ROM set:</th><td><a href="%s">%s (%s)</a></td></tr>\n' %
- (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(parent[1]), cgi.escape(machine_info['romof']))).encode('utf-8')
+ (self.machine_href(machine_info['romof']), htmlescape(parent[1]), htmlescape(machine_info['romof']))).encode('utf-8')
else:
yield (
- ' <tr><th>Parent Machine:</th><td><a href="%s">%s</a></td></tr>\n' %
- (cgi.escape('%smachine/%s' % (self.application_uri, machine_info['romof']), True), cgi.escape(machine_info['romof']))).encode('utf-8')
+ ' <tr><th>Parent ROM set:</th><td><a href="%s">%s</a></td></tr>\n' %
+ (self.machine_href(machine_info['romof']), htmlescape(machine_info['romof']))).encode('utf-8')
unemulated = []
imperfect = []
for feature, status, overall in self.dbcurs.get_feature_flags(id):
@@ -207,6 +294,38 @@ class MachineHandler(QueryPageHandler):
tuple(imperfect)).encode('utf-8');
yield '</table>\n'.encode('utf-8')
+ # make a table of clones
+ first = True
+ for clone, clonedescription, cloneyear, clonemanufacturer in self.dbcurs.get_clones(self.shortname):
+ if first:
+ yield htmltmpl.MACHINE_CLONES_PROLOGUE.substitute().encode('utf-8')
+ first = False
+ yield htmltmpl.MACHINE_CLONES_ROW.substitute(
+ href=self.machine_href(clone),
+ shortname=htmlescape(clone),
+ description=htmlescape(clonedescription),
+ year=htmlescape(cloneyear or ''),
+ manufacturer=htmlescape(clonemanufacturer or '')).encode('utf-8')
+ if not first:
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-clones').encode('utf-8')
+ yield '<script>make_collapsible(document.getElementById("heading-clones"), document.getElementById("tbl-clones"));</script>\n'.encode('utf-8')
+
+ # make a table of software lists
+ yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_PROLOGUE.substitute().encode('utf-8')
+ for softwarelist in self.dbcurs.get_machine_softwarelists(id):
+ total = softwarelist['total']
+ yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_ROW.substitute(
+ rowid=htmlescape(softwarelist['tag'].replace(':', '-'), True),
+ href=self.softwarelist_href(softwarelist['shortname']),
+ shortname=htmlescape(softwarelist['shortname']),
+ description=htmlescape(softwarelist['description']),
+ status=htmlescape(softwarelist['status']),
+ total=htmlescape('%d' % (total, )),
+ supported=htmlescape('%.1f%%' % (softwarelist['supported'] * 100.0 / (total or 1), )),
+ partiallysupported=htmlescape('%.1f%%' % (softwarelist['partiallysupported'] * 100.0 / (total or 1), )),
+ unsupported=htmlescape('%.1f%%' % (softwarelist['unsupported'] * 100.0 / (total or 1), ))).encode('utf-8')
+ yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_EPILOGUE.substitute().encode('utf-8')
+
# allow system BIOS selection
haveoptions = False
for name, desc, isdef in self.dbcurs.get_biossets(id):
@@ -215,8 +334,8 @@ class MachineHandler(QueryPageHandler):
yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
yield htmltmpl.MACHINE_BIOS_PROLOGUE.substitute().encode('utf-8')
yield htmltmpl.MACHINE_BIOS_OPTION.substitute(
- name=cgi.escape(name, True),
- description=cgi.escape(desc),
+ name=htmlescape(name, True),
+ description=htmlescape(desc),
isdefault=('yes' if isdef else 'no')).encode('utf-8')
if haveoptions:
yield '</select>\n<script>set_default_system_bios();</script>\n'.encode('utf-8')
@@ -231,26 +350,51 @@ class MachineHandler(QueryPageHandler):
yield htmltmpl.MACHINE_RAM_PROLOGUE.substitute().encode('utf-8')
first = False
yield htmltmpl.MACHINE_RAM_OPTION.substitute(
- name=cgi.escape(name, True),
- size=cgi.escape('{:,}'.format(size)),
+ name=htmlescape(name, True),
+ size=htmlescape('{:,}'.format(size)),
isdefault=('yes' if isdef else 'no')).encode('utf-8')
if not first:
- yield '</select>\n<script>set_default_ram_option();</script>\n'.encode('utf-8')
+ yield ' </select>\n <script>set_default_ram_option();</script>\n'.encode('utf-8')
# placeholder for machine slots - populated by client-side JavaScript
if self.dbcurs.count_slots(id):
if not haveoptions:
haveoptions = True
yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8')
- yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER.substitute(
- machine=self.js_escape(self.shortname)).encode('utf=8')
+ yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER_PROLOGUE.substitute().encode('utf=8')
+ pending = set((self.shortname, ))
+ added = set((self.shortname, ))
+ haveextra = set()
+ while pending:
+ requested = pending.pop()
+ slots = self.slot_data(self.dbcurs.get_machine_id(requested))
+ yield (' slot_info[%s] = %s;\n' % (self.sanitised_json(requested), self.sanitised_json(slots))).encode('utf-8')
+ for slotname, slot in slots['slots'].items():
+ for choice, card in slot.items():
+ carddev = card['device']
+ if carddev not in added:
+ pending.add(carddev)
+ added.add(carddev)
+ if (carddev not in haveextra) and (slots['defaults'].get(slotname) == choice):
+ haveextra.add(carddev)
+ cardid = self.dbcurs.get_machine_id(carddev)
+ carddev = self.sanitised_json(carddev)
+ yield (
+ ' bios_sets[%s] = %s;\n machine_flags[%s] = %s;\n softwarelist_info[%s] = %s;\n' %
+ (carddev, self.sanitised_json(self.bios_data(cardid)), carddev, self.sanitised_json(self.flags_data(cardid)), carddev, self.sanitised_json(self.softwarelist_data(cardid)))).encode('utf-8')
+ yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER_EPILOGUE.substitute(
+ machine=self.sanitised_json(self.shortname)).encode('utf=8')
+
+ # add disclosure triangle for options if present
+ if haveoptions:
+ yield htmltmpl.MACHINE_OPTIONS_EPILOGUE.substitute().encode('utf=8')
# list devices referenced by this system/device
first = True
for name, desc, src in self.dbcurs.get_devices_referenced(id):
if first:
yield \
- '<h2>Devices Referenced</h2>\n' \
+ '<h2 id="heading-dev-refs">Devices Referenced</h2>\n' \
'<table id="tbl-dev-refs">\n' \
' <thead>\n' \
' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \
@@ -260,13 +404,14 @@ class MachineHandler(QueryPageHandler):
yield self.machine_row(name, desc, src)
if not first:
yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-dev-refs').encode('utf-8')
+ yield '<script>make_collapsible(document.getElementById("heading-dev-refs"), document.getElementById("tbl-dev-refs"));</script>\n'.encode('utf-8')
# list slots where this device is an option
first = True
for name, desc, slot, opt, src in self.dbcurs.get_compatible_slots(id):
if (first):
yield \
- '<h2>Compatible Slots</h2>\n' \
+ '<h2 id="heading-comp-slots">Compatible Slots</h2>\n' \
'<table id="tbl-comp-slots">\n' \
' <thead>\n' \
' <tr><th>Short name</th><th>Description</th><th>Slot</th><th>Choice</th><th>Source file</th></tr>\n' \
@@ -276,20 +421,21 @@ class MachineHandler(QueryPageHandler):
yield htmltmpl.COMPATIBLE_SLOT_ROW.substitute(
machinehref=self.machine_href(name),
sourcehref=self.sourcefile_href(src),
- shortname=cgi.escape(name),
- description=cgi.escape(desc),
- sourcefile=cgi.escape(src),
- slot=cgi.escape(slot),
- slotoption=cgi.escape(opt)).encode('utf-8')
+ shortname=htmlescape(name),
+ description=htmlescape(desc),
+ sourcefile=htmlescape(src),
+ slot=htmlescape(slot),
+ slotoption=htmlescape(opt)).encode('utf-8')
if not first:
yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-comp-slots').encode('utf-8')
+ yield '<script>make_collapsible(document.getElementById("heading-comp-slots"), document.getElementById("tbl-comp-slots"));</script>\n'.encode('utf-8')
# list systems/devices that reference this device
first = True
for name, desc, src in self.dbcurs.get_device_references(id):
if first:
yield \
- '<h2>Referenced By</h2>\n' \
+ '<h2 id="heading-ref-by">Referenced By</h2>\n' \
'<table id="tbl-ref-by">\n' \
' <thead>\n' \
' <tr><th>Short name</th><th>Description</th><th>Source file</th></tr>\n' \
@@ -299,6 +445,7 @@ class MachineHandler(QueryPageHandler):
yield self.machine_row(name, desc, src)
if not first:
yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-ref-by').encode('utf-8')
+ yield '<script>make_collapsible(document.getElementById("heading-ref-by"), document.getElementById("tbl-ref-by"));</script>\n'.encode('utf-8')
yield '</html>\n'.encode('utf-8')
@@ -306,14 +453,18 @@ class MachineHandler(QueryPageHandler):
return (htmltmpl.MACHINE_ROW if description is not None else htmltmpl.EXCL_MACHINE_ROW).substitute(
machinehref=self.machine_href(shortname),
sourcehref=self.sourcefile_href(sourcefile),
- shortname=cgi.escape(shortname),
- description=cgi.escape(description or ''),
- sourcefile=cgi.escape(sourcefile or '')).encode('utf-8')
+ shortname=htmlescape(shortname),
+ description=htmlescape(description or ''),
+ sourcefile=htmlescape(sourcefile or '')).encode('utf-8')
+
+ @staticmethod
+ def sanitised_json(data):
+ return json.dumps(data).replace('<', '\\u003c').replace('>', '\\u003e')
class SourceFileHandler(QueryPageHandler):
def __init__(self, app, application_uri, environ, start_response, **kwargs):
- super(SourceFileHandler, self).__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
def __iter__(self):
self.filename = self.environ['PATH_INFO']
@@ -355,21 +506,21 @@ class SourceFileHandler(QueryPageHandler):
title = heading = 'All Source Files'
else:
heading = self.linked_title(pattern)
- title = 'Source Files: ' + cgi.escape(pattern)
+ title = 'Source Files: ' + htmlescape(pattern)
yield htmltmpl.SOURCEFILE_LIST_PROLOGUE.substitute(
- assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
+ assets=htmlescape(urljoin(self.application_uri, 'static'), True),
title=title,
heading=heading).encode('utf-8')
for filename, machines in self.dbcurs.get_sourcefiles(pattern):
yield htmltmpl.SOURCEFILE_LIST_ROW.substitute(
sourcefile=self.linked_title(filename, True),
- machines=cgi.escape('%d' % machines)).encode('utf-8')
- yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-sourcefiles"));</script>\n</body>\n</html>\n'.encode('utf-8')
+ machines=htmlescape('%d' % (machines, ))).encode('utf-8')
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-sourcefiles').encode('utf-8')
def sourcefile_page(self, id):
yield htmltmpl.SOURCEFILE_PROLOGUE.substitute(
- assets=cgi.escape(urlparse.urljoin(self.application_uri, 'static'), True),
- filename=cgi.escape(self.filename),
+ assets=htmlescape(urljoin(self.application_uri, 'static'), True),
+ filename=htmlescape(self.filename),
title=self.linked_title(self.filename)).encode('utf-8')
first = True
@@ -393,7 +544,7 @@ class SourceFileHandler(QueryPageHandler):
if first:
yield '<p>No machines found.</p>\n'.encode('utf-8')
else:
- yield ' </tbody>\n</table>\n<script>make_table_sortable(document.getElementById("tbl-machines"));</script>\n'.encode('utf-8')
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-machines').encode('utf-8')
yield '</body>\n</html>\n'.encode('utf-8')
@@ -401,14 +552,14 @@ class SourceFileHandler(QueryPageHandler):
parts = filename.split('/')
final = parts[-1]
del parts[-1]
- uri = urlparse.urljoin(self.application_uri, 'sourcefile')
+ uri = urljoin(self.application_uri, 'sourcefile')
title = ''
for part in parts:
- uri = urlparse.urljoin(uri + '/', part)
- title += '<a href="{0}">{1}</a>/'.format(cgi.escape(uri, True), cgi.escape(part))
+ uri = urljoin(uri + '/', urlquote(part))
+ title += '<a href="{0}">{1}</a>/'.format(htmlescape(uri, True), htmlescape(part))
if linkfinal:
- uri = urlparse.urljoin(uri + '/', final)
- return title + '<a href="{0}">{1}</a>'.format(cgi.escape(uri, True), cgi.escape(final))
+ uri = urljoin(uri + '/', urlquote(final))
+ return title + '<a href="{0}">{1}</a>'.format(htmlescape(uri, True), htmlescape(final))
else:
return title + final
@@ -416,12 +567,245 @@ class SourceFileHandler(QueryPageHandler):
return (htmltmpl.SOURCEFILE_ROW_PARENT if machine_info['cloneof'] is None else htmltmpl.SOURCEFILE_ROW_CLONE).substitute(
machinehref=self.machine_href(machine_info['shortname']),
parenthref=self.machine_href(machine_info['cloneof'] or '__invalid'),
- shortname=cgi.escape(machine_info['shortname']),
- description=cgi.escape(machine_info['description']),
- year=cgi.escape(machine_info['year'] or ''),
- manufacturer=cgi.escape(machine_info['manufacturer'] or ''),
- runnable=cgi.escape('Yes' if machine_info['runnable'] else 'No'),
- parent=cgi.escape(machine_info['cloneof'] or '')).encode('utf-8')
+ shortname=htmlescape(machine_info['shortname']),
+ description=htmlescape(machine_info['description']),
+ year=htmlescape(machine_info['year'] or ''),
+ manufacturer=htmlescape(machine_info['manufacturer'] or ''),
+ runnable=htmlescape('Yes' if machine_info['runnable'] else 'No'),
+ parent=htmlescape(machine_info['cloneof'] or '')).encode('utf-8')
+
+
+class SoftwareListHandler(QueryPageHandler):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.shortname = shiftpath(environ)
+ self.software = shiftpath(environ)
+
+ def __iter__(self):
+ if self.environ['PATH_INFO']:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.software and ('*' not in self.software) and ('?' not in self.software):
+ software_info = self.dbcurs.get_software_details(self.shortname, self.software).fetchone()
+ if not software_info:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.software_page(software_info)
+ elif self.software or (self.shortname and ('*' not in self.shortname) and ('?' not in self.shortname)):
+ softwarelist_info = self.dbcurs.get_softwarelist_details(self.shortname, self.software or None).fetchone()
+ if not softwarelist_info:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.softwarelist_page(softwarelist_info, self.software or None)
+ else:
+ if self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.softwarelist_listing_page(self.shortname or None)
+
+ def softwarelist_listing_page(self, pattern):
+ if not pattern:
+ title = heading = 'All Software Lists'
+ else:
+ title = heading = 'Software Lists: ' + htmlescape(pattern)
+ yield htmltmpl.SOFTWARELIST_LIST_PROLOGUE.substitute(
+ assets=htmlescape(urljoin(self.application_uri, 'static'), True),
+ title=title,
+ heading=heading).encode('utf-8')
+ for shortname, description, total, supported, partiallysupported, unsupported in self.dbcurs.get_softwarelists(pattern):
+ yield htmltmpl.SOFTWARELIST_LIST_ROW.substitute(
+ href=self.softwarelist_href(shortname),
+ shortname=htmlescape(shortname),
+ description=htmlescape(description),
+ total=htmlescape('%d' % (total, )),
+ supported=htmlescape('%.1f%%' % (supported * 100.0 / (total or 1), )),
+ partiallysupported=htmlescape('%.1f%%' % (partiallysupported * 100.0 / (total or 1), )),
+ unsupported=htmlescape('%.1f%%' % (unsupported * 100.0 / (total or 1), ))).encode('utf-8')
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-softwarelists').encode('utf-8')
+
+ def softwarelist_page(self, softwarelist_info, pattern):
+ if not pattern:
+ title = 'Software List: %s (%s)' % (htmlescape(softwarelist_info['description']), htmlescape(softwarelist_info['shortname']))
+ heading = htmlescape(softwarelist_info['description'])
+ else:
+ title = 'Software List: %s (%s): %s' % (htmlescape(softwarelist_info['description']), htmlescape(softwarelist_info['shortname']), htmlescape(pattern))
+ heading = '<a href="%s">%s</a>: %s' % (self.softwarelist_href(softwarelist_info['shortname']), htmlescape(softwarelist_info['description']), htmlescape(pattern))
+ yield htmltmpl.SOFTWARELIST_PROLOGUE.substitute(
+ assets=htmlescape(urljoin(self.application_uri, 'static'), True),
+ title=title,
+ heading=heading,
+ shortname=htmlescape(softwarelist_info['shortname']),
+ total=htmlescape('%d' % (softwarelist_info['total'], )),
+ supported=htmlescape('%d' % (softwarelist_info['supported'], )),
+ supportedpc=htmlescape('%.1f' % (softwarelist_info['supported'] * 100.0 / (softwarelist_info['total'] or 1), )),
+ partiallysupported=htmlescape('%d' % (softwarelist_info['partiallysupported'], )),
+ partiallysupportedpc=htmlescape('%.1f' % (softwarelist_info['partiallysupported'] * 100.0 / (softwarelist_info['total'] or 1), )),
+ unsupported=htmlescape('%d' % (softwarelist_info['unsupported'], )),
+ unsupportedpc=htmlescape('%.1f' % (softwarelist_info['unsupported'] * 100.0 / (softwarelist_info['total'] or 1), ))).encode('utf-8')
+
+ if softwarelist_info['notes'] is not None:
+ yield htmltmpl.SOFTWARELIST_NOTES_PROLOGUE.substitute().encode('utf-8')
+ first = True
+ for line in softwarelist_info['notes'].strip().splitlines():
+ if line:
+ yield (('<p>%s' if first else '<br />\n%s') % (htmlescape(line), )).encode('utf-8')
+ first = False
+ elif not first:
+ yield '</p>\n'.encode('utf-8')
+ first = True
+ if not first:
+ yield '</p>\n'.encode('utf-8')
+ yield htmltmpl.SOFTWARELIST_NOTES_EPILOGUE.substitute().encode('utf-8')
+
+ first = True
+ for machine_info in self.dbcurs.get_softwarelist_machines(softwarelist_info['id']):
+ if first:
+ yield htmltmpl.SOFTWARELIST_MACHINE_TABLE_HEADER.substitute().encode('utf-8')
+ first = False
+ yield htmltmpl.SOFTWARELIST_MACHINE_TABLE_ROW.substitute(
+ machinehref=self.machine_href(machine_info['shortname']),
+ shortname=htmlescape(machine_info['shortname']),
+ description=htmlescape(machine_info['description']),
+ year=htmlescape(machine_info['year'] or ''),
+ manufacturer=htmlescape(machine_info['manufacturer'] or ''),
+ status=htmlescape(machine_info['status'])).encode('utf-8')
+ if not first:
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-machines').encode('utf-8')
+ yield '<script>make_collapsible(document.getElementById("heading-machines"), document.getElementById("tbl-machines"));</script>\n'.encode('utf-8')
+
+ first = True
+ for software_info in self.dbcurs.get_softwarelist_software(softwarelist_info['id'], self.software or None):
+ if first:
+ yield htmltmpl.SOFTWARELIST_SOFTWARE_TABLE_HEADER.substitute().encode('utf-8')
+ first = False
+ yield self.software_row(software_info)
+ if first:
+ yield '<p>No software found.</p>\n'.encode('utf-8')
+ else:
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-software').encode('utf-8')
+ yield '<script>make_collapsible(document.getElementById("heading-software"), document.getElementById("tbl-software"));</script>\n'.encode('utf-8')
+
+ yield '</body>\n</html>\n'.encode('utf-8')
+
+ def software_page(self, software_info):
+ yield htmltmpl.SOFTWARE_PROLOGUE.substitute(
+ assets=htmlescape(urljoin(self.application_uri, 'static'), True),
+ title=htmlescape(software_info['description']),
+ heading=htmlescape(software_info['description']),
+ softwarelisthref=self.softwarelist_href(self.shortname),
+ softwarelistdescription=htmlescape(software_info['softwarelistdescription']),
+ softwarelist=htmlescape(self.shortname),
+ shortname=htmlescape(software_info['shortname']),
+ year=htmlescape(software_info['year']),
+ publisher=htmlescape(software_info['publisher'])).encode('utf-8')
+ if software_info['parent'] is not None:
+ yield (' <tr><th>Parent:</th><td><a href="%s">%s</a></td>\n' % (self.software_href(software_info['parentsoftwarelist'], software_info['parent']), htmlescape(software_info['parentdescription']))).encode('utf-8')
+ yield (' <tr><th>Supported:</th><td>%s</td>\n' % (self.format_supported(software_info['supported']), )).encode('utf-8')
+ for name, value in self.dbcurs.get_software_info(software_info['id']):
+ yield (' <tr><th>%s:</th><td>%s</td>\n' % (htmlescape(name), htmlescape(value))).encode('utf-8')
+ yield '</table>\n\n'.encode('utf-8')
+
+ first = True
+ for clone_info in self.dbcurs.get_software_clones(software_info['id']):
+ if first:
+ yield htmltmpl.SOFTWARE_CLONES_PROLOGUE.substitute().encode('utf-8')
+ first = False
+ yield self.clone_row(clone_info)
+ if not first:
+ yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-clones').encode('utf-8')
+ yield '<script>make_collapsible(document.getElementById("heading-clones"), document.getElementById("tbl-clones"));</script>\n'.encode('utf-8')
+
+ if software_info['notes'] is not None:
+ yield htmltmpl.SOFTWARE_NOTES_PROLOGUE.substitute().encode('utf-8')
+ first = True
+ for line in software_info['notes'].strip().splitlines():
+ if line:
+ yield (('<p>%s' if first else '<br />\n%s') % (htmlescape(line), )).encode('utf-8')
+ first = False
+ elif not first:
+ yield '</p>\n'.encode('utf-8')
+ first = True
+ if not first:
+ yield '</p>\n'.encode('utf-8')
+ yield htmltmpl.SOFTWARE_NOTES_EPILOGUE.substitute().encode('utf-8')
+
+ parts = self.dbcurs.get_software_parts(software_info['id']).fetchall()
+ first = True
+ for id, partname, interface, part_id in parts:
+ if first:
+ yield htmltmpl.SOFTWARE_PARTS_PROLOGUE.substitute().encode('utf-8')
+ first = False
+ yield htmltmpl.SOFTWARE_PART_PROLOGUE.substitute(
+ heading=htmlescape(('%s (%s)' % (part_id, partname)) if part_id is not None else partname),
+ shortname=htmlescape(partname),
+ interface=htmlescape(interface)).encode('utf-8')
+ for name, value in self.dbcurs.get_softwarepart_features(id):
+ yield (' <tr><th>%s:</th><td>%s</td>\n' % (htmlescape(name), htmlescape(value))).encode('utf-8')
+ yield ' </table>\n\n'.encode('utf-8')
+ if not first:
+ yield htmltmpl.SOFTWARE_PARTS_EPILOGUE.substitute().encode('utf-8')
+
+ yield '</body>\n</html>\n'.encode('utf-8')
+
+ def software_row(self, software_info):
+ parent = software_info['parent']
+ return htmltmpl.SOFTWARELIST_SOFTWARE_ROW.substitute(
+ softwarehref=self.software_href(self.shortname, software_info['shortname']),
+ shortname=htmlescape(software_info['shortname']),
+ description=htmlescape(software_info['description']),
+ year=htmlescape(software_info['year']),
+ publisher=htmlescape(software_info['publisher']),
+ supported=self.format_supported(software_info['supported']),
+ parts=htmlescape('%d' % (software_info['parts'], )),
+ baddumps=htmlescape('%d' % (software_info['baddumps'], )),
+ parent='<a href="%s">%s</a>' % (self.software_href(software_info['parentsoftwarelist'], parent), htmlescape(parent)) if parent is not None else '').encode('utf-8')
+
+ def clone_row(self, clone_info):
+ return htmltmpl.SOFTWARE_CLONES_ROW.substitute(
+ href=self.software_href(clone_info['softwarelist'], clone_info['shortname']),
+ shortname=htmlescape(clone_info['shortname']),
+ description=htmlescape(clone_info['description']),
+ year=htmlescape(clone_info['year']),
+ publisher=htmlescape(clone_info['publisher']),
+ supported=self.format_supported(clone_info['supported'])).encode('utf-8')
+
+ @staticmethod
+ def format_supported(supported):
+ return 'Yes' if supported == 0 else 'Partial' if supported == 1 else 'No'
+
+
+class RomIdentHandler(QueryPageHandler):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ self.dbcurs = app.dbconn.cursor()
+
+ def __iter__(self):
+ if self.environ['PATH_INFO']:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ self.start_response('200 OK', [('Content-type', 'text/html; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.form_page()
+
+ def form_page(self):
+ yield htmltmpl.ROMIDENT_PAGE.substitute(
+ app=self.js_escape(htmlescape(self.application_uri, True)),
+ assets=self.js_escape(htmlescape(urljoin(self.application_uri, 'static'), True))).encode('utf-8')
class BiosRpcHandler(MachineRpcHandlerBase):
@@ -434,58 +818,143 @@ class BiosRpcHandler(MachineRpcHandlerBase):
class FlagsRpcHandler(MachineRpcHandlerBase):
def data_page(self, machine):
- result = { 'features': { } }
- for feature, status, overall in self.dbcurs.get_feature_flags(machine):
- detail = { }
- if status == 1:
- detail['status'] = 'imperfect'
- elif status > 1:
- detail['status'] = 'unemulated'
- if overall == 1:
- detail['overall'] = 'imperfect'
- elif overall > 1:
- detail['overall'] = 'unemulated'
- result['features'][feature] = detail
- yield json.dumps(result).encode('utf-8')
+ yield json.dumps(self.flags_data(machine)).encode('utf-8')
class SlotsRpcHandler(MachineRpcHandlerBase):
def data_page(self, machine):
- result = { 'defaults': { }, 'slots': { } }
+ yield json.dumps(self.slot_data(machine)).encode('utf-8')
+
+
+class SoftwareListsRpcHandler(MachineRpcHandlerBase):
+ def data_page(self, machine):
+ yield json.dumps(self.softwarelist_data(machine)).encode('utf-8')
+
+
+class RomDumpsRpcHandler(QueryPageHandler):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+
+ def __iter__(self):
+ if self.environ['PATH_INFO']:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ try:
+ args = urlparsequery(self.environ['QUERY_STRING'], keep_blank_values=True, strict_parsing=True)
+ crc = args.get('crc')
+ sha1 = args.get('sha1')
+ if (len(args) == 2) and (crc is not None) and (len(crc) == 1) and (sha1 is not None) and (len(sha1) == 1):
+ crc = int(crc[0], 16)
+ sha1 = sha1[0]
+ self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.data_page(crc, sha1)
+ except BaseException as e:
+ pass
+ self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(500)
+
+ def data_page(self, crc, sha1):
+ machines = { }
+ for shortname, description, label, bad in self.dbcurs.get_rom_dumps(crc, sha1):
+ machine = machines.get(shortname)
+ if machine is None:
+ machine = { 'description': description, 'matches': [ ] }
+ machines[shortname] = machine
+ machine['matches'].append({ 'name': label, 'bad': bool(bad) })
+
+ software = { }
+ for softwarelist, softwarelistdescription, shortname, description, part, part_id, label, bad in self.dbcurs.get_software_rom_dumps(crc, sha1):
+ listinfo = software.get(softwarelist)
+ if listinfo is None:
+ listinfo = { 'description': softwarelistdescription, 'software': { } }
+ software[softwarelist] = listinfo
+ softwareinfo = listinfo['software'].get(shortname)
+ if softwareinfo is None:
+ softwareinfo = { 'description': description, 'parts': { } }
+ listinfo['software'][shortname] = softwareinfo
+ partinfo = softwareinfo['parts'].get(part)
+ if partinfo is None:
+ partinfo = { 'matches': [ ] }
+ if part_id is not None:
+ partinfo['description'] = part_id
+ softwareinfo['parts'][part] = partinfo
+ partinfo['matches'].append({ 'name': label, 'bad': bool(bad) })
+
+ result = { 'machines': machines, 'software': software }
+ yield json.dumps(result).encode('utf-8')
- # get defaults and slot options
- for slot, default in self.dbcurs.get_slot_defaults(machine):
- result['defaults'][slot] = default
- prev = None
- for slot, option, shortname, description in self.dbcurs.get_slot_options(machine):
- if slot != prev:
- if slot in result['slots']:
- options = result['slots'][slot]
- else:
- options = { }
- result['slots'][slot] = options
- prev = slot
- options[option] = { 'device': shortname, 'description': description }
- # remove slots that come from default cards in other slots
- for slot in tuple(result['slots'].keys()):
- slot += ':'
- for candidate in tuple(result['slots'].keys()):
- if candidate.startswith(slot):
- del result['slots'][candidate]
+class DiskDumpsRpcHandler(QueryPageHandler):
+ def __init__(self, app, application_uri, environ, start_response, **kwargs):
+ super().__init__(app=app, application_uri=application_uri, environ=environ, start_response=start_response, **kwargs)
+ def __iter__(self):
+ if self.environ['PATH_INFO']:
+ self.start_response('404 %s' % (self.STATUS_MESSAGE[404], ), [('Content-type', 'text/html; charset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(404)
+ elif self.environ['REQUEST_METHOD'] != 'GET':
+ self.start_response('405 %s' % (self.STATUS_MESSAGE[405], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(405)
+ else:
+ try:
+ args = urlparsequery(self.environ['QUERY_STRING'], keep_blank_values=True, strict_parsing=True)
+ sha1 = args.get('sha1')
+ if (len(args) == 1) and (sha1 is not None) and (len(sha1) == 1):
+ sha1 = sha1[0]
+ self.start_response('200 OK', [('Content-type', 'application/json; chearset=utf-8'), ('Cache-Control', 'public, max-age=3600')])
+ return self.data_page(sha1)
+ except BaseException as e:
+ pass
+ self.start_response('500 %s' % (self.STATUS_MESSAGE[500], ), [('Content-type', 'text/html; charset=utf-8'), ('Accept', 'GET, HEAD, OPTIONS'), ('Cache-Control', 'public, max-age=3600')])
+ return self.error_page(500)
+
+ def data_page(self, sha1):
+ machines = { }
+ for shortname, description, label, bad in self.dbcurs.get_disk_dumps(sha1):
+ machine = machines.get(shortname)
+ if machine is None:
+ machine = { 'description': description, 'matches': [ ] }
+ machines[shortname] = machine
+ machine['matches'].append({ 'name': label, 'bad': bool(bad) })
+
+ software = { }
+ for softwarelist, softwarelistdescription, shortname, description, part, part_id, label, bad in self.dbcurs.get_software_disk_dumps(sha1):
+ listinfo = software.get(softwarelist)
+ if listinfo is None:
+ listinfo = { 'description': softwarelistdescription, 'software': { } }
+ software[softwarelist] = listinfo
+ softwareinfo = listinfo['software'].get(shortname)
+ if softwareinfo is None:
+ softwareinfo = { 'description': description, 'parts': { } }
+ listinfo['software'][shortname] = softwareinfo
+ partinfo = softwareinfo['parts'].get(part)
+ if partinfo is None:
+ partinfo = { 'matches': [ ] }
+ if part_id is not None:
+ partinfo['description'] = part_id
+ softwareinfo['parts'][part] = partinfo
+ partinfo['matches'].append({ 'name': label, 'bad': bool(bad) })
+
+ result = { 'machines': machines, 'software': software }
yield json.dumps(result).encode('utf-8')
class MiniMawsApp(object):
JS_ESCAPE = re.compile('([\"\'\\\\])')
RPC_SERVICES = {
- 'bios': BiosRpcHandler,
- 'flags': FlagsRpcHandler,
- 'slots': SlotsRpcHandler }
+ 'bios': BiosRpcHandler,
+ 'flags': FlagsRpcHandler,
+ 'slots': SlotsRpcHandler,
+ 'softwarelists': SoftwareListsRpcHandler,
+ 'romdumps': RomDumpsRpcHandler,
+ 'diskdumps': DiskDumpsRpcHandler }
def __init__(self, dbfile, **kwargs):
- super(MiniMawsApp, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.dbconn = dbaccess.QueryConnection(dbfile)
self.assetsdir = os.path.join(os.path.dirname(inspect.getfile(self.__class__)), 'assets')
if not mimetypes.inited:
@@ -493,15 +962,21 @@ class MiniMawsApp(object):
def __call__(self, environ, start_response):
application_uri = wsgiref.util.application_uri(environ)
- module = wsgiref.util.shift_path_info(environ)
+ if application_uri[-1] != '/':
+ application_uri += '/'
+ module = shiftpath(environ)
if module == 'machine':
return MachineHandler(self, application_uri, environ, start_response)
elif module == 'sourcefile':
return SourceFileHandler(self, application_uri, environ, start_response)
+ elif module == 'softwarelist':
+ return SoftwareListHandler(self, application_uri, environ, start_response)
+ elif module == 'romident':
+ return RomIdentHandler(self, application_uri, environ, start_response)
elif module == 'static':
return AssetHandler(self.assetsdir, self, application_uri, environ, start_response)
elif module == 'rpc':
- service = wsgiref.util.shift_path_info(environ)
+ service = shiftpath(environ)
if not service:
return ErrorPageHandler(403, self, application_uri, environ, start_response)
elif service in self.RPC_SERVICES:
@@ -515,12 +990,3 @@ class MiniMawsApp(object):
def js_escape(self, str):
return self.JS_ESCAPE.sub('\\\\\\1', str).replace('\0', '\\0')
-
-
-def run_server(options):
- application = MiniMawsApp(options.database)
- server = wsgiref.simple_server.make_server(options.host, options.port, application)
- try:
- server.serve_forever()
- except KeyboardInterrupt:
- pass