123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779 |
- 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
- import 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.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.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(.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):
- if headers:
- self.send_response(200)
- self.send_header("Content-type", ctype)
- self.send_header("Content-Length", str(len(s)))
- if mtime is None:
- mtime = self.dynamic_mtime
- self.send_header("Last-Modified", self.date_time_string(mtime))
- self.end_headers()
- return StringIO.StringIO(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,'r')
- except IOError:
- return self.send_404()
- fs = os.fstat(f.fileno())
- data = f.read()
- 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)
|