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