| from __future__ import print_function |
| |
| try: |
| from http.server import HTTPServer, SimpleHTTPRequestHandler |
| except ImportError: |
| from BaseHTTPServer import HTTPServer |
| from SimpleHTTPServer import SimpleHTTPRequestHandler |
| import os |
| import sys |
| |
| try: |
| from urlparse import urlparse |
| from urllib import unquote |
| except ImportError: |
| from urllib.parse import urlparse, unquote |
| |
| import posixpath |
| |
| if sys.version_info.major >= 3: |
| from io import StringIO, BytesIO |
| else: |
| from io import BytesIO, BytesIO as StringIO |
| |
| import re |
| import shutil |
| import threading |
| import time |
| import socket |
| import itertools |
| |
| import Reporter |
| |
| try: |
| import configparser |
| except ImportError: |
| import ConfigParser as configparser |
| |
| ### |
| # Various patterns matched or replaced by server. |
| |
| kReportFileRE = re.compile("(.*/)?report-(.*)\\.html") |
| |
| kBugKeyValueRE = re.compile("<!-- BUG([^ ]*) (.*) -->") |
| |
| # <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" --> |
| |
| kReportCrashEntryRE = re.compile("<!-- REPORTPROBLEM (.*?)-->") |
| kReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"') |
| |
| kReportReplacements = [] |
| |
| # Add custom javascript. |
| kReportReplacements.append( |
| ( |
| re.compile("<!-- SUMMARYENDHEAD -->"), |
| """\ |
| <script language="javascript" type="text/javascript"> |
| function load(url) { |
| if (window.XMLHttpRequest) { |
| req = new XMLHttpRequest(); |
| } else if (window.ActiveXObject) { |
| req = new ActiveXObject("Microsoft.XMLHTTP"); |
| } |
| if (req != undefined) { |
| req.open("GET", url, true); |
| req.send(""); |
| } |
| } |
| </script>""", |
| ) |
| ) |
| |
| # Insert additional columns. |
| kReportReplacements.append((re.compile("<!-- REPORTBUGCOL -->"), "<td></td><td></td>")) |
| |
| # Insert report bug and open file links. |
| kReportReplacements.append( |
| ( |
| re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'), |
| ( |
| '<td class="Button"><a href="report/\\1">Report Bug</a></td>' |
| + '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>' |
| ), |
| ) |
| ) |
| |
| kReportReplacements.append( |
| ( |
| re.compile("<!-- REPORTHEADER -->"), |
| '<h3><a href="/">Summary</a> > Report %(report)s</h3>', |
| ) |
| ) |
| |
| kReportReplacements.append( |
| ( |
| re.compile("<!-- REPORTSUMMARYEXTRA -->"), |
| '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>', |
| ) |
| ) |
| |
| # Insert report crashes link. |
| |
| # Disabled for the time being until we decide exactly when this should |
| # be enabled. Also the radar reporter needs to be fixed to report |
| # multiple files. |
| |
| # kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'), |
| # '<br>These files will automatically be attached to ' + |
| # 'reports filed here: <a href="report_crashes">Report Crashes</a>.')) |
| |
| ### |
| # Other simple parameters |
| |
| kShare = posixpath.join(posixpath.dirname(__file__), "../share/scan-view") |
| kConfigPath = os.path.expanduser("~/.scanview.cfg") |
| |
| ### |
| |
| __version__ = "0.1" |
| |
| __all__ = ["create_server"] |
| |
| |
| class ReporterThread(threading.Thread): |
| def __init__(self, report, reporter, parameters, server): |
| threading.Thread.__init__(self) |
| self.report = report |
| self.server = server |
| self.reporter = reporter |
| self.parameters = parameters |
| self.success = False |
| self.status = None |
| |
| def run(self): |
| result = None |
| try: |
| if self.server.options.debug: |
| print("%s: SERVER: submitting bug." % (sys.argv[0],), file=sys.stderr) |
| self.status = self.reporter.fileReport(self.report, self.parameters) |
| self.success = True |
| time.sleep(3) |
| if self.server.options.debug: |
| print( |
| "%s: SERVER: submission complete." % (sys.argv[0],), file=sys.stderr |
| ) |
| except Reporter.ReportFailure as e: |
| self.status = e.value |
| except Exception as e: |
| s = StringIO() |
| import traceback |
| |
| print("<b>Unhandled Exception</b><br><pre>", file=s) |
| traceback.print_exc(file=s) |
| print("</pre>", file=s) |
| self.status = s.getvalue() |
| |
| |
| class ScanViewServer(HTTPServer): |
| def __init__(self, address, handler, root, reporters, options): |
| HTTPServer.__init__(self, address, handler) |
| self.root = root |
| self.reporters = reporters |
| self.options = options |
| self.halted = False |
| self.config = None |
| self.load_config() |
| |
| def load_config(self): |
| self.config = configparser.RawConfigParser() |
| |
| # Add defaults |
| self.config.add_section("ScanView") |
| for r in self.reporters: |
| self.config.add_section(r.getName()) |
| for p in r.getParameters(): |
| if p.saveConfigValue(): |
| self.config.set(r.getName(), p.getName(), "") |
| |
| # Ignore parse errors |
| try: |
| self.config.read([kConfigPath]) |
| except: |
| pass |
| |
| # Save on exit |
| import atexit |
| |
| atexit.register(lambda: self.save_config()) |
| |
| def save_config(self): |
| # Ignore errors (only called on exit). |
| try: |
| f = open(kConfigPath, "w") |
| self.config.write(f) |
| f.close() |
| except: |
| pass |
| |
| def halt(self): |
| self.halted = True |
| if self.options.debug: |
| print("%s: SERVER: halting." % (sys.argv[0],), file=sys.stderr) |
| |
| def serve_forever(self): |
| while not self.halted: |
| if self.options.debug > 1: |
| print("%s: SERVER: waiting..." % (sys.argv[0],), file=sys.stderr) |
| try: |
| self.handle_request() |
| except OSError as e: |
| print("OSError", e.errno) |
| |
| def finish_request(self, request, client_address): |
| if self.options.autoReload: |
| import ScanView |
| |
| self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler |
| HTTPServer.finish_request(self, request, client_address) |
| |
| def handle_error(self, request, client_address): |
| # Ignore socket errors |
| info = sys.exc_info() |
| if info and isinstance(info[1], socket.error): |
| if self.options.debug > 1: |
| print( |
| "%s: SERVER: ignored socket error." % (sys.argv[0],), |
| file=sys.stderr, |
| ) |
| return |
| HTTPServer.handle_error(self, request, client_address) |
| |
| |
| # Borrowed from Quixote, with simplifications. |
| def parse_query(qs, fields=None): |
| if fields is None: |
| fields = {} |
| for chunk in (_f for _f in qs.split("&") if _f): |
| if "=" not in chunk: |
| name = chunk |
| value = "" |
| else: |
| name, value = chunk.split("=", 1) |
| name = unquote(name.replace("+", " ")) |
| value = unquote(value.replace("+", " ")) |
| item = fields.get(name) |
| if item is None: |
| fields[name] = [value] |
| else: |
| item.append(value) |
| return fields |
| |
| |
| class ScanViewRequestHandler(SimpleHTTPRequestHandler): |
| server_version = "ScanViewServer/" + __version__ |
| dynamic_mtime = time.time() |
| |
| def do_HEAD(self): |
| try: |
| SimpleHTTPRequestHandler.do_HEAD(self) |
| except Exception as e: |
| self.handle_exception(e) |
| |
| def do_GET(self): |
| try: |
| SimpleHTTPRequestHandler.do_GET(self) |
| except Exception as e: |
| self.handle_exception(e) |
| |
| def do_POST(self): |
| """Serve a POST request.""" |
| try: |
| length = self.headers.getheader("content-length") or "0" |
| try: |
| length = int(length) |
| except: |
| length = 0 |
| content = self.rfile.read(length) |
| fields = parse_query(content) |
| f = self.send_head(fields) |
| if f: |
| self.copyfile(f, self.wfile) |
| f.close() |
| except Exception as e: |
| self.handle_exception(e) |
| |
| def log_message(self, format, *args): |
| if self.server.options.debug: |
| sys.stderr.write( |
| "%s: SERVER: %s - - [%s] %s\n" |
| % ( |
| sys.argv[0], |
| self.address_string(), |
| self.log_date_time_string(), |
| format % args, |
| ) |
| ) |
| |
| def load_report(self, report): |
| path = os.path.join(self.server.root, "report-%s.html" % report) |
| data = open(path).read() |
| keys = {} |
| for item in kBugKeyValueRE.finditer(data): |
| k, v = item.groups() |
| keys[k] = v |
| return keys |
| |
| def load_crashes(self): |
| path = posixpath.join(self.server.root, "index.html") |
| data = open(path).read() |
| problems = [] |
| for item in kReportCrashEntryRE.finditer(data): |
| fieldData = item.group(1) |
| fields = dict( |
| [i.groups() for i in kReportCrashEntryKeyValueRE.finditer(fieldData)] |
| ) |
| problems.append(fields) |
| return problems |
| |
| def handle_exception(self, exc): |
| import traceback |
| |
| s = StringIO() |
| print("INTERNAL ERROR\n", file=s) |
| traceback.print_exc(file=s) |
| f = self.send_string(s.getvalue(), "text/plain") |
| if f: |
| self.copyfile(f, self.wfile) |
| f.close() |
| |
| def get_scalar_field(self, name): |
| if name in self.fields: |
| return self.fields[name][0] |
| else: |
| return None |
| |
| def submit_bug(self, c): |
| title = self.get_scalar_field("title") |
| description = self.get_scalar_field("description") |
| report = self.get_scalar_field("report") |
| reporterIndex = self.get_scalar_field("reporter") |
| files = [] |
| for fileID in self.fields.get("files", []): |
| try: |
| i = int(fileID) |
| except: |
| i = None |
| if i is None or i < 0 or i >= len(c.files): |
| return (False, "Invalid file ID") |
| files.append(c.files[i]) |
| |
| if not title: |
| return (False, "Missing title.") |
| if not description: |
| return (False, "Missing description.") |
| try: |
| reporterIndex = int(reporterIndex) |
| except: |
| return (False, "Invalid report method.") |
| |
| # Get the reporter and parameters. |
| reporter = self.server.reporters[reporterIndex] |
| parameters = {} |
| for o in reporter.getParameters(): |
| name = "%s_%s" % (reporter.getName(), o.getName()) |
| if name not in self.fields: |
| return ( |
| False, |
| 'Missing field "%s" for %s report method.' |
| % (name, reporter.getName()), |
| ) |
| parameters[o.getName()] = self.get_scalar_field(name) |
| |
| # Update config defaults. |
| if report != "None": |
| self.server.config.set("ScanView", "reporter", reporterIndex) |
| for o in reporter.getParameters(): |
| if o.saveConfigValue(): |
| name = o.getName() |
| self.server.config.set(reporter.getName(), name, parameters[name]) |
| |
| # Create the report. |
| bug = Reporter.BugReport(title, description, files) |
| |
| # Kick off a reporting thread. |
| t = ReporterThread(bug, reporter, parameters, self.server) |
| t.start() |
| |
| # Wait for thread to die... |
| while t.isAlive(): |
| time.sleep(0.25) |
| submitStatus = t.status |
| |
| return (t.success, t.status) |
| |
| def send_report_submit(self): |
| report = self.get_scalar_field("report") |
| c = self.get_report_context(report) |
| if c.reportSource is None: |
| reportingFor = "Report Crashes > " |
| fileBug = ( |
| """\ |
| <a href="/report_crashes">File Bug</a> > """ |
| % locals() |
| ) |
| else: |
| reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource, report) |
| fileBug = '<a href="/report/%s">File Bug</a> > ' % report |
| title = self.get_scalar_field("title") |
| description = self.get_scalar_field("description") |
| |
| res, message = self.submit_bug(c) |
| |
| if res: |
| statusClass = "SubmitOk" |
| statusName = "Succeeded" |
| else: |
| statusClass = "SubmitFail" |
| statusName = "Failed" |
| |
| result = ( |
| """ |
| <head> |
| <title>Bug Submission</title> |
| <link rel="stylesheet" type="text/css" href="/scanview.css" /> |
| </head> |
| <body> |
| <h3> |
| <a href="/">Summary</a> > |
| %(reportingFor)s |
| %(fileBug)s |
| Submit</h3> |
| <form name="form" action=""> |
| <table class="form"> |
| <tr><td> |
| <table class="form_group"> |
| <tr> |
| <td class="form_clabel">Title:</td> |
| <td class="form_value"> |
| <input type="text" name="title" size="50" value="%(title)s" disabled> |
| </td> |
| </tr> |
| <tr> |
| <td class="form_label">Description:</td> |
| <td class="form_value"> |
| <textarea rows="10" cols="80" name="description" disabled> |
| %(description)s |
| </textarea> |
| </td> |
| </table> |
| </td></tr> |
| </table> |
| </form> |
| <h1 class="%(statusClass)s">Submission %(statusName)s</h1> |
| %(message)s |
| <p> |
| <hr> |
| <a href="/">Return to Summary</a> |
| </body> |
| </html>""" |
| % locals() |
| ) |
| return self.send_string(result) |
| |
| def send_open_report(self, report): |
| try: |
| keys = self.load_report(report) |
| except IOError: |
| return self.send_error(400, "Invalid report.") |
| |
| file = keys.get("FILE") |
| if not file or not posixpath.exists(file): |
| return self.send_error(400, 'File does not exist: "%s"' % file) |
| |
| import startfile |
| |
| if self.server.options.debug: |
| print('%s: SERVER: opening "%s"' % (sys.argv[0], file), file=sys.stderr) |
| |
| status = startfile.open(file) |
| if status: |
| res = 'Opened: "%s"' % file |
| else: |
| res = 'Open failed: "%s"' % file |
| |
| return self.send_string(res, "text/plain") |
| |
| def get_report_context(self, report): |
| class Context(object): |
| pass |
| |
| if report is None or report == "None": |
| data = self.load_crashes() |
| # Don't allow empty reports. |
| if not data: |
| raise ValueError("No crashes detected!") |
| c = Context() |
| c.title = "clang static analyzer failures" |
| |
| stderrSummary = "" |
| for item in data: |
| if "stderr" in item: |
| path = posixpath.join(self.server.root, item["stderr"]) |
| if os.path.exists(path): |
| lns = itertools.islice(open(path), 0, 10) |
| stderrSummary += "%s\n--\n%s" % ( |
| item.get("src", "<unknown>"), |
| "".join(lns), |
| ) |
| |
| c.description = """\ |
| The clang static analyzer failed on these inputs: |
| %s |
| |
| STDERR Summary |
| -------------- |
| %s |
| """ % ( |
| "\n".join([item.get("src", "<unknown>") for item in data]), |
| stderrSummary, |
| ) |
| c.reportSource = None |
| c.navMarkup = "Report Crashes > " |
| c.files = [] |
| for item in data: |
| c.files.append(item.get("src", "")) |
| c.files.append(posixpath.join(self.server.root, item.get("file", ""))) |
| c.files.append( |
| posixpath.join(self.server.root, item.get("clangfile", "")) |
| ) |
| c.files.append(posixpath.join(self.server.root, item.get("stderr", ""))) |
| c.files.append(posixpath.join(self.server.root, item.get("info", ""))) |
| # Just in case something failed, ignore files which don't |
| # exist. |
| c.files = [f for f in c.files if os.path.exists(f) and os.path.isfile(f)] |
| else: |
| # Check that this is a valid report. |
| path = posixpath.join(self.server.root, "report-%s.html" % report) |
| if not posixpath.exists(path): |
| raise ValueError("Invalid report ID") |
| keys = self.load_report(report) |
| c = Context() |
| c.title = keys.get("DESC", "clang error (unrecognized") |
| c.description = """\ |
| Bug reported by the clang static analyzer. |
| |
| Description: %s |
| File: %s |
| Line: %s |
| """ % ( |
| c.title, |
| keys.get("FILE", "<unknown>"), |
| keys.get("LINE", "<unknown>"), |
| ) |
| c.reportSource = "report-%s.html" % report |
| c.navMarkup = """<a href="/%s">Report %s</a> > """ % ( |
| c.reportSource, |
| report, |
| ) |
| |
| c.files = [path] |
| return c |
| |
| def send_report(self, report, configOverrides=None): |
| def getConfigOption(section, field): |
| if ( |
| configOverrides is not None |
| and section in configOverrides |
| and field in configOverrides[section] |
| ): |
| return configOverrides[section][field] |
| return self.server.config.get(section, field) |
| |
| # report is None is used for crashes |
| try: |
| c = self.get_report_context(report) |
| except ValueError as e: |
| return self.send_error(400, e.message) |
| |
| title = c.title |
| description = c.description |
| reportingFor = c.navMarkup |
| if c.reportSource is None: |
| extraIFrame = "" |
| else: |
| extraIFrame = """\ |
| <iframe src="/%s" width="100%%" height="40%%" |
| scrolling="auto" frameborder="1"> |
| <a href="/%s">View Bug Report</a> |
| </iframe>""" % ( |
| c.reportSource, |
| c.reportSource, |
| ) |
| |
| reporterSelections = [] |
| reporterOptions = [] |
| |
| try: |
| active = int(getConfigOption("ScanView", "reporter")) |
| except: |
| active = 0 |
| for i, r in enumerate(self.server.reporters): |
| selected = i == active |
| if selected: |
| selectedStr = " selected" |
| else: |
| selectedStr = "" |
| reporterSelections.append( |
| '<option value="%d"%s>%s</option>' % (i, selectedStr, r.getName()) |
| ) |
| options = "\n".join( |
| [o.getHTML(r, title, getConfigOption) for o in r.getParameters()] |
| ) |
| display = ("none", "")[selected] |
| reporterOptions.append( |
| """\ |
| <tr id="%sReporterOptions" style="display:%s"> |
| <td class="form_label">%s Options</td> |
| <td class="form_value"> |
| <table class="form_inner_group"> |
| %s |
| </table> |
| </td> |
| </tr> |
| """ |
| % (r.getName(), display, r.getName(), options) |
| ) |
| reporterSelections = "\n".join(reporterSelections) |
| reporterOptionsDivs = "\n".join(reporterOptions) |
| reportersArray = "[%s]" % ( |
| ",".join([repr(r.getName()) for r in self.server.reporters]) |
| ) |
| |
| if c.files: |
| fieldSize = min(5, len(c.files)) |
| attachFileOptions = "\n".join( |
| [ |
| """\ |
| <option value="%d" selected>%s</option>""" |
| % (i, v) |
| for i, v in enumerate(c.files) |
| ] |
| ) |
| attachFileRow = """\ |
| <tr> |
| <td class="form_label">Attach:</td> |
| <td class="form_value"> |
| <select style="width:100%%" name="files" multiple size=%d> |
| %s |
| </select> |
| </td> |
| </tr> |
| """ % ( |
| min(5, len(c.files)), |
| attachFileOptions, |
| ) |
| else: |
| attachFileRow = "" |
| |
| result = ( |
| """<html> |
| <head> |
| <title>File Bug</title> |
| <link rel="stylesheet" type="text/css" href="/scanview.css" /> |
| </head> |
| <script language="javascript" type="text/javascript"> |
| var reporters = %(reportersArray)s; |
| function updateReporterOptions() { |
| index = document.getElementById('reporter').selectedIndex; |
| for (var i=0; i < reporters.length; ++i) { |
| o = document.getElementById(reporters[i] + "ReporterOptions"); |
| if (i == index) { |
| o.style.display = ""; |
| } else { |
| o.style.display = "none"; |
| } |
| } |
| } |
| </script> |
| <body onLoad="updateReporterOptions()"> |
| <h3> |
| <a href="/">Summary</a> > |
| %(reportingFor)s |
| File Bug</h3> |
| <form name="form" action="/report_submit" method="post"> |
| <input type="hidden" name="report" value="%(report)s"> |
| |
| <table class="form"> |
| <tr><td> |
| <table class="form_group"> |
| <tr> |
| <td class="form_clabel">Title:</td> |
| <td class="form_value"> |
| <input type="text" name="title" size="50" value="%(title)s"> |
| </td> |
| </tr> |
| <tr> |
| <td class="form_label">Description:</td> |
| <td class="form_value"> |
| <textarea rows="10" cols="80" name="description"> |
| %(description)s |
| </textarea> |
| </td> |
| </tr> |
| |
| %(attachFileRow)s |
| |
| </table> |
| <br> |
| <table class="form_group"> |
| <tr> |
| <td class="form_clabel">Method:</td> |
| <td class="form_value"> |
| <select id="reporter" name="reporter" onChange="updateReporterOptions()"> |
| %(reporterSelections)s |
| </select> |
| </td> |
| </tr> |
| %(reporterOptionsDivs)s |
| </table> |
| <br> |
| </td></tr> |
| <tr><td class="form_submit"> |
| <input align="right" type="submit" name="Submit" value="Submit"> |
| </td></tr> |
| </table> |
| </form> |
| |
| %(extraIFrame)s |
| |
| </body> |
| </html>""" |
| % locals() |
| ) |
| |
| return self.send_string(result) |
| |
| def send_head(self, fields=None): |
| if self.server.options.onlyServeLocal and self.client_address[0] != "127.0.0.1": |
| return self.send_error(401, "Unauthorized host.") |
| |
| if fields is None: |
| fields = {} |
| self.fields = fields |
| |
| o = urlparse(self.path) |
| self.fields = parse_query(o.query, fields) |
| path = posixpath.normpath(unquote(o.path)) |
| |
| # Split the components and strip the root prefix. |
| components = path.split("/")[1:] |
| |
| # Special case some top-level entries. |
| if components: |
| name = components[0] |
| if len(components) == 2: |
| if name == "report": |
| return self.send_report(components[1]) |
| elif name == "open": |
| return self.send_open_report(components[1]) |
| elif len(components) == 1: |
| if name == "quit": |
| self.server.halt() |
| return self.send_string("Goodbye.", "text/plain") |
| elif name == "report_submit": |
| return self.send_report_submit() |
| elif name == "report_crashes": |
| overrides = {"ScanView": {}, "Radar": {}, "Email": {}} |
| for i, r in enumerate(self.server.reporters): |
| if r.getName() == "Radar": |
| overrides["ScanView"]["reporter"] = i |
| break |
| overrides["Radar"]["Component"] = "llvm - checker" |
| overrides["Radar"]["Component Version"] = "X" |
| return self.send_report(None, overrides) |
| elif name == "favicon.ico": |
| return self.send_path(posixpath.join(kShare, "bugcatcher.ico")) |
| |
| # Match directory entries. |
| if components[-1] == "": |
| components[-1] = "index.html" |
| |
| relpath = "/".join(components) |
| path = posixpath.join(self.server.root, relpath) |
| |
| if self.server.options.debug > 1: |
| print( |
| '%s: SERVER: sending path "%s"' % (sys.argv[0], path), file=sys.stderr |
| ) |
| return self.send_path(path) |
| |
| def send_404(self): |
| self.send_error(404, "File not found") |
| return None |
| |
| def send_path(self, path): |
| # If the requested path is outside the root directory, do not open it |
| rel = os.path.abspath(path) |
| if not rel.startswith(os.path.abspath(self.server.root)): |
| return self.send_404() |
| |
| ctype = self.guess_type(path) |
| if ctype.startswith("text/"): |
| # Patch file instead |
| return self.send_patched_file(path, ctype) |
| else: |
| mode = "rb" |
| try: |
| f = open(path, mode) |
| except IOError: |
| return self.send_404() |
| return self.send_file(f, ctype) |
| |
| def send_file(self, f, ctype): |
| # Patch files to add links, but skip binary files. |
| self.send_response(200) |
| self.send_header("Content-type", ctype) |
| fs = os.fstat(f.fileno()) |
| self.send_header("Content-Length", str(fs[6])) |
| self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) |
| self.end_headers() |
| return f |
| |
| def send_string(self, s, ctype="text/html", headers=True, mtime=None): |
| encoded_s = s.encode("utf-8") |
| if headers: |
| self.send_response(200) |
| self.send_header("Content-type", ctype) |
| self.send_header("Content-Length", str(len(encoded_s))) |
| if mtime is None: |
| mtime = self.dynamic_mtime |
| self.send_header("Last-Modified", self.date_time_string(mtime)) |
| self.end_headers() |
| return BytesIO(encoded_s) |
| |
| def send_patched_file(self, path, ctype): |
| # Allow a very limited set of variables. This is pretty gross. |
| variables = {} |
| variables["report"] = "" |
| m = kReportFileRE.match(path) |
| if m: |
| variables["report"] = m.group(2) |
| |
| try: |
| f = open(path, "rb") |
| except IOError: |
| return self.send_404() |
| fs = os.fstat(f.fileno()) |
| data = f.read().decode("utf-8") |
| for a, b in kReportReplacements: |
| data = a.sub(b % variables, data) |
| return self.send_string(data, ctype, mtime=fs.st_mtime) |
| |
| |
| def create_server(address, options, root): |
| import Reporter |
| |
| reporters = Reporter.getReporters() |
| |
| return ScanViewServer(address, ScanViewRequestHandler, root, reporters, options) |