xref: /openbsd-src/gnu/llvm/clang/tools/scan-view/share/ScanView.py (revision a9ac8606c53d55cee9c3a39778b249c51df111ef)
1e5dd7070Spatrickfrom __future__ import print_function
2e5dd7070Spatricktry:
3e5dd7070Spatrick    from http.server import HTTPServer, SimpleHTTPRequestHandler
4e5dd7070Spatrickexcept ImportError:
5e5dd7070Spatrick    from BaseHTTPServer import HTTPServer
6e5dd7070Spatrick    from SimpleHTTPServer import SimpleHTTPRequestHandler
7e5dd7070Spatrickimport os
8e5dd7070Spatrickimport sys
9e5dd7070Spatricktry:
10e5dd7070Spatrick    from urlparse import urlparse
11e5dd7070Spatrick    from urllib import unquote
12e5dd7070Spatrickexcept ImportError:
13e5dd7070Spatrick    from urllib.parse import urlparse, unquote
14e5dd7070Spatrick
15e5dd7070Spatrickimport posixpath
16e5dd7070Spatrick
17e5dd7070Spatrickif sys.version_info.major >= 3:
18e5dd7070Spatrick    from io import StringIO, BytesIO
19e5dd7070Spatrickelse:
20e5dd7070Spatrick    from io import BytesIO, BytesIO as StringIO
21e5dd7070Spatrick
22e5dd7070Spatrickimport re
23e5dd7070Spatrickimport shutil
24e5dd7070Spatrickimport threading
25e5dd7070Spatrickimport time
26e5dd7070Spatrickimport socket
27e5dd7070Spatrickimport itertools
28e5dd7070Spatrick
29e5dd7070Spatrickimport Reporter
30e5dd7070Spatricktry:
31e5dd7070Spatrick    import configparser
32e5dd7070Spatrickexcept ImportError:
33e5dd7070Spatrick    import ConfigParser as configparser
34e5dd7070Spatrick
35e5dd7070Spatrick###
36e5dd7070Spatrick# Various patterns matched or replaced by server.
37e5dd7070Spatrick
38e5dd7070SpatrickkReportFileRE = re.compile('(.*/)?report-(.*)\\.html')
39e5dd7070Spatrick
40e5dd7070SpatrickkBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
41e5dd7070Spatrick
42e5dd7070Spatrick#  <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" -->
43e5dd7070Spatrick
44e5dd7070SpatrickkReportCrashEntryRE = re.compile('<!-- REPORTPROBLEM (.*?)-->')
45e5dd7070SpatrickkReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"')
46e5dd7070Spatrick
47e5dd7070SpatrickkReportReplacements = []
48e5dd7070Spatrick
49e5dd7070Spatrick# Add custom javascript.
50e5dd7070SpatrickkReportReplacements.append((re.compile('<!-- SUMMARYENDHEAD -->'), """\
51e5dd7070Spatrick<script language="javascript" type="text/javascript">
52e5dd7070Spatrickfunction load(url) {
53e5dd7070Spatrick  if (window.XMLHttpRequest) {
54e5dd7070Spatrick    req = new XMLHttpRequest();
55e5dd7070Spatrick  } else if (window.ActiveXObject) {
56e5dd7070Spatrick    req = new ActiveXObject("Microsoft.XMLHTTP");
57e5dd7070Spatrick  }
58e5dd7070Spatrick  if (req != undefined) {
59e5dd7070Spatrick    req.open("GET", url, true);
60e5dd7070Spatrick    req.send("");
61e5dd7070Spatrick  }
62e5dd7070Spatrick}
63e5dd7070Spatrick</script>"""))
64e5dd7070Spatrick
65e5dd7070Spatrick# Insert additional columns.
66e5dd7070SpatrickkReportReplacements.append((re.compile('<!-- REPORTBUGCOL -->'),
67e5dd7070Spatrick                            '<td></td><td></td>'))
68e5dd7070Spatrick
69e5dd7070Spatrick# Insert report bug and open file links.
70e5dd7070SpatrickkReportReplacements.append((re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'),
71e5dd7070Spatrick                            ('<td class="Button"><a href="report/\\1">Report Bug</a></td>' +
72e5dd7070Spatrick                             '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>')))
73e5dd7070Spatrick
74e5dd7070SpatrickkReportReplacements.append((re.compile('<!-- REPORTHEADER -->'),
75e5dd7070Spatrick                                       '<h3><a href="/">Summary</a> > Report %(report)s</h3>'))
76e5dd7070Spatrick
77e5dd7070SpatrickkReportReplacements.append((re.compile('<!-- REPORTSUMMARYEXTRA -->'),
78e5dd7070Spatrick                            '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>'))
79e5dd7070Spatrick
80e5dd7070Spatrick# Insert report crashes link.
81e5dd7070Spatrick
82e5dd7070Spatrick# Disabled for the time being until we decide exactly when this should
83e5dd7070Spatrick# be enabled. Also the radar reporter needs to be fixed to report
84e5dd7070Spatrick# multiple files.
85e5dd7070Spatrick
86e5dd7070Spatrick#kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'),
87e5dd7070Spatrick#                            '<br>These files will automatically be attached to ' +
88e5dd7070Spatrick#                            'reports filed here: <a href="report_crashes">Report Crashes</a>.'))
89e5dd7070Spatrick
90e5dd7070Spatrick###
91e5dd7070Spatrick# Other simple parameters
92e5dd7070Spatrick
93e5dd7070SpatrickkShare = posixpath.join(posixpath.dirname(__file__), '../share/scan-view')
94e5dd7070SpatrickkConfigPath = os.path.expanduser('~/.scanview.cfg')
95e5dd7070Spatrick
96e5dd7070Spatrick###
97e5dd7070Spatrick
98e5dd7070Spatrick__version__ = "0.1"
99e5dd7070Spatrick
100e5dd7070Spatrick__all__ = ["create_server"]
101e5dd7070Spatrick
102e5dd7070Spatrickclass ReporterThread(threading.Thread):
103e5dd7070Spatrick    def __init__(self, report, reporter, parameters, server):
104e5dd7070Spatrick        threading.Thread.__init__(self)
105e5dd7070Spatrick        self.report = report
106e5dd7070Spatrick        self.server = server
107e5dd7070Spatrick        self.reporter = reporter
108e5dd7070Spatrick        self.parameters = parameters
109e5dd7070Spatrick        self.success = False
110e5dd7070Spatrick        self.status = None
111e5dd7070Spatrick
112e5dd7070Spatrick    def run(self):
113e5dd7070Spatrick        result = None
114e5dd7070Spatrick        try:
115e5dd7070Spatrick            if self.server.options.debug:
116e5dd7070Spatrick                print("%s: SERVER: submitting bug."%(sys.argv[0],), file=sys.stderr)
117e5dd7070Spatrick            self.status = self.reporter.fileReport(self.report, self.parameters)
118e5dd7070Spatrick            self.success = True
119e5dd7070Spatrick            time.sleep(3)
120e5dd7070Spatrick            if self.server.options.debug:
121e5dd7070Spatrick                print("%s: SERVER: submission complete."%(sys.argv[0],), file=sys.stderr)
122e5dd7070Spatrick        except Reporter.ReportFailure as e:
123e5dd7070Spatrick            self.status = e.value
124e5dd7070Spatrick        except Exception as e:
125e5dd7070Spatrick            s = StringIO()
126e5dd7070Spatrick            import traceback
127e5dd7070Spatrick            print('<b>Unhandled Exception</b><br><pre>', file=s)
128e5dd7070Spatrick            traceback.print_exc(file=s)
129e5dd7070Spatrick            print('</pre>', file=s)
130e5dd7070Spatrick            self.status = s.getvalue()
131e5dd7070Spatrick
132e5dd7070Spatrickclass ScanViewServer(HTTPServer):
133e5dd7070Spatrick    def __init__(self, address, handler, root, reporters, options):
134e5dd7070Spatrick        HTTPServer.__init__(self, address, handler)
135e5dd7070Spatrick        self.root = root
136e5dd7070Spatrick        self.reporters = reporters
137e5dd7070Spatrick        self.options = options
138e5dd7070Spatrick        self.halted = False
139e5dd7070Spatrick        self.config = None
140e5dd7070Spatrick        self.load_config()
141e5dd7070Spatrick
142e5dd7070Spatrick    def load_config(self):
143e5dd7070Spatrick        self.config = configparser.RawConfigParser()
144e5dd7070Spatrick
145e5dd7070Spatrick        # Add defaults
146e5dd7070Spatrick        self.config.add_section('ScanView')
147e5dd7070Spatrick        for r in self.reporters:
148e5dd7070Spatrick            self.config.add_section(r.getName())
149e5dd7070Spatrick            for p in r.getParameters():
150e5dd7070Spatrick              if p.saveConfigValue():
151e5dd7070Spatrick                self.config.set(r.getName(), p.getName(), '')
152e5dd7070Spatrick
153e5dd7070Spatrick        # Ignore parse errors
154e5dd7070Spatrick        try:
155e5dd7070Spatrick            self.config.read([kConfigPath])
156e5dd7070Spatrick        except:
157e5dd7070Spatrick            pass
158e5dd7070Spatrick
159e5dd7070Spatrick        # Save on exit
160e5dd7070Spatrick        import atexit
161e5dd7070Spatrick        atexit.register(lambda: self.save_config())
162e5dd7070Spatrick
163e5dd7070Spatrick    def save_config(self):
164e5dd7070Spatrick        # Ignore errors (only called on exit).
165e5dd7070Spatrick        try:
166e5dd7070Spatrick            f = open(kConfigPath,'w')
167e5dd7070Spatrick            self.config.write(f)
168e5dd7070Spatrick            f.close()
169e5dd7070Spatrick        except:
170e5dd7070Spatrick            pass
171e5dd7070Spatrick
172e5dd7070Spatrick    def halt(self):
173e5dd7070Spatrick        self.halted = True
174e5dd7070Spatrick        if self.options.debug:
175e5dd7070Spatrick            print("%s: SERVER: halting." % (sys.argv[0],), file=sys.stderr)
176e5dd7070Spatrick
177e5dd7070Spatrick    def serve_forever(self):
178e5dd7070Spatrick        while not self.halted:
179e5dd7070Spatrick            if self.options.debug > 1:
180e5dd7070Spatrick                print("%s: SERVER: waiting..." % (sys.argv[0],), file=sys.stderr)
181e5dd7070Spatrick            try:
182e5dd7070Spatrick                self.handle_request()
183e5dd7070Spatrick            except OSError as e:
184e5dd7070Spatrick                print('OSError',e.errno)
185e5dd7070Spatrick
186e5dd7070Spatrick    def finish_request(self, request, client_address):
187e5dd7070Spatrick        if self.options.autoReload:
188e5dd7070Spatrick            import ScanView
189e5dd7070Spatrick            self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
190e5dd7070Spatrick        HTTPServer.finish_request(self, request, client_address)
191e5dd7070Spatrick
192e5dd7070Spatrick    def handle_error(self, request, client_address):
193e5dd7070Spatrick        # Ignore socket errors
194e5dd7070Spatrick        info = sys.exc_info()
195e5dd7070Spatrick        if info and isinstance(info[1], socket.error):
196e5dd7070Spatrick            if self.options.debug > 1:
197e5dd7070Spatrick                print("%s: SERVER: ignored socket error." % (sys.argv[0],), file=sys.stderr)
198e5dd7070Spatrick            return
199e5dd7070Spatrick        HTTPServer.handle_error(self, request, client_address)
200e5dd7070Spatrick
201e5dd7070Spatrick# Borrowed from Quixote, with simplifications.
202e5dd7070Spatrickdef parse_query(qs, fields=None):
203e5dd7070Spatrick    if fields is None:
204e5dd7070Spatrick        fields = {}
205e5dd7070Spatrick    for chunk in (_f for _f in qs.split('&') if _f):
206e5dd7070Spatrick        if '=' not in chunk:
207e5dd7070Spatrick            name = chunk
208e5dd7070Spatrick            value = ''
209e5dd7070Spatrick        else:
210e5dd7070Spatrick            name, value = chunk.split('=', 1)
211e5dd7070Spatrick        name = unquote(name.replace('+', ' '))
212e5dd7070Spatrick        value = unquote(value.replace('+', ' '))
213e5dd7070Spatrick        item = fields.get(name)
214e5dd7070Spatrick        if item is None:
215e5dd7070Spatrick            fields[name] = [value]
216e5dd7070Spatrick        else:
217e5dd7070Spatrick            item.append(value)
218e5dd7070Spatrick    return fields
219e5dd7070Spatrick
220e5dd7070Spatrickclass ScanViewRequestHandler(SimpleHTTPRequestHandler):
221e5dd7070Spatrick    server_version = "ScanViewServer/" + __version__
222e5dd7070Spatrick    dynamic_mtime = time.time()
223e5dd7070Spatrick
224e5dd7070Spatrick    def do_HEAD(self):
225e5dd7070Spatrick        try:
226e5dd7070Spatrick            SimpleHTTPRequestHandler.do_HEAD(self)
227e5dd7070Spatrick        except Exception as e:
228e5dd7070Spatrick            self.handle_exception(e)
229e5dd7070Spatrick
230e5dd7070Spatrick    def do_GET(self):
231e5dd7070Spatrick        try:
232e5dd7070Spatrick            SimpleHTTPRequestHandler.do_GET(self)
233e5dd7070Spatrick        except Exception as e:
234e5dd7070Spatrick            self.handle_exception(e)
235e5dd7070Spatrick
236e5dd7070Spatrick    def do_POST(self):
237e5dd7070Spatrick        """Serve a POST request."""
238e5dd7070Spatrick        try:
239e5dd7070Spatrick            length = self.headers.getheader('content-length') or "0"
240e5dd7070Spatrick            try:
241e5dd7070Spatrick                length = int(length)
242e5dd7070Spatrick            except:
243e5dd7070Spatrick                length = 0
244e5dd7070Spatrick            content = self.rfile.read(length)
245e5dd7070Spatrick            fields = parse_query(content)
246e5dd7070Spatrick            f = self.send_head(fields)
247e5dd7070Spatrick            if f:
248e5dd7070Spatrick                self.copyfile(f, self.wfile)
249e5dd7070Spatrick                f.close()
250e5dd7070Spatrick        except Exception as e:
251e5dd7070Spatrick            self.handle_exception(e)
252e5dd7070Spatrick
253e5dd7070Spatrick    def log_message(self, format, *args):
254e5dd7070Spatrick        if self.server.options.debug:
255e5dd7070Spatrick            sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
256e5dd7070Spatrick                             (sys.argv[0],
257e5dd7070Spatrick                              self.address_string(),
258e5dd7070Spatrick                              self.log_date_time_string(),
259e5dd7070Spatrick                              format%args))
260e5dd7070Spatrick
261e5dd7070Spatrick    def load_report(self, report):
262e5dd7070Spatrick        path = os.path.join(self.server.root, 'report-%s.html'%report)
263e5dd7070Spatrick        data = open(path).read()
264e5dd7070Spatrick        keys = {}
265e5dd7070Spatrick        for item in kBugKeyValueRE.finditer(data):
266e5dd7070Spatrick            k,v = item.groups()
267e5dd7070Spatrick            keys[k] = v
268e5dd7070Spatrick        return keys
269e5dd7070Spatrick
270e5dd7070Spatrick    def load_crashes(self):
271e5dd7070Spatrick        path = posixpath.join(self.server.root, 'index.html')
272e5dd7070Spatrick        data = open(path).read()
273e5dd7070Spatrick        problems = []
274e5dd7070Spatrick        for item in kReportCrashEntryRE.finditer(data):
275e5dd7070Spatrick            fieldData = item.group(1)
276e5dd7070Spatrick            fields = dict([i.groups() for i in
277e5dd7070Spatrick                           kReportCrashEntryKeyValueRE.finditer(fieldData)])
278e5dd7070Spatrick            problems.append(fields)
279e5dd7070Spatrick        return problems
280e5dd7070Spatrick
281e5dd7070Spatrick    def handle_exception(self, exc):
282e5dd7070Spatrick        import traceback
283e5dd7070Spatrick        s = StringIO()
284e5dd7070Spatrick        print("INTERNAL ERROR\n", file=s)
285e5dd7070Spatrick        traceback.print_exc(file=s)
286e5dd7070Spatrick        f = self.send_string(s.getvalue(), 'text/plain')
287e5dd7070Spatrick        if f:
288e5dd7070Spatrick            self.copyfile(f, self.wfile)
289e5dd7070Spatrick            f.close()
290e5dd7070Spatrick
291e5dd7070Spatrick    def get_scalar_field(self, name):
292e5dd7070Spatrick        if name in self.fields:
293e5dd7070Spatrick            return self.fields[name][0]
294e5dd7070Spatrick        else:
295e5dd7070Spatrick            return None
296e5dd7070Spatrick
297e5dd7070Spatrick    def submit_bug(self, c):
298e5dd7070Spatrick        title = self.get_scalar_field('title')
299e5dd7070Spatrick        description = self.get_scalar_field('description')
300e5dd7070Spatrick        report = self.get_scalar_field('report')
301e5dd7070Spatrick        reporterIndex = self.get_scalar_field('reporter')
302e5dd7070Spatrick        files = []
303e5dd7070Spatrick        for fileID in self.fields.get('files',[]):
304e5dd7070Spatrick            try:
305e5dd7070Spatrick                i = int(fileID)
306e5dd7070Spatrick            except:
307e5dd7070Spatrick                i = None
308e5dd7070Spatrick            if i is None or i<0 or i>=len(c.files):
309e5dd7070Spatrick                return (False, 'Invalid file ID')
310e5dd7070Spatrick            files.append(c.files[i])
311e5dd7070Spatrick
312e5dd7070Spatrick        if not title:
313e5dd7070Spatrick            return (False, "Missing title.")
314e5dd7070Spatrick        if not description:
315e5dd7070Spatrick            return (False, "Missing description.")
316e5dd7070Spatrick        try:
317e5dd7070Spatrick            reporterIndex = int(reporterIndex)
318e5dd7070Spatrick        except:
319e5dd7070Spatrick            return (False, "Invalid report method.")
320e5dd7070Spatrick
321e5dd7070Spatrick        # Get the reporter and parameters.
322e5dd7070Spatrick        reporter = self.server.reporters[reporterIndex]
323e5dd7070Spatrick        parameters = {}
324e5dd7070Spatrick        for o in reporter.getParameters():
325e5dd7070Spatrick            name = '%s_%s'%(reporter.getName(),o.getName())
326e5dd7070Spatrick            if name not in self.fields:
327e5dd7070Spatrick                return (False,
328e5dd7070Spatrick                        'Missing field "%s" for %s report method.'%(name,
329e5dd7070Spatrick                                                                    reporter.getName()))
330e5dd7070Spatrick            parameters[o.getName()] = self.get_scalar_field(name)
331e5dd7070Spatrick
332e5dd7070Spatrick        # Update config defaults.
333e5dd7070Spatrick        if report != 'None':
334e5dd7070Spatrick            self.server.config.set('ScanView', 'reporter', reporterIndex)
335e5dd7070Spatrick            for o in reporter.getParameters():
336e5dd7070Spatrick              if o.saveConfigValue():
337e5dd7070Spatrick                name = o.getName()
338e5dd7070Spatrick                self.server.config.set(reporter.getName(), name, parameters[name])
339e5dd7070Spatrick
340e5dd7070Spatrick        # Create the report.
341e5dd7070Spatrick        bug = Reporter.BugReport(title, description, files)
342e5dd7070Spatrick
343e5dd7070Spatrick        # Kick off a reporting thread.
344e5dd7070Spatrick        t = ReporterThread(bug, reporter, parameters, self.server)
345e5dd7070Spatrick        t.start()
346e5dd7070Spatrick
347e5dd7070Spatrick        # Wait for thread to die...
348e5dd7070Spatrick        while t.isAlive():
349e5dd7070Spatrick            time.sleep(.25)
350e5dd7070Spatrick        submitStatus = t.status
351e5dd7070Spatrick
352e5dd7070Spatrick        return (t.success, t.status)
353e5dd7070Spatrick
354e5dd7070Spatrick    def send_report_submit(self):
355e5dd7070Spatrick        report = self.get_scalar_field('report')
356e5dd7070Spatrick        c = self.get_report_context(report)
357e5dd7070Spatrick        if c.reportSource is None:
358e5dd7070Spatrick            reportingFor = "Report Crashes > "
359e5dd7070Spatrick            fileBug = """\
360e5dd7070Spatrick<a href="/report_crashes">File Bug</a> > """%locals()
361e5dd7070Spatrick        else:
362e5dd7070Spatrick            reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource,
363e5dd7070Spatrick                                                                   report)
364e5dd7070Spatrick            fileBug = '<a href="/report/%s">File Bug</a> > ' % report
365e5dd7070Spatrick        title = self.get_scalar_field('title')
366e5dd7070Spatrick        description = self.get_scalar_field('description')
367e5dd7070Spatrick
368e5dd7070Spatrick        res,message = self.submit_bug(c)
369e5dd7070Spatrick
370e5dd7070Spatrick        if res:
371e5dd7070Spatrick            statusClass = 'SubmitOk'
372e5dd7070Spatrick            statusName = 'Succeeded'
373e5dd7070Spatrick        else:
374e5dd7070Spatrick            statusClass = 'SubmitFail'
375e5dd7070Spatrick            statusName = 'Failed'
376e5dd7070Spatrick
377e5dd7070Spatrick        result = """
378e5dd7070Spatrick<head>
379e5dd7070Spatrick  <title>Bug Submission</title>
380e5dd7070Spatrick  <link rel="stylesheet" type="text/css" href="/scanview.css" />
381e5dd7070Spatrick</head>
382e5dd7070Spatrick<body>
383e5dd7070Spatrick<h3>
384e5dd7070Spatrick<a href="/">Summary</a> >
385e5dd7070Spatrick%(reportingFor)s
386e5dd7070Spatrick%(fileBug)s
387e5dd7070SpatrickSubmit</h3>
388e5dd7070Spatrick<form name="form" action="">
389e5dd7070Spatrick<table class="form">
390e5dd7070Spatrick<tr><td>
391e5dd7070Spatrick<table class="form_group">
392e5dd7070Spatrick<tr>
393e5dd7070Spatrick  <td class="form_clabel">Title:</td>
394e5dd7070Spatrick  <td class="form_value">
395e5dd7070Spatrick    <input type="text" name="title" size="50" value="%(title)s" disabled>
396e5dd7070Spatrick  </td>
397e5dd7070Spatrick</tr>
398e5dd7070Spatrick<tr>
399e5dd7070Spatrick  <td class="form_label">Description:</td>
400e5dd7070Spatrick  <td class="form_value">
401e5dd7070Spatrick<textarea rows="10" cols="80" name="description" disabled>
402e5dd7070Spatrick%(description)s
403e5dd7070Spatrick</textarea>
404e5dd7070Spatrick  </td>
405e5dd7070Spatrick</table>
406e5dd7070Spatrick</td></tr>
407e5dd7070Spatrick</table>
408e5dd7070Spatrick</form>
409e5dd7070Spatrick<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
410e5dd7070Spatrick%(message)s
411e5dd7070Spatrick<p>
412e5dd7070Spatrick<hr>
413e5dd7070Spatrick<a href="/">Return to Summary</a>
414e5dd7070Spatrick</body>
415e5dd7070Spatrick</html>"""%locals()
416e5dd7070Spatrick        return self.send_string(result)
417e5dd7070Spatrick
418e5dd7070Spatrick    def send_open_report(self, report):
419e5dd7070Spatrick        try:
420e5dd7070Spatrick            keys = self.load_report(report)
421e5dd7070Spatrick        except IOError:
422e5dd7070Spatrick            return self.send_error(400, 'Invalid report.')
423e5dd7070Spatrick
424e5dd7070Spatrick        file = keys.get('FILE')
425e5dd7070Spatrick        if not file or not posixpath.exists(file):
426e5dd7070Spatrick            return self.send_error(400, 'File does not exist: "%s"' % file)
427e5dd7070Spatrick
428e5dd7070Spatrick        import startfile
429e5dd7070Spatrick        if self.server.options.debug:
430e5dd7070Spatrick            print('%s: SERVER: opening "%s"'%(sys.argv[0],
431e5dd7070Spatrick                                                            file), file=sys.stderr)
432e5dd7070Spatrick
433e5dd7070Spatrick        status = startfile.open(file)
434e5dd7070Spatrick        if status:
435e5dd7070Spatrick            res = 'Opened: "%s"' % file
436e5dd7070Spatrick        else:
437e5dd7070Spatrick            res = 'Open failed: "%s"' % file
438e5dd7070Spatrick
439e5dd7070Spatrick        return self.send_string(res, 'text/plain')
440e5dd7070Spatrick
441e5dd7070Spatrick    def get_report_context(self, report):
442e5dd7070Spatrick        class Context(object):
443e5dd7070Spatrick            pass
444e5dd7070Spatrick        if report is None or report == 'None':
445e5dd7070Spatrick            data = self.load_crashes()
446e5dd7070Spatrick            # Don't allow empty reports.
447e5dd7070Spatrick            if not data:
448e5dd7070Spatrick                raise ValueError('No crashes detected!')
449e5dd7070Spatrick            c = Context()
450e5dd7070Spatrick            c.title = 'clang static analyzer failures'
451e5dd7070Spatrick
452e5dd7070Spatrick            stderrSummary = ""
453e5dd7070Spatrick            for item in data:
454e5dd7070Spatrick                if 'stderr' in item:
455e5dd7070Spatrick                    path = posixpath.join(self.server.root, item['stderr'])
456e5dd7070Spatrick                    if os.path.exists(path):
457e5dd7070Spatrick                        lns = itertools.islice(open(path), 0, 10)
458e5dd7070Spatrick                        stderrSummary += '%s\n--\n%s' % (item.get('src',
459e5dd7070Spatrick                                                                  '<unknown>'),
460e5dd7070Spatrick                                                         ''.join(lns))
461e5dd7070Spatrick
462e5dd7070Spatrick            c.description = """\
463e5dd7070SpatrickThe clang static analyzer failed on these inputs:
464e5dd7070Spatrick%s
465e5dd7070Spatrick
466e5dd7070SpatrickSTDERR Summary
467e5dd7070Spatrick--------------
468e5dd7070Spatrick%s
469e5dd7070Spatrick""" % ('\n'.join([item.get('src','<unknown>') for item in data]),
470e5dd7070Spatrick       stderrSummary)
471e5dd7070Spatrick            c.reportSource = None
472e5dd7070Spatrick            c.navMarkup = "Report Crashes > "
473e5dd7070Spatrick            c.files = []
474e5dd7070Spatrick            for item in data:
475e5dd7070Spatrick                c.files.append(item.get('src',''))
476e5dd7070Spatrick                c.files.append(posixpath.join(self.server.root,
477e5dd7070Spatrick                                              item.get('file','')))
478e5dd7070Spatrick                c.files.append(posixpath.join(self.server.root,
479e5dd7070Spatrick                                              item.get('clangfile','')))
480e5dd7070Spatrick                c.files.append(posixpath.join(self.server.root,
481e5dd7070Spatrick                                              item.get('stderr','')))
482e5dd7070Spatrick                c.files.append(posixpath.join(self.server.root,
483e5dd7070Spatrick                                              item.get('info','')))
484e5dd7070Spatrick            # Just in case something failed, ignore files which don't
485e5dd7070Spatrick            # exist.
486e5dd7070Spatrick            c.files = [f for f in c.files
487e5dd7070Spatrick                       if os.path.exists(f) and os.path.isfile(f)]
488e5dd7070Spatrick        else:
489e5dd7070Spatrick            # Check that this is a valid report.
490e5dd7070Spatrick            path = posixpath.join(self.server.root, 'report-%s.html' % report)
491e5dd7070Spatrick            if not posixpath.exists(path):
492e5dd7070Spatrick                raise ValueError('Invalid report ID')
493e5dd7070Spatrick            keys = self.load_report(report)
494e5dd7070Spatrick            c = Context()
495e5dd7070Spatrick            c.title = keys.get('DESC','clang error (unrecognized')
496e5dd7070Spatrick            c.description = """\
497e5dd7070SpatrickBug reported by the clang static analyzer.
498e5dd7070Spatrick
499e5dd7070SpatrickDescription: %s
500e5dd7070SpatrickFile: %s
501e5dd7070SpatrickLine: %s
502e5dd7070Spatrick"""%(c.title, keys.get('FILE','<unknown>'), keys.get('LINE', '<unknown>'))
503e5dd7070Spatrick            c.reportSource = 'report-%s.html' % report
504e5dd7070Spatrick            c.navMarkup = """<a href="/%s">Report %s</a> > """ % (c.reportSource,
505e5dd7070Spatrick                                                                  report)
506e5dd7070Spatrick
507e5dd7070Spatrick            c.files = [path]
508e5dd7070Spatrick        return c
509e5dd7070Spatrick
510e5dd7070Spatrick    def send_report(self, report, configOverrides=None):
511e5dd7070Spatrick        def getConfigOption(section, field):
512e5dd7070Spatrick            if (configOverrides is not None and
513e5dd7070Spatrick                section in configOverrides and
514e5dd7070Spatrick                field in configOverrides[section]):
515e5dd7070Spatrick                return configOverrides[section][field]
516e5dd7070Spatrick            return self.server.config.get(section, field)
517e5dd7070Spatrick
518e5dd7070Spatrick        # report is None is used for crashes
519e5dd7070Spatrick        try:
520e5dd7070Spatrick            c = self.get_report_context(report)
521e5dd7070Spatrick        except ValueError as e:
522e5dd7070Spatrick            return self.send_error(400, e.message)
523e5dd7070Spatrick
524e5dd7070Spatrick        title = c.title
525e5dd7070Spatrick        description= c.description
526e5dd7070Spatrick        reportingFor = c.navMarkup
527e5dd7070Spatrick        if c.reportSource is None:
528e5dd7070Spatrick            extraIFrame = ""
529e5dd7070Spatrick        else:
530e5dd7070Spatrick            extraIFrame = """\
531e5dd7070Spatrick<iframe src="/%s" width="100%%" height="40%%"
532e5dd7070Spatrick        scrolling="auto" frameborder="1">
533e5dd7070Spatrick  <a href="/%s">View Bug Report</a>
534e5dd7070Spatrick</iframe>""" % (c.reportSource, c.reportSource)
535e5dd7070Spatrick
536e5dd7070Spatrick        reporterSelections = []
537e5dd7070Spatrick        reporterOptions = []
538e5dd7070Spatrick
539e5dd7070Spatrick        try:
540e5dd7070Spatrick            active = int(getConfigOption('ScanView','reporter'))
541e5dd7070Spatrick        except:
542e5dd7070Spatrick            active = 0
543e5dd7070Spatrick        for i,r in enumerate(self.server.reporters):
544e5dd7070Spatrick            selected = (i == active)
545e5dd7070Spatrick            if selected:
546e5dd7070Spatrick                selectedStr = ' selected'
547e5dd7070Spatrick            else:
548e5dd7070Spatrick                selectedStr = ''
549e5dd7070Spatrick            reporterSelections.append('<option value="%d"%s>%s</option>'%(i,selectedStr,r.getName()))
550e5dd7070Spatrick            options = '\n'.join([ o.getHTML(r,title,getConfigOption) for o in r.getParameters()])
551e5dd7070Spatrick            display = ('none','')[selected]
552e5dd7070Spatrick            reporterOptions.append("""\
553e5dd7070Spatrick<tr id="%sReporterOptions" style="display:%s">
554e5dd7070Spatrick  <td class="form_label">%s Options</td>
555e5dd7070Spatrick  <td class="form_value">
556e5dd7070Spatrick    <table class="form_inner_group">
557e5dd7070Spatrick%s
558e5dd7070Spatrick    </table>
559e5dd7070Spatrick  </td>
560e5dd7070Spatrick</tr>
561e5dd7070Spatrick"""%(r.getName(),display,r.getName(),options))
562e5dd7070Spatrick        reporterSelections = '\n'.join(reporterSelections)
563e5dd7070Spatrick        reporterOptionsDivs = '\n'.join(reporterOptions)
564e5dd7070Spatrick        reportersArray = '[%s]'%(','.join([repr(r.getName()) for r in self.server.reporters]))
565e5dd7070Spatrick
566e5dd7070Spatrick        if c.files:
567e5dd7070Spatrick            fieldSize = min(5, len(c.files))
568e5dd7070Spatrick            attachFileOptions = '\n'.join(["""\
569e5dd7070Spatrick<option value="%d" selected>%s</option>""" % (i,v) for i,v in enumerate(c.files)])
570e5dd7070Spatrick            attachFileRow = """\
571e5dd7070Spatrick<tr>
572e5dd7070Spatrick  <td class="form_label">Attach:</td>
573e5dd7070Spatrick  <td class="form_value">
574e5dd7070Spatrick<select style="width:100%%" name="files" multiple size=%d>
575e5dd7070Spatrick%s
576e5dd7070Spatrick</select>
577e5dd7070Spatrick  </td>
578e5dd7070Spatrick</tr>
579e5dd7070Spatrick""" % (min(5, len(c.files)), attachFileOptions)
580e5dd7070Spatrick        else:
581e5dd7070Spatrick            attachFileRow = ""
582e5dd7070Spatrick
583e5dd7070Spatrick        result = """<html>
584e5dd7070Spatrick<head>
585e5dd7070Spatrick  <title>File Bug</title>
586e5dd7070Spatrick  <link rel="stylesheet" type="text/css" href="/scanview.css" />
587e5dd7070Spatrick</head>
588e5dd7070Spatrick<script language="javascript" type="text/javascript">
589e5dd7070Spatrickvar reporters = %(reportersArray)s;
590e5dd7070Spatrickfunction updateReporterOptions() {
591e5dd7070Spatrick  index = document.getElementById('reporter').selectedIndex;
592e5dd7070Spatrick  for (var i=0; i < reporters.length; ++i) {
593e5dd7070Spatrick    o = document.getElementById(reporters[i] + "ReporterOptions");
594e5dd7070Spatrick    if (i == index) {
595e5dd7070Spatrick      o.style.display = "";
596e5dd7070Spatrick    } else {
597e5dd7070Spatrick      o.style.display = "none";
598e5dd7070Spatrick    }
599e5dd7070Spatrick  }
600e5dd7070Spatrick}
601e5dd7070Spatrick</script>
602e5dd7070Spatrick<body onLoad="updateReporterOptions()">
603e5dd7070Spatrick<h3>
604e5dd7070Spatrick<a href="/">Summary</a> >
605e5dd7070Spatrick%(reportingFor)s
606e5dd7070SpatrickFile Bug</h3>
607e5dd7070Spatrick<form name="form" action="/report_submit" method="post">
608e5dd7070Spatrick<input type="hidden" name="report" value="%(report)s">
609e5dd7070Spatrick
610e5dd7070Spatrick<table class="form">
611e5dd7070Spatrick<tr><td>
612e5dd7070Spatrick<table class="form_group">
613e5dd7070Spatrick<tr>
614e5dd7070Spatrick  <td class="form_clabel">Title:</td>
615e5dd7070Spatrick  <td class="form_value">
616e5dd7070Spatrick    <input type="text" name="title" size="50" value="%(title)s">
617e5dd7070Spatrick  </td>
618e5dd7070Spatrick</tr>
619e5dd7070Spatrick<tr>
620e5dd7070Spatrick  <td class="form_label">Description:</td>
621e5dd7070Spatrick  <td class="form_value">
622e5dd7070Spatrick<textarea rows="10" cols="80" name="description">
623e5dd7070Spatrick%(description)s
624e5dd7070Spatrick</textarea>
625e5dd7070Spatrick  </td>
626e5dd7070Spatrick</tr>
627e5dd7070Spatrick
628e5dd7070Spatrick%(attachFileRow)s
629e5dd7070Spatrick
630e5dd7070Spatrick</table>
631e5dd7070Spatrick<br>
632e5dd7070Spatrick<table class="form_group">
633e5dd7070Spatrick<tr>
634e5dd7070Spatrick  <td class="form_clabel">Method:</td>
635e5dd7070Spatrick  <td class="form_value">
636e5dd7070Spatrick    <select id="reporter" name="reporter" onChange="updateReporterOptions()">
637e5dd7070Spatrick    %(reporterSelections)s
638e5dd7070Spatrick    </select>
639e5dd7070Spatrick  </td>
640e5dd7070Spatrick</tr>
641e5dd7070Spatrick%(reporterOptionsDivs)s
642e5dd7070Spatrick</table>
643e5dd7070Spatrick<br>
644e5dd7070Spatrick</td></tr>
645e5dd7070Spatrick<tr><td class="form_submit">
646e5dd7070Spatrick  <input align="right" type="submit" name="Submit" value="Submit">
647e5dd7070Spatrick</td></tr>
648e5dd7070Spatrick</table>
649e5dd7070Spatrick</form>
650e5dd7070Spatrick
651e5dd7070Spatrick%(extraIFrame)s
652e5dd7070Spatrick
653e5dd7070Spatrick</body>
654e5dd7070Spatrick</html>"""%locals()
655e5dd7070Spatrick
656e5dd7070Spatrick        return self.send_string(result)
657e5dd7070Spatrick
658e5dd7070Spatrick    def send_head(self, fields=None):
659e5dd7070Spatrick        if (self.server.options.onlyServeLocal and
660e5dd7070Spatrick            self.client_address[0] != '127.0.0.1'):
661e5dd7070Spatrick            return self.send_error(401, 'Unauthorized host.')
662e5dd7070Spatrick
663e5dd7070Spatrick        if fields is None:
664e5dd7070Spatrick            fields = {}
665e5dd7070Spatrick        self.fields = fields
666e5dd7070Spatrick
667e5dd7070Spatrick        o = urlparse(self.path)
668e5dd7070Spatrick        self.fields = parse_query(o.query, fields)
669e5dd7070Spatrick        path = posixpath.normpath(unquote(o.path))
670e5dd7070Spatrick
671e5dd7070Spatrick        # Split the components and strip the root prefix.
672e5dd7070Spatrick        components = path.split('/')[1:]
673e5dd7070Spatrick
674e5dd7070Spatrick        # Special case some top-level entries.
675e5dd7070Spatrick        if components:
676e5dd7070Spatrick            name = components[0]
677e5dd7070Spatrick            if len(components)==2:
678e5dd7070Spatrick                if name=='report':
679e5dd7070Spatrick                    return self.send_report(components[1])
680e5dd7070Spatrick                elif name=='open':
681e5dd7070Spatrick                    return self.send_open_report(components[1])
682e5dd7070Spatrick            elif len(components)==1:
683e5dd7070Spatrick                if name=='quit':
684e5dd7070Spatrick                    self.server.halt()
685e5dd7070Spatrick                    return self.send_string('Goodbye.', 'text/plain')
686e5dd7070Spatrick                elif name=='report_submit':
687e5dd7070Spatrick                    return self.send_report_submit()
688e5dd7070Spatrick                elif name=='report_crashes':
689e5dd7070Spatrick                    overrides = { 'ScanView' : {},
690e5dd7070Spatrick                                  'Radar' : {},
691e5dd7070Spatrick                                  'Email' : {} }
692e5dd7070Spatrick                    for i,r in enumerate(self.server.reporters):
693e5dd7070Spatrick                        if r.getName() == 'Radar':
694e5dd7070Spatrick                            overrides['ScanView']['reporter'] = i
695e5dd7070Spatrick                            break
696e5dd7070Spatrick                    overrides['Radar']['Component'] = 'llvm - checker'
697e5dd7070Spatrick                    overrides['Radar']['Component Version'] = 'X'
698e5dd7070Spatrick                    return self.send_report(None, overrides)
699e5dd7070Spatrick                elif name=='favicon.ico':
700e5dd7070Spatrick                    return self.send_path(posixpath.join(kShare,'bugcatcher.ico'))
701e5dd7070Spatrick
702e5dd7070Spatrick        # Match directory entries.
703e5dd7070Spatrick        if components[-1] == '':
704e5dd7070Spatrick            components[-1] = 'index.html'
705e5dd7070Spatrick
706e5dd7070Spatrick        relpath = '/'.join(components)
707e5dd7070Spatrick        path = posixpath.join(self.server.root, relpath)
708e5dd7070Spatrick
709e5dd7070Spatrick        if self.server.options.debug > 1:
710e5dd7070Spatrick            print('%s: SERVER: sending path "%s"'%(sys.argv[0],
711e5dd7070Spatrick                                                                 path), file=sys.stderr)
712e5dd7070Spatrick        return self.send_path(path)
713e5dd7070Spatrick
714e5dd7070Spatrick    def send_404(self):
715e5dd7070Spatrick        self.send_error(404, "File not found")
716e5dd7070Spatrick        return None
717e5dd7070Spatrick
718e5dd7070Spatrick    def send_path(self, path):
719e5dd7070Spatrick        # If the requested path is outside the root directory, do not open it
720e5dd7070Spatrick        rel = os.path.abspath(path)
721e5dd7070Spatrick        if not rel.startswith(os.path.abspath(self.server.root)):
722e5dd7070Spatrick          return self.send_404()
723e5dd7070Spatrick
724e5dd7070Spatrick        ctype = self.guess_type(path)
725e5dd7070Spatrick        if ctype.startswith('text/'):
726e5dd7070Spatrick            # Patch file instead
727e5dd7070Spatrick            return self.send_patched_file(path, ctype)
728e5dd7070Spatrick        else:
729e5dd7070Spatrick            mode = 'rb'
730e5dd7070Spatrick        try:
731e5dd7070Spatrick            f = open(path, mode)
732e5dd7070Spatrick        except IOError:
733e5dd7070Spatrick            return self.send_404()
734e5dd7070Spatrick        return self.send_file(f, ctype)
735e5dd7070Spatrick
736e5dd7070Spatrick    def send_file(self, f, ctype):
737e5dd7070Spatrick        # Patch files to add links, but skip binary files.
738e5dd7070Spatrick        self.send_response(200)
739e5dd7070Spatrick        self.send_header("Content-type", ctype)
740e5dd7070Spatrick        fs = os.fstat(f.fileno())
741e5dd7070Spatrick        self.send_header("Content-Length", str(fs[6]))
742e5dd7070Spatrick        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
743e5dd7070Spatrick        self.end_headers()
744e5dd7070Spatrick        return f
745e5dd7070Spatrick
746e5dd7070Spatrick    def send_string(self, s, ctype='text/html', headers=True, mtime=None):
747*a9ac8606Spatrick        encoded_s = s.encode('utf-8')
748e5dd7070Spatrick        if headers:
749e5dd7070Spatrick            self.send_response(200)
750e5dd7070Spatrick            self.send_header("Content-type", ctype)
751e5dd7070Spatrick            self.send_header("Content-Length", str(len(encoded_s)))
752e5dd7070Spatrick            if mtime is None:
753e5dd7070Spatrick                mtime = self.dynamic_mtime
754e5dd7070Spatrick            self.send_header("Last-Modified", self.date_time_string(mtime))
755e5dd7070Spatrick            self.end_headers()
756e5dd7070Spatrick        return BytesIO(encoded_s)
757e5dd7070Spatrick
758e5dd7070Spatrick    def send_patched_file(self, path, ctype):
759e5dd7070Spatrick        # Allow a very limited set of variables. This is pretty gross.
760e5dd7070Spatrick        variables = {}
761e5dd7070Spatrick        variables['report'] = ''
762e5dd7070Spatrick        m = kReportFileRE.match(path)
763e5dd7070Spatrick        if m:
764e5dd7070Spatrick            variables['report'] = m.group(2)
765e5dd7070Spatrick
766e5dd7070Spatrick        try:
767e5dd7070Spatrick            f = open(path,'rb')
768e5dd7070Spatrick        except IOError:
769e5dd7070Spatrick            return self.send_404()
770e5dd7070Spatrick        fs = os.fstat(f.fileno())
771e5dd7070Spatrick        data = f.read().decode('utf-8')
772e5dd7070Spatrick        for a,b in kReportReplacements:
773e5dd7070Spatrick            data = a.sub(b % variables, data)
774e5dd7070Spatrick        return self.send_string(data, ctype, mtime=fs.st_mtime)
775e5dd7070Spatrick
776e5dd7070Spatrick
777e5dd7070Spatrickdef create_server(address, options, root):
778e5dd7070Spatrick    import Reporter
779e5dd7070Spatrick
780e5dd7070Spatrick    reporters = Reporter.getReporters()
781e5dd7070Spatrick
782e5dd7070Spatrick    return ScanViewServer(address, ScanViewRequestHandler,
783e5dd7070Spatrick                          root,
784e5dd7070Spatrick                          reporters,
785e5dd7070Spatrick                          options)
786