106f32e7eSjoergfrom __future__ import print_function
206f32e7eSjoergtry:
306f32e7eSjoerg    from http.server import HTTPServer, SimpleHTTPRequestHandler
406f32e7eSjoergexcept ImportError:
506f32e7eSjoerg    from BaseHTTPServer import HTTPServer
606f32e7eSjoerg    from SimpleHTTPServer import SimpleHTTPRequestHandler
706f32e7eSjoergimport os
806f32e7eSjoergimport sys
906f32e7eSjoergtry:
1006f32e7eSjoerg    from urlparse import urlparse
1106f32e7eSjoerg    from urllib import unquote
1206f32e7eSjoergexcept ImportError:
1306f32e7eSjoerg    from urllib.parse import urlparse, unquote
1406f32e7eSjoerg
1506f32e7eSjoergimport posixpath
1606f32e7eSjoerg
1706f32e7eSjoergif sys.version_info.major >= 3:
1806f32e7eSjoerg    from io import StringIO, BytesIO
1906f32e7eSjoergelse:
2006f32e7eSjoerg    from io import BytesIO, BytesIO as StringIO
2106f32e7eSjoerg
2206f32e7eSjoergimport re
2306f32e7eSjoergimport shutil
2406f32e7eSjoergimport threading
2506f32e7eSjoergimport time
2606f32e7eSjoergimport socket
2706f32e7eSjoergimport itertools
2806f32e7eSjoerg
2906f32e7eSjoergimport Reporter
3006f32e7eSjoergtry:
3106f32e7eSjoerg    import configparser
3206f32e7eSjoergexcept ImportError:
3306f32e7eSjoerg    import ConfigParser as configparser
3406f32e7eSjoerg
3506f32e7eSjoerg###
3606f32e7eSjoerg# Various patterns matched or replaced by server.
3706f32e7eSjoerg
3806f32e7eSjoergkReportFileRE = re.compile('(.*/)?report-(.*)\\.html')
3906f32e7eSjoerg
4006f32e7eSjoergkBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
4106f32e7eSjoerg
4206f32e7eSjoerg#  <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" -->
4306f32e7eSjoerg
4406f32e7eSjoergkReportCrashEntryRE = re.compile('<!-- REPORTPROBLEM (.*?)-->')
4506f32e7eSjoergkReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"')
4606f32e7eSjoerg
4706f32e7eSjoergkReportReplacements = []
4806f32e7eSjoerg
4906f32e7eSjoerg# Add custom javascript.
5006f32e7eSjoergkReportReplacements.append((re.compile('<!-- SUMMARYENDHEAD -->'), """\
5106f32e7eSjoerg<script language="javascript" type="text/javascript">
5206f32e7eSjoergfunction load(url) {
5306f32e7eSjoerg  if (window.XMLHttpRequest) {
5406f32e7eSjoerg    req = new XMLHttpRequest();
5506f32e7eSjoerg  } else if (window.ActiveXObject) {
5606f32e7eSjoerg    req = new ActiveXObject("Microsoft.XMLHTTP");
5706f32e7eSjoerg  }
5806f32e7eSjoerg  if (req != undefined) {
5906f32e7eSjoerg    req.open("GET", url, true);
6006f32e7eSjoerg    req.send("");
6106f32e7eSjoerg  }
6206f32e7eSjoerg}
6306f32e7eSjoerg</script>"""))
6406f32e7eSjoerg
6506f32e7eSjoerg# Insert additional columns.
6606f32e7eSjoergkReportReplacements.append((re.compile('<!-- REPORTBUGCOL -->'),
6706f32e7eSjoerg                            '<td></td><td></td>'))
6806f32e7eSjoerg
6906f32e7eSjoerg# Insert report bug and open file links.
7006f32e7eSjoergkReportReplacements.append((re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'),
7106f32e7eSjoerg                            ('<td class="Button"><a href="report/\\1">Report Bug</a></td>' +
7206f32e7eSjoerg                             '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>')))
7306f32e7eSjoerg
7406f32e7eSjoergkReportReplacements.append((re.compile('<!-- REPORTHEADER -->'),
7506f32e7eSjoerg                                       '<h3><a href="/">Summary</a> > Report %(report)s</h3>'))
7606f32e7eSjoerg
7706f32e7eSjoergkReportReplacements.append((re.compile('<!-- REPORTSUMMARYEXTRA -->'),
7806f32e7eSjoerg                            '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>'))
7906f32e7eSjoerg
8006f32e7eSjoerg# Insert report crashes link.
8106f32e7eSjoerg
8206f32e7eSjoerg# Disabled for the time being until we decide exactly when this should
8306f32e7eSjoerg# be enabled. Also the radar reporter needs to be fixed to report
8406f32e7eSjoerg# multiple files.
8506f32e7eSjoerg
8606f32e7eSjoerg#kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'),
8706f32e7eSjoerg#                            '<br>These files will automatically be attached to ' +
8806f32e7eSjoerg#                            'reports filed here: <a href="report_crashes">Report Crashes</a>.'))
8906f32e7eSjoerg
9006f32e7eSjoerg###
9106f32e7eSjoerg# Other simple parameters
9206f32e7eSjoerg
9306f32e7eSjoergkShare = posixpath.join(posixpath.dirname(__file__), '../share/scan-view')
9406f32e7eSjoergkConfigPath = os.path.expanduser('~/.scanview.cfg')
9506f32e7eSjoerg
9606f32e7eSjoerg###
9706f32e7eSjoerg
9806f32e7eSjoerg__version__ = "0.1"
9906f32e7eSjoerg
10006f32e7eSjoerg__all__ = ["create_server"]
10106f32e7eSjoerg
10206f32e7eSjoergclass ReporterThread(threading.Thread):
10306f32e7eSjoerg    def __init__(self, report, reporter, parameters, server):
10406f32e7eSjoerg        threading.Thread.__init__(self)
10506f32e7eSjoerg        self.report = report
10606f32e7eSjoerg        self.server = server
10706f32e7eSjoerg        self.reporter = reporter
10806f32e7eSjoerg        self.parameters = parameters
10906f32e7eSjoerg        self.success = False
11006f32e7eSjoerg        self.status = None
11106f32e7eSjoerg
11206f32e7eSjoerg    def run(self):
11306f32e7eSjoerg        result = None
11406f32e7eSjoerg        try:
11506f32e7eSjoerg            if self.server.options.debug:
11606f32e7eSjoerg                print("%s: SERVER: submitting bug."%(sys.argv[0],), file=sys.stderr)
11706f32e7eSjoerg            self.status = self.reporter.fileReport(self.report, self.parameters)
11806f32e7eSjoerg            self.success = True
11906f32e7eSjoerg            time.sleep(3)
12006f32e7eSjoerg            if self.server.options.debug:
12106f32e7eSjoerg                print("%s: SERVER: submission complete."%(sys.argv[0],), file=sys.stderr)
12206f32e7eSjoerg        except Reporter.ReportFailure as e:
12306f32e7eSjoerg            self.status = e.value
12406f32e7eSjoerg        except Exception as e:
12506f32e7eSjoerg            s = StringIO()
12606f32e7eSjoerg            import traceback
12706f32e7eSjoerg            print('<b>Unhandled Exception</b><br><pre>', file=s)
12806f32e7eSjoerg            traceback.print_exc(file=s)
12906f32e7eSjoerg            print('</pre>', file=s)
13006f32e7eSjoerg            self.status = s.getvalue()
13106f32e7eSjoerg
13206f32e7eSjoergclass ScanViewServer(HTTPServer):
13306f32e7eSjoerg    def __init__(self, address, handler, root, reporters, options):
13406f32e7eSjoerg        HTTPServer.__init__(self, address, handler)
13506f32e7eSjoerg        self.root = root
13606f32e7eSjoerg        self.reporters = reporters
13706f32e7eSjoerg        self.options = options
13806f32e7eSjoerg        self.halted = False
13906f32e7eSjoerg        self.config = None
14006f32e7eSjoerg        self.load_config()
14106f32e7eSjoerg
14206f32e7eSjoerg    def load_config(self):
14306f32e7eSjoerg        self.config = configparser.RawConfigParser()
14406f32e7eSjoerg
14506f32e7eSjoerg        # Add defaults
14606f32e7eSjoerg        self.config.add_section('ScanView')
14706f32e7eSjoerg        for r in self.reporters:
14806f32e7eSjoerg            self.config.add_section(r.getName())
14906f32e7eSjoerg            for p in r.getParameters():
15006f32e7eSjoerg              if p.saveConfigValue():
15106f32e7eSjoerg                self.config.set(r.getName(), p.getName(), '')
15206f32e7eSjoerg
15306f32e7eSjoerg        # Ignore parse errors
15406f32e7eSjoerg        try:
15506f32e7eSjoerg            self.config.read([kConfigPath])
15606f32e7eSjoerg        except:
15706f32e7eSjoerg            pass
15806f32e7eSjoerg
15906f32e7eSjoerg        # Save on exit
16006f32e7eSjoerg        import atexit
16106f32e7eSjoerg        atexit.register(lambda: self.save_config())
16206f32e7eSjoerg
16306f32e7eSjoerg    def save_config(self):
16406f32e7eSjoerg        # Ignore errors (only called on exit).
16506f32e7eSjoerg        try:
16606f32e7eSjoerg            f = open(kConfigPath,'w')
16706f32e7eSjoerg            self.config.write(f)
16806f32e7eSjoerg            f.close()
16906f32e7eSjoerg        except:
17006f32e7eSjoerg            pass
17106f32e7eSjoerg
17206f32e7eSjoerg    def halt(self):
17306f32e7eSjoerg        self.halted = True
17406f32e7eSjoerg        if self.options.debug:
17506f32e7eSjoerg            print("%s: SERVER: halting." % (sys.argv[0],), file=sys.stderr)
17606f32e7eSjoerg
17706f32e7eSjoerg    def serve_forever(self):
17806f32e7eSjoerg        while not self.halted:
17906f32e7eSjoerg            if self.options.debug > 1:
18006f32e7eSjoerg                print("%s: SERVER: waiting..." % (sys.argv[0],), file=sys.stderr)
18106f32e7eSjoerg            try:
18206f32e7eSjoerg                self.handle_request()
18306f32e7eSjoerg            except OSError as e:
18406f32e7eSjoerg                print('OSError',e.errno)
18506f32e7eSjoerg
18606f32e7eSjoerg    def finish_request(self, request, client_address):
18706f32e7eSjoerg        if self.options.autoReload:
18806f32e7eSjoerg            import ScanView
18906f32e7eSjoerg            self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
19006f32e7eSjoerg        HTTPServer.finish_request(self, request, client_address)
19106f32e7eSjoerg
19206f32e7eSjoerg    def handle_error(self, request, client_address):
19306f32e7eSjoerg        # Ignore socket errors
19406f32e7eSjoerg        info = sys.exc_info()
19506f32e7eSjoerg        if info and isinstance(info[1], socket.error):
19606f32e7eSjoerg            if self.options.debug > 1:
19706f32e7eSjoerg                print("%s: SERVER: ignored socket error." % (sys.argv[0],), file=sys.stderr)
19806f32e7eSjoerg            return
19906f32e7eSjoerg        HTTPServer.handle_error(self, request, client_address)
20006f32e7eSjoerg
20106f32e7eSjoerg# Borrowed from Quixote, with simplifications.
20206f32e7eSjoergdef parse_query(qs, fields=None):
20306f32e7eSjoerg    if fields is None:
20406f32e7eSjoerg        fields = {}
20506f32e7eSjoerg    for chunk in (_f for _f in qs.split('&') if _f):
20606f32e7eSjoerg        if '=' not in chunk:
20706f32e7eSjoerg            name = chunk
20806f32e7eSjoerg            value = ''
20906f32e7eSjoerg        else:
21006f32e7eSjoerg            name, value = chunk.split('=', 1)
21106f32e7eSjoerg        name = unquote(name.replace('+', ' '))
21206f32e7eSjoerg        value = unquote(value.replace('+', ' '))
21306f32e7eSjoerg        item = fields.get(name)
21406f32e7eSjoerg        if item is None:
21506f32e7eSjoerg            fields[name] = [value]
21606f32e7eSjoerg        else:
21706f32e7eSjoerg            item.append(value)
21806f32e7eSjoerg    return fields
21906f32e7eSjoerg
22006f32e7eSjoergclass ScanViewRequestHandler(SimpleHTTPRequestHandler):
22106f32e7eSjoerg    server_version = "ScanViewServer/" + __version__
22206f32e7eSjoerg    dynamic_mtime = time.time()
22306f32e7eSjoerg
22406f32e7eSjoerg    def do_HEAD(self):
22506f32e7eSjoerg        try:
22606f32e7eSjoerg            SimpleHTTPRequestHandler.do_HEAD(self)
22706f32e7eSjoerg        except Exception as e:
22806f32e7eSjoerg            self.handle_exception(e)
22906f32e7eSjoerg
23006f32e7eSjoerg    def do_GET(self):
23106f32e7eSjoerg        try:
23206f32e7eSjoerg            SimpleHTTPRequestHandler.do_GET(self)
23306f32e7eSjoerg        except Exception as e:
23406f32e7eSjoerg            self.handle_exception(e)
23506f32e7eSjoerg
23606f32e7eSjoerg    def do_POST(self):
23706f32e7eSjoerg        """Serve a POST request."""
23806f32e7eSjoerg        try:
23906f32e7eSjoerg            length = self.headers.getheader('content-length') or "0"
24006f32e7eSjoerg            try:
24106f32e7eSjoerg                length = int(length)
24206f32e7eSjoerg            except:
24306f32e7eSjoerg                length = 0
24406f32e7eSjoerg            content = self.rfile.read(length)
24506f32e7eSjoerg            fields = parse_query(content)
24606f32e7eSjoerg            f = self.send_head(fields)
24706f32e7eSjoerg            if f:
24806f32e7eSjoerg                self.copyfile(f, self.wfile)
24906f32e7eSjoerg                f.close()
25006f32e7eSjoerg        except Exception as e:
25106f32e7eSjoerg            self.handle_exception(e)
25206f32e7eSjoerg
25306f32e7eSjoerg    def log_message(self, format, *args):
25406f32e7eSjoerg        if self.server.options.debug:
25506f32e7eSjoerg            sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
25606f32e7eSjoerg                             (sys.argv[0],
25706f32e7eSjoerg                              self.address_string(),
25806f32e7eSjoerg                              self.log_date_time_string(),
25906f32e7eSjoerg                              format%args))
26006f32e7eSjoerg
26106f32e7eSjoerg    def load_report(self, report):
26206f32e7eSjoerg        path = os.path.join(self.server.root, 'report-%s.html'%report)
26306f32e7eSjoerg        data = open(path).read()
26406f32e7eSjoerg        keys = {}
26506f32e7eSjoerg        for item in kBugKeyValueRE.finditer(data):
26606f32e7eSjoerg            k,v = item.groups()
26706f32e7eSjoerg            keys[k] = v
26806f32e7eSjoerg        return keys
26906f32e7eSjoerg
27006f32e7eSjoerg    def load_crashes(self):
27106f32e7eSjoerg        path = posixpath.join(self.server.root, 'index.html')
27206f32e7eSjoerg        data = open(path).read()
27306f32e7eSjoerg        problems = []
27406f32e7eSjoerg        for item in kReportCrashEntryRE.finditer(data):
27506f32e7eSjoerg            fieldData = item.group(1)
27606f32e7eSjoerg            fields = dict([i.groups() for i in
27706f32e7eSjoerg                           kReportCrashEntryKeyValueRE.finditer(fieldData)])
27806f32e7eSjoerg            problems.append(fields)
27906f32e7eSjoerg        return problems
28006f32e7eSjoerg
28106f32e7eSjoerg    def handle_exception(self, exc):
28206f32e7eSjoerg        import traceback
28306f32e7eSjoerg        s = StringIO()
28406f32e7eSjoerg        print("INTERNAL ERROR\n", file=s)
28506f32e7eSjoerg        traceback.print_exc(file=s)
28606f32e7eSjoerg        f = self.send_string(s.getvalue(), 'text/plain')
28706f32e7eSjoerg        if f:
28806f32e7eSjoerg            self.copyfile(f, self.wfile)
28906f32e7eSjoerg            f.close()
29006f32e7eSjoerg
29106f32e7eSjoerg    def get_scalar_field(self, name):
29206f32e7eSjoerg        if name in self.fields:
29306f32e7eSjoerg            return self.fields[name][0]
29406f32e7eSjoerg        else:
29506f32e7eSjoerg            return None
29606f32e7eSjoerg
29706f32e7eSjoerg    def submit_bug(self, c):
29806f32e7eSjoerg        title = self.get_scalar_field('title')
29906f32e7eSjoerg        description = self.get_scalar_field('description')
30006f32e7eSjoerg        report = self.get_scalar_field('report')
30106f32e7eSjoerg        reporterIndex = self.get_scalar_field('reporter')
30206f32e7eSjoerg        files = []
30306f32e7eSjoerg        for fileID in self.fields.get('files',[]):
30406f32e7eSjoerg            try:
30506f32e7eSjoerg                i = int(fileID)
30606f32e7eSjoerg            except:
30706f32e7eSjoerg                i = None
30806f32e7eSjoerg            if i is None or i<0 or i>=len(c.files):
30906f32e7eSjoerg                return (False, 'Invalid file ID')
31006f32e7eSjoerg            files.append(c.files[i])
31106f32e7eSjoerg
31206f32e7eSjoerg        if not title:
31306f32e7eSjoerg            return (False, "Missing title.")
31406f32e7eSjoerg        if not description:
31506f32e7eSjoerg            return (False, "Missing description.")
31606f32e7eSjoerg        try:
31706f32e7eSjoerg            reporterIndex = int(reporterIndex)
31806f32e7eSjoerg        except:
31906f32e7eSjoerg            return (False, "Invalid report method.")
32006f32e7eSjoerg
32106f32e7eSjoerg        # Get the reporter and parameters.
32206f32e7eSjoerg        reporter = self.server.reporters[reporterIndex]
32306f32e7eSjoerg        parameters = {}
32406f32e7eSjoerg        for o in reporter.getParameters():
32506f32e7eSjoerg            name = '%s_%s'%(reporter.getName(),o.getName())
32606f32e7eSjoerg            if name not in self.fields:
32706f32e7eSjoerg                return (False,
32806f32e7eSjoerg                        'Missing field "%s" for %s report method.'%(name,
32906f32e7eSjoerg                                                                    reporter.getName()))
33006f32e7eSjoerg            parameters[o.getName()] = self.get_scalar_field(name)
33106f32e7eSjoerg
33206f32e7eSjoerg        # Update config defaults.
33306f32e7eSjoerg        if report != 'None':
33406f32e7eSjoerg            self.server.config.set('ScanView', 'reporter', reporterIndex)
33506f32e7eSjoerg            for o in reporter.getParameters():
33606f32e7eSjoerg              if o.saveConfigValue():
33706f32e7eSjoerg                name = o.getName()
33806f32e7eSjoerg                self.server.config.set(reporter.getName(), name, parameters[name])
33906f32e7eSjoerg
34006f32e7eSjoerg        # Create the report.
34106f32e7eSjoerg        bug = Reporter.BugReport(title, description, files)
34206f32e7eSjoerg
34306f32e7eSjoerg        # Kick off a reporting thread.
34406f32e7eSjoerg        t = ReporterThread(bug, reporter, parameters, self.server)
34506f32e7eSjoerg        t.start()
34606f32e7eSjoerg
34706f32e7eSjoerg        # Wait for thread to die...
34806f32e7eSjoerg        while t.isAlive():
34906f32e7eSjoerg            time.sleep(.25)
35006f32e7eSjoerg        submitStatus = t.status
35106f32e7eSjoerg
35206f32e7eSjoerg        return (t.success, t.status)
35306f32e7eSjoerg
35406f32e7eSjoerg    def send_report_submit(self):
35506f32e7eSjoerg        report = self.get_scalar_field('report')
35606f32e7eSjoerg        c = self.get_report_context(report)
35706f32e7eSjoerg        if c.reportSource is None:
35806f32e7eSjoerg            reportingFor = "Report Crashes > "
35906f32e7eSjoerg            fileBug = """\
36006f32e7eSjoerg<a href="/report_crashes">File Bug</a> > """%locals()
36106f32e7eSjoerg        else:
36206f32e7eSjoerg            reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource,
36306f32e7eSjoerg                                                                   report)
36406f32e7eSjoerg            fileBug = '<a href="/report/%s">File Bug</a> > ' % report
36506f32e7eSjoerg        title = self.get_scalar_field('title')
36606f32e7eSjoerg        description = self.get_scalar_field('description')
36706f32e7eSjoerg
36806f32e7eSjoerg        res,message = self.submit_bug(c)
36906f32e7eSjoerg
37006f32e7eSjoerg        if res:
37106f32e7eSjoerg            statusClass = 'SubmitOk'
37206f32e7eSjoerg            statusName = 'Succeeded'
37306f32e7eSjoerg        else:
37406f32e7eSjoerg            statusClass = 'SubmitFail'
37506f32e7eSjoerg            statusName = 'Failed'
37606f32e7eSjoerg
37706f32e7eSjoerg        result = """
37806f32e7eSjoerg<head>
37906f32e7eSjoerg  <title>Bug Submission</title>
38006f32e7eSjoerg  <link rel="stylesheet" type="text/css" href="/scanview.css" />
38106f32e7eSjoerg</head>
38206f32e7eSjoerg<body>
38306f32e7eSjoerg<h3>
38406f32e7eSjoerg<a href="/">Summary</a> >
38506f32e7eSjoerg%(reportingFor)s
38606f32e7eSjoerg%(fileBug)s
38706f32e7eSjoergSubmit</h3>
38806f32e7eSjoerg<form name="form" action="">
38906f32e7eSjoerg<table class="form">
39006f32e7eSjoerg<tr><td>
39106f32e7eSjoerg<table class="form_group">
39206f32e7eSjoerg<tr>
39306f32e7eSjoerg  <td class="form_clabel">Title:</td>
39406f32e7eSjoerg  <td class="form_value">
39506f32e7eSjoerg    <input type="text" name="title" size="50" value="%(title)s" disabled>
39606f32e7eSjoerg  </td>
39706f32e7eSjoerg</tr>
39806f32e7eSjoerg<tr>
39906f32e7eSjoerg  <td class="form_label">Description:</td>
40006f32e7eSjoerg  <td class="form_value">
40106f32e7eSjoerg<textarea rows="10" cols="80" name="description" disabled>
40206f32e7eSjoerg%(description)s
40306f32e7eSjoerg</textarea>
40406f32e7eSjoerg  </td>
40506f32e7eSjoerg</table>
40606f32e7eSjoerg</td></tr>
40706f32e7eSjoerg</table>
40806f32e7eSjoerg</form>
40906f32e7eSjoerg<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
41006f32e7eSjoerg%(message)s
41106f32e7eSjoerg<p>
41206f32e7eSjoerg<hr>
41306f32e7eSjoerg<a href="/">Return to Summary</a>
41406f32e7eSjoerg</body>
41506f32e7eSjoerg</html>"""%locals()
41606f32e7eSjoerg        return self.send_string(result)
41706f32e7eSjoerg
41806f32e7eSjoerg    def send_open_report(self, report):
41906f32e7eSjoerg        try:
42006f32e7eSjoerg            keys = self.load_report(report)
42106f32e7eSjoerg        except IOError:
42206f32e7eSjoerg            return self.send_error(400, 'Invalid report.')
42306f32e7eSjoerg
42406f32e7eSjoerg        file = keys.get('FILE')
42506f32e7eSjoerg        if not file or not posixpath.exists(file):
42606f32e7eSjoerg            return self.send_error(400, 'File does not exist: "%s"' % file)
42706f32e7eSjoerg
42806f32e7eSjoerg        import startfile
42906f32e7eSjoerg        if self.server.options.debug:
43006f32e7eSjoerg            print('%s: SERVER: opening "%s"'%(sys.argv[0],
43106f32e7eSjoerg                                                            file), file=sys.stderr)
43206f32e7eSjoerg
43306f32e7eSjoerg        status = startfile.open(file)
43406f32e7eSjoerg        if status:
43506f32e7eSjoerg            res = 'Opened: "%s"' % file
43606f32e7eSjoerg        else:
43706f32e7eSjoerg            res = 'Open failed: "%s"' % file
43806f32e7eSjoerg
43906f32e7eSjoerg        return self.send_string(res, 'text/plain')
44006f32e7eSjoerg
44106f32e7eSjoerg    def get_report_context(self, report):
44206f32e7eSjoerg        class Context(object):
44306f32e7eSjoerg            pass
44406f32e7eSjoerg        if report is None or report == 'None':
44506f32e7eSjoerg            data = self.load_crashes()
44606f32e7eSjoerg            # Don't allow empty reports.
44706f32e7eSjoerg            if not data:
44806f32e7eSjoerg                raise ValueError('No crashes detected!')
44906f32e7eSjoerg            c = Context()
45006f32e7eSjoerg            c.title = 'clang static analyzer failures'
45106f32e7eSjoerg
45206f32e7eSjoerg            stderrSummary = ""
45306f32e7eSjoerg            for item in data:
45406f32e7eSjoerg                if 'stderr' in item:
45506f32e7eSjoerg                    path = posixpath.join(self.server.root, item['stderr'])
45606f32e7eSjoerg                    if os.path.exists(path):
45706f32e7eSjoerg                        lns = itertools.islice(open(path), 0, 10)
45806f32e7eSjoerg                        stderrSummary += '%s\n--\n%s' % (item.get('src',
45906f32e7eSjoerg                                                                  '<unknown>'),
46006f32e7eSjoerg                                                         ''.join(lns))
46106f32e7eSjoerg
46206f32e7eSjoerg            c.description = """\
46306f32e7eSjoergThe clang static analyzer failed on these inputs:
46406f32e7eSjoerg%s
46506f32e7eSjoerg
46606f32e7eSjoergSTDERR Summary
46706f32e7eSjoerg--------------
46806f32e7eSjoerg%s
46906f32e7eSjoerg""" % ('\n'.join([item.get('src','<unknown>') for item in data]),
47006f32e7eSjoerg       stderrSummary)
47106f32e7eSjoerg            c.reportSource = None
47206f32e7eSjoerg            c.navMarkup = "Report Crashes > "
47306f32e7eSjoerg            c.files = []
47406f32e7eSjoerg            for item in data:
47506f32e7eSjoerg                c.files.append(item.get('src',''))
47606f32e7eSjoerg                c.files.append(posixpath.join(self.server.root,
47706f32e7eSjoerg                                              item.get('file','')))
47806f32e7eSjoerg                c.files.append(posixpath.join(self.server.root,
47906f32e7eSjoerg                                              item.get('clangfile','')))
48006f32e7eSjoerg                c.files.append(posixpath.join(self.server.root,
48106f32e7eSjoerg                                              item.get('stderr','')))
48206f32e7eSjoerg                c.files.append(posixpath.join(self.server.root,
48306f32e7eSjoerg                                              item.get('info','')))
48406f32e7eSjoerg            # Just in case something failed, ignore files which don't
48506f32e7eSjoerg            # exist.
48606f32e7eSjoerg            c.files = [f for f in c.files
48706f32e7eSjoerg                       if os.path.exists(f) and os.path.isfile(f)]
48806f32e7eSjoerg        else:
48906f32e7eSjoerg            # Check that this is a valid report.
49006f32e7eSjoerg            path = posixpath.join(self.server.root, 'report-%s.html' % report)
49106f32e7eSjoerg            if not posixpath.exists(path):
49206f32e7eSjoerg                raise ValueError('Invalid report ID')
49306f32e7eSjoerg            keys = self.load_report(report)
49406f32e7eSjoerg            c = Context()
49506f32e7eSjoerg            c.title = keys.get('DESC','clang error (unrecognized')
49606f32e7eSjoerg            c.description = """\
49706f32e7eSjoergBug reported by the clang static analyzer.
49806f32e7eSjoerg
49906f32e7eSjoergDescription: %s
50006f32e7eSjoergFile: %s
50106f32e7eSjoergLine: %s
50206f32e7eSjoerg"""%(c.title, keys.get('FILE','<unknown>'), keys.get('LINE', '<unknown>'))
50306f32e7eSjoerg            c.reportSource = 'report-%s.html' % report
50406f32e7eSjoerg            c.navMarkup = """<a href="/%s">Report %s</a> > """ % (c.reportSource,
50506f32e7eSjoerg                                                                  report)
50606f32e7eSjoerg
50706f32e7eSjoerg            c.files = [path]
50806f32e7eSjoerg        return c
50906f32e7eSjoerg
51006f32e7eSjoerg    def send_report(self, report, configOverrides=None):
51106f32e7eSjoerg        def getConfigOption(section, field):
51206f32e7eSjoerg            if (configOverrides is not None and
51306f32e7eSjoerg                section in configOverrides and
51406f32e7eSjoerg                field in configOverrides[section]):
51506f32e7eSjoerg                return configOverrides[section][field]
51606f32e7eSjoerg            return self.server.config.get(section, field)
51706f32e7eSjoerg
51806f32e7eSjoerg        # report is None is used for crashes
51906f32e7eSjoerg        try:
52006f32e7eSjoerg            c = self.get_report_context(report)
52106f32e7eSjoerg        except ValueError as e:
52206f32e7eSjoerg            return self.send_error(400, e.message)
52306f32e7eSjoerg
52406f32e7eSjoerg        title = c.title
52506f32e7eSjoerg        description= c.description
52606f32e7eSjoerg        reportingFor = c.navMarkup
52706f32e7eSjoerg        if c.reportSource is None:
52806f32e7eSjoerg            extraIFrame = ""
52906f32e7eSjoerg        else:
53006f32e7eSjoerg            extraIFrame = """\
53106f32e7eSjoerg<iframe src="/%s" width="100%%" height="40%%"
53206f32e7eSjoerg        scrolling="auto" frameborder="1">
53306f32e7eSjoerg  <a href="/%s">View Bug Report</a>
53406f32e7eSjoerg</iframe>""" % (c.reportSource, c.reportSource)
53506f32e7eSjoerg
53606f32e7eSjoerg        reporterSelections = []
53706f32e7eSjoerg        reporterOptions = []
53806f32e7eSjoerg
53906f32e7eSjoerg        try:
54006f32e7eSjoerg            active = int(getConfigOption('ScanView','reporter'))
54106f32e7eSjoerg        except:
54206f32e7eSjoerg            active = 0
54306f32e7eSjoerg        for i,r in enumerate(self.server.reporters):
54406f32e7eSjoerg            selected = (i == active)
54506f32e7eSjoerg            if selected:
54606f32e7eSjoerg                selectedStr = ' selected'
54706f32e7eSjoerg            else:
54806f32e7eSjoerg                selectedStr = ''
54906f32e7eSjoerg            reporterSelections.append('<option value="%d"%s>%s</option>'%(i,selectedStr,r.getName()))
55006f32e7eSjoerg            options = '\n'.join([ o.getHTML(r,title,getConfigOption) for o in r.getParameters()])
55106f32e7eSjoerg            display = ('none','')[selected]
55206f32e7eSjoerg            reporterOptions.append("""\
55306f32e7eSjoerg<tr id="%sReporterOptions" style="display:%s">
55406f32e7eSjoerg  <td class="form_label">%s Options</td>
55506f32e7eSjoerg  <td class="form_value">
55606f32e7eSjoerg    <table class="form_inner_group">
55706f32e7eSjoerg%s
55806f32e7eSjoerg    </table>
55906f32e7eSjoerg  </td>
56006f32e7eSjoerg</tr>
56106f32e7eSjoerg"""%(r.getName(),display,r.getName(),options))
56206f32e7eSjoerg        reporterSelections = '\n'.join(reporterSelections)
56306f32e7eSjoerg        reporterOptionsDivs = '\n'.join(reporterOptions)
56406f32e7eSjoerg        reportersArray = '[%s]'%(','.join([repr(r.getName()) for r in self.server.reporters]))
56506f32e7eSjoerg
56606f32e7eSjoerg        if c.files:
56706f32e7eSjoerg            fieldSize = min(5, len(c.files))
56806f32e7eSjoerg            attachFileOptions = '\n'.join(["""\
56906f32e7eSjoerg<option value="%d" selected>%s</option>""" % (i,v) for i,v in enumerate(c.files)])
57006f32e7eSjoerg            attachFileRow = """\
57106f32e7eSjoerg<tr>
57206f32e7eSjoerg  <td class="form_label">Attach:</td>
57306f32e7eSjoerg  <td class="form_value">
57406f32e7eSjoerg<select style="width:100%%" name="files" multiple size=%d>
57506f32e7eSjoerg%s
57606f32e7eSjoerg</select>
57706f32e7eSjoerg  </td>
57806f32e7eSjoerg</tr>
57906f32e7eSjoerg""" % (min(5, len(c.files)), attachFileOptions)
58006f32e7eSjoerg        else:
58106f32e7eSjoerg            attachFileRow = ""
58206f32e7eSjoerg
58306f32e7eSjoerg        result = """<html>
58406f32e7eSjoerg<head>
58506f32e7eSjoerg  <title>File Bug</title>
58606f32e7eSjoerg  <link rel="stylesheet" type="text/css" href="/scanview.css" />
58706f32e7eSjoerg</head>
58806f32e7eSjoerg<script language="javascript" type="text/javascript">
58906f32e7eSjoergvar reporters = %(reportersArray)s;
59006f32e7eSjoergfunction updateReporterOptions() {
59106f32e7eSjoerg  index = document.getElementById('reporter').selectedIndex;
59206f32e7eSjoerg  for (var i=0; i < reporters.length; ++i) {
59306f32e7eSjoerg    o = document.getElementById(reporters[i] + "ReporterOptions");
59406f32e7eSjoerg    if (i == index) {
59506f32e7eSjoerg      o.style.display = "";
59606f32e7eSjoerg    } else {
59706f32e7eSjoerg      o.style.display = "none";
59806f32e7eSjoerg    }
59906f32e7eSjoerg  }
60006f32e7eSjoerg}
60106f32e7eSjoerg</script>
60206f32e7eSjoerg<body onLoad="updateReporterOptions()">
60306f32e7eSjoerg<h3>
60406f32e7eSjoerg<a href="/">Summary</a> >
60506f32e7eSjoerg%(reportingFor)s
60606f32e7eSjoergFile Bug</h3>
60706f32e7eSjoerg<form name="form" action="/report_submit" method="post">
60806f32e7eSjoerg<input type="hidden" name="report" value="%(report)s">
60906f32e7eSjoerg
61006f32e7eSjoerg<table class="form">
61106f32e7eSjoerg<tr><td>
61206f32e7eSjoerg<table class="form_group">
61306f32e7eSjoerg<tr>
61406f32e7eSjoerg  <td class="form_clabel">Title:</td>
61506f32e7eSjoerg  <td class="form_value">
61606f32e7eSjoerg    <input type="text" name="title" size="50" value="%(title)s">
61706f32e7eSjoerg  </td>
61806f32e7eSjoerg</tr>
61906f32e7eSjoerg<tr>
62006f32e7eSjoerg  <td class="form_label">Description:</td>
62106f32e7eSjoerg  <td class="form_value">
62206f32e7eSjoerg<textarea rows="10" cols="80" name="description">
62306f32e7eSjoerg%(description)s
62406f32e7eSjoerg</textarea>
62506f32e7eSjoerg  </td>
62606f32e7eSjoerg</tr>
62706f32e7eSjoerg
62806f32e7eSjoerg%(attachFileRow)s
62906f32e7eSjoerg
63006f32e7eSjoerg</table>
63106f32e7eSjoerg<br>
63206f32e7eSjoerg<table class="form_group">
63306f32e7eSjoerg<tr>
63406f32e7eSjoerg  <td class="form_clabel">Method:</td>
63506f32e7eSjoerg  <td class="form_value">
63606f32e7eSjoerg    <select id="reporter" name="reporter" onChange="updateReporterOptions()">
63706f32e7eSjoerg    %(reporterSelections)s
63806f32e7eSjoerg    </select>
63906f32e7eSjoerg  </td>
64006f32e7eSjoerg</tr>
64106f32e7eSjoerg%(reporterOptionsDivs)s
64206f32e7eSjoerg</table>
64306f32e7eSjoerg<br>
64406f32e7eSjoerg</td></tr>
64506f32e7eSjoerg<tr><td class="form_submit">
64606f32e7eSjoerg  <input align="right" type="submit" name="Submit" value="Submit">
64706f32e7eSjoerg</td></tr>
64806f32e7eSjoerg</table>
64906f32e7eSjoerg</form>
65006f32e7eSjoerg
65106f32e7eSjoerg%(extraIFrame)s
65206f32e7eSjoerg
65306f32e7eSjoerg</body>
65406f32e7eSjoerg</html>"""%locals()
65506f32e7eSjoerg
65606f32e7eSjoerg        return self.send_string(result)
65706f32e7eSjoerg
65806f32e7eSjoerg    def send_head(self, fields=None):
65906f32e7eSjoerg        if (self.server.options.onlyServeLocal and
66006f32e7eSjoerg            self.client_address[0] != '127.0.0.1'):
66106f32e7eSjoerg            return self.send_error(401, 'Unauthorized host.')
66206f32e7eSjoerg
66306f32e7eSjoerg        if fields is None:
66406f32e7eSjoerg            fields = {}
66506f32e7eSjoerg        self.fields = fields
66606f32e7eSjoerg
66706f32e7eSjoerg        o = urlparse(self.path)
66806f32e7eSjoerg        self.fields = parse_query(o.query, fields)
66906f32e7eSjoerg        path = posixpath.normpath(unquote(o.path))
67006f32e7eSjoerg
67106f32e7eSjoerg        # Split the components and strip the root prefix.
67206f32e7eSjoerg        components = path.split('/')[1:]
67306f32e7eSjoerg
67406f32e7eSjoerg        # Special case some top-level entries.
67506f32e7eSjoerg        if components:
67606f32e7eSjoerg            name = components[0]
67706f32e7eSjoerg            if len(components)==2:
67806f32e7eSjoerg                if name=='report':
67906f32e7eSjoerg                    return self.send_report(components[1])
68006f32e7eSjoerg                elif name=='open':
68106f32e7eSjoerg                    return self.send_open_report(components[1])
68206f32e7eSjoerg            elif len(components)==1:
68306f32e7eSjoerg                if name=='quit':
68406f32e7eSjoerg                    self.server.halt()
68506f32e7eSjoerg                    return self.send_string('Goodbye.', 'text/plain')
68606f32e7eSjoerg                elif name=='report_submit':
68706f32e7eSjoerg                    return self.send_report_submit()
68806f32e7eSjoerg                elif name=='report_crashes':
68906f32e7eSjoerg                    overrides = { 'ScanView' : {},
69006f32e7eSjoerg                                  'Radar' : {},
69106f32e7eSjoerg                                  'Email' : {} }
69206f32e7eSjoerg                    for i,r in enumerate(self.server.reporters):
69306f32e7eSjoerg                        if r.getName() == 'Radar':
69406f32e7eSjoerg                            overrides['ScanView']['reporter'] = i
69506f32e7eSjoerg                            break
69606f32e7eSjoerg                    overrides['Radar']['Component'] = 'llvm - checker'
69706f32e7eSjoerg                    overrides['Radar']['Component Version'] = 'X'
69806f32e7eSjoerg                    return self.send_report(None, overrides)
69906f32e7eSjoerg                elif name=='favicon.ico':
70006f32e7eSjoerg                    return self.send_path(posixpath.join(kShare,'bugcatcher.ico'))
70106f32e7eSjoerg
70206f32e7eSjoerg        # Match directory entries.
70306f32e7eSjoerg        if components[-1] == '':
70406f32e7eSjoerg            components[-1] = 'index.html'
70506f32e7eSjoerg
70606f32e7eSjoerg        relpath = '/'.join(components)
70706f32e7eSjoerg        path = posixpath.join(self.server.root, relpath)
70806f32e7eSjoerg
70906f32e7eSjoerg        if self.server.options.debug > 1:
71006f32e7eSjoerg            print('%s: SERVER: sending path "%s"'%(sys.argv[0],
71106f32e7eSjoerg                                                                 path), file=sys.stderr)
71206f32e7eSjoerg        return self.send_path(path)
71306f32e7eSjoerg
71406f32e7eSjoerg    def send_404(self):
71506f32e7eSjoerg        self.send_error(404, "File not found")
71606f32e7eSjoerg        return None
71706f32e7eSjoerg
71806f32e7eSjoerg    def send_path(self, path):
71906f32e7eSjoerg        # If the requested path is outside the root directory, do not open it
72006f32e7eSjoerg        rel = os.path.abspath(path)
72106f32e7eSjoerg        if not rel.startswith(os.path.abspath(self.server.root)):
72206f32e7eSjoerg          return self.send_404()
72306f32e7eSjoerg
72406f32e7eSjoerg        ctype = self.guess_type(path)
72506f32e7eSjoerg        if ctype.startswith('text/'):
72606f32e7eSjoerg            # Patch file instead
72706f32e7eSjoerg            return self.send_patched_file(path, ctype)
72806f32e7eSjoerg        else:
72906f32e7eSjoerg            mode = 'rb'
73006f32e7eSjoerg        try:
73106f32e7eSjoerg            f = open(path, mode)
73206f32e7eSjoerg        except IOError:
73306f32e7eSjoerg            return self.send_404()
73406f32e7eSjoerg        return self.send_file(f, ctype)
73506f32e7eSjoerg
73606f32e7eSjoerg    def send_file(self, f, ctype):
73706f32e7eSjoerg        # Patch files to add links, but skip binary files.
73806f32e7eSjoerg        self.send_response(200)
73906f32e7eSjoerg        self.send_header("Content-type", ctype)
74006f32e7eSjoerg        fs = os.fstat(f.fileno())
74106f32e7eSjoerg        self.send_header("Content-Length", str(fs[6]))
74206f32e7eSjoerg        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
74306f32e7eSjoerg        self.end_headers()
74406f32e7eSjoerg        return f
74506f32e7eSjoerg
74606f32e7eSjoerg    def send_string(self, s, ctype='text/html', headers=True, mtime=None):
747*13fbcb42Sjoerg        encoded_s = s.encode('utf-8')
74806f32e7eSjoerg        if headers:
74906f32e7eSjoerg            self.send_response(200)
75006f32e7eSjoerg            self.send_header("Content-type", ctype)
75106f32e7eSjoerg            self.send_header("Content-Length", str(len(encoded_s)))
75206f32e7eSjoerg            if mtime is None:
75306f32e7eSjoerg                mtime = self.dynamic_mtime
75406f32e7eSjoerg            self.send_header("Last-Modified", self.date_time_string(mtime))
75506f32e7eSjoerg            self.end_headers()
75606f32e7eSjoerg        return BytesIO(encoded_s)
75706f32e7eSjoerg
75806f32e7eSjoerg    def send_patched_file(self, path, ctype):
75906f32e7eSjoerg        # Allow a very limited set of variables. This is pretty gross.
76006f32e7eSjoerg        variables = {}
76106f32e7eSjoerg        variables['report'] = ''
76206f32e7eSjoerg        m = kReportFileRE.match(path)
76306f32e7eSjoerg        if m:
76406f32e7eSjoerg            variables['report'] = m.group(2)
76506f32e7eSjoerg
76606f32e7eSjoerg        try:
76706f32e7eSjoerg            f = open(path,'rb')
76806f32e7eSjoerg        except IOError:
76906f32e7eSjoerg            return self.send_404()
77006f32e7eSjoerg        fs = os.fstat(f.fileno())
77106f32e7eSjoerg        data = f.read().decode('utf-8')
77206f32e7eSjoerg        for a,b in kReportReplacements:
77306f32e7eSjoerg            data = a.sub(b % variables, data)
77406f32e7eSjoerg        return self.send_string(data, ctype, mtime=fs.st_mtime)
77506f32e7eSjoerg
77606f32e7eSjoerg
77706f32e7eSjoergdef create_server(address, options, root):
77806f32e7eSjoerg    import Reporter
77906f32e7eSjoerg
78006f32e7eSjoerg    reporters = Reporter.getReporters()
78106f32e7eSjoerg
78206f32e7eSjoerg    return ScanViewServer(address, ScanViewRequestHandler,
78306f32e7eSjoerg                          root,
78406f32e7eSjoerg                          reporters,
78506f32e7eSjoerg                          options)
786