blob: c213d3a38499a921386ad76423915c647899b0e1 [file] [log] [blame]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +00001import BaseHTTPServer
2import SimpleHTTPServer
3import os
4import sys
5import urllib, urlparse
6import posixpath
7import StringIO
8import re
9import shutil
10import threading
11import time
12import socket
13
Daniel Dunbar61717b32008-09-20 01:43:16 +000014import Reporter
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000015
16# Keys replaced by server.
17
Daniel Dunbar14cad832008-09-21 20:34:58 +000018kReportColRE = re.compile('<!-- REPORTBUGCOL -->')
19kReportColRepl = '<td></td>'
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000020kReportBugRE = re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->')
Ted Kremenek16a80232008-09-21 03:55:51 +000021kReportBugRepl = '<td class="ReportBug"><a href="report/\\1">Report Bug</a></td>'
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000022kBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
23
Daniel Dunbar14cad832008-09-21 20:34:58 +000024kReportReplacements = [(kReportColRE, kReportColRepl),
25 (kReportBugRE, kReportBugRepl)]
26
Daniel Dunbar2b353482008-09-21 23:02:25 +000027# Other simple parameters
28
29kResources = posixpath.join(posixpath.dirname(__file__), 'Resources')
30
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000031###
32
33__version__ = "0.1"
34
35__all__ = ["create_server"]
36
37class ReporterThread(threading.Thread):
38 def __init__(self, report, reporter, parameters, server):
39 threading.Thread.__init__(self)
40 self.report = report
41 self.server = server
42 self.reporter = reporter
43 self.parameters = parameters
44 self.status = None
45
46 def run(self):
47 result = None
48 try:
49 if self.server.options.debug:
50 print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
51 result = self.reporter.fileReport(self.report, self.parameters)
52 time.sleep(3)
53 if self.server.options.debug:
54 print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
Daniel Dunbar61717b32008-09-20 01:43:16 +000055 except Reporter.ReportFailure,e:
56 s = StringIO.StringIO()
57 print >>s,'Submission Failed<br><pre>'
58 print >>s,e.value
59 print >>s,'</pre>'
60 self.status = s.getvalue()
61 return
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000062 except Exception,e:
63 s = StringIO.StringIO()
64 import traceback
65 print >>s,'Submission Failed<br><pre>'
66 traceback.print_exc(e,file=s)
67 print >>s,'</pre>'
68 self.status = s.getvalue()
69 return
70
71 s = StringIO.StringIO()
72 print >>s, 'Submission Complete!'
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000073 if result is not None:
Daniel Dunbar61717b32008-09-20 01:43:16 +000074 print >>s, '<hr>'
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000075 print >>s, result
76 self.status = s.getvalue()
77
78class ScanViewServer(BaseHTTPServer.HTTPServer):
79 def __init__(self, address, handler, root, reporters, options):
80 BaseHTTPServer.HTTPServer.__init__(self, address, handler)
81 self.root = root
82 self.reporters = reporters
83 self.options = options
84 self.halted = False
85
86 def halt(self):
87 self.halted = True
88 if self.options.debug:
89 print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
90
91 def serve_forever(self):
92 while not self.halted:
93 if self.options.debug > 1:
94 print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
95 try:
96 self.handle_request()
97 except OSError,e:
98 print 'OSError',e.errno
99
Daniel Dunbar14cad832008-09-21 20:34:58 +0000100 def finish_request(self, request, client_address):
101 if self.options.autoReload:
102 import ScanView
103 self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
104 BaseHTTPServer.HTTPServer.finish_request(self, request, client_address)
105
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000106 def handle_error(self, request, client_address):
107 # Ignore socket errors
108 info = sys.exc_info()
109 if info and isinstance(info[1], socket.error):
110 if self.options.debug > 1:
111 print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
112 return
Daniel Dunbar14cad832008-09-21 20:34:58 +0000113 BaseHTTPServer.HTTPServer.handle_error(self, request, client_address)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000114
115# Borrowed from Quixote, with simplifications.
116def parse_query(qs, fields=None):
117 if fields is None:
118 fields = {}
119 for chunk in filter(None, qs.split('&')):
120 if '=' not in chunk:
121 name = chunk
122 value = ''
123 else:
124 name, value = chunk.split('=', 1)
125 name = urllib.unquote(name.replace('+', ' '))
126 value = urllib.unquote(value.replace('+', ' '))
127 fields[name] = value
128 return fields
129
130class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
131 server_version = "ScanViewServer/" + __version__
Daniel Dunbar14cad832008-09-21 20:34:58 +0000132 dynamic_mtime = time.time()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000133
134 def do_HEAD(self):
135 try:
136 SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
137 except Exception,e:
138 self.handle_exception(e)
139
140 def do_GET(self):
141 try:
142 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
143 except Exception,e:
144 self.handle_exception(e)
145
146 def do_POST(self):
147 """Serve a POST request."""
148 try:
149 length = self.headers.getheader('content-length') or "0"
150 try:
151 length = int(length)
152 except:
153 length = 0
154 content = self.rfile.read(length)
155 fields = parse_query(content)
156 f = self.send_head(fields)
157 if f:
158 self.copyfile(f, self.wfile)
159 f.close()
160 except Exception,e:
161 self.handle_exception(e)
162
163 def log_message(self, format, *args):
164 if self.server.options.debug:
165 sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
166 (sys.argv[0],
167 self.address_string(),
168 self.log_date_time_string(),
169 format%args))
170
171 def load_report(self, report):
172 path = os.path.join(self.server.root, 'report-%s.html'%report)
173 data = open(path).read()
174 keys = {}
175 for item in kBugKeyValueRE.finditer(data):
176 k,v = item.groups()
177 keys[k] = v
178 return keys
179
180 def handle_exception(self, exc):
181 import traceback
182 s = StringIO.StringIO()
183 print >>s, "INTERNAL ERROR\n"
184 traceback.print_exc(exc, s)
185 f = self.send_string(s.getvalue(), 'text/plain')
186 if f:
187 self.copyfile(f, self.wfile)
188 f.close()
189
190 def send_internal_error(self, message):
191 return self.send_string('ERROR: %s'%(message,), 'text/plain')
192
193 def send_report_submit(self):
194 s = StringIO.StringIO()
195 report = self.fields.get('report')
196 reporter = self.fields.get('reporter')
197 title = self.fields.get('title')
198 description = self.fields.get('description')
199
200 # Get the reporter and parameters.
201 reporter = self.server.reporters[int(reporter)]
202 parameters = {}
203 for o in reporter.getParameterNames():
204 name = '%s_%s'%(reporter.getName(),o)
205 parameters[o] = self.fields.get(name)
206
207 # Create the report.
208 path = os.path.join(self.server.root, 'report-%s.html'%report)
209 files = [path]
Daniel Dunbar61717b32008-09-20 01:43:16 +0000210 br = Reporter.BugReport(title, description, files)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000211
212 # Send back an initial response and wait for the report to
213 # finish.
214 initial_response = """<html>
Daniel Dunbar0174b282008-09-22 00:11:51 +0000215<head>
216 <title>Filing Report</title>
217 <link rel="stylesheet" type="text/css" href="/scanview.css" />
218</head>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000219<body>
220<h1>Filing Report</h1>
221<b>Report</b>: %(report)s<br>
222<b>Title</b>: %(title)s<br>
223<b>Description</b>: %(description)s<br>
224<hr>
225Submission in progress."""%locals()
226
227 self.send_response(200)
228 self.send_header("Content-type", 'text/html')
229 self.end_headers()
230 self.wfile.write(initial_response)
231 self.wfile.flush()
232
233 # Kick off a reporting thread.
234 t = ReporterThread(br, reporter, parameters, self.server)
235 t.start()
236
237 # Wait for thread to die...
238 while t.isAlive():
239 self.wfile.write('.')
240 self.wfile.flush()
241 time.sleep(.25)
242 submitStatus = t.status
243
244 end_response = """<br>
245%(submitStatus)s
246<hr>
247<a href="/">Home</a>
248</body>
249</html>
250"""%locals()
251 return self.send_string(end_response, headers=False)
252
253 def send_report(self, report):
254 try:
255 keys = self.load_report(report)
256 except IOError:
257 return self.send_internal_error('Invalid report.')
258
259 initialTitle = keys.get('DESC','')
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000260 initialDescription = """\
261Bug generated by the clang static analyzer.
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000262
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000263Description: %s
264File: %s
265Line: %s
266"""%(initialTitle,
267 keys.get('FILE','<unknown>'),
268 keys.get('LINE','<unknown>'))
269
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000270 reporterSelections = []
271 reporterOptions = []
272
273 for i,r in enumerate(self.server.reporters):
274 reporterSelections.append('<option value="%d">%s</option>'%(i,r.getName()))
Daniel Dunbar0174b282008-09-22 00:11:51 +0000275 options = '\n'.join(["""\
276<tr>
277 <td class="form_clabel">%s:</td>
278 <td class="form_value"><input type="text" name="%s_%s"></td>
279</tr>"""%(o,r.getName(),o) for o in r.getParameterNames()])
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000280 if i==0:
Daniel Dunbar0174b282008-09-22 00:11:51 +0000281 display = ''
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000282 else:
283 display = 'none'
Daniel Dunbar0174b282008-09-22 00:11:51 +0000284 reporterOptions.append("""\
285<tr id="%sReporterOptions" style="display:%s">
286 <td class="form_label">%s Options</td>
287 <td class="form_value">
288 <table class="form_inner_group">
289%s
290 </table>
291 </td>
292</tr>
293"""%(r.getName(),display,r.getName(),options))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000294 reporterSelections = '\n'.join(reporterSelections)
295 reporterOptionsDivs = '\n'.join(reporterOptions)
296 reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
297
298 result = """<html>
299<head>
300 <title>File Report</title>
Daniel Dunbar0174b282008-09-22 00:11:51 +0000301 <link rel="stylesheet" type="text/css" href="/scanview.css" />
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000302</head>
303<script language="javascript" type="text/javascript">
304var reporters = %(reportersArray)s;
305function updateReporterOptions() {
306 index = document.getElementById('reporter').selectedIndex;
307 for (var i=0; i < reporters.length; ++i) {
308 o = document.getElementById(reporters[i] + "ReporterOptions");
309 if (i == index) {
Daniel Dunbar0174b282008-09-22 00:11:51 +0000310 o.style.display = "";
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000311 } else {
312 o.style.display = "none";
313 }
314 }
315}
316</script>
317<body>
318<h1>File Report</h1>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000319<form name="form" action="/report_submit" method="post">
Daniel Dunbar0174b282008-09-22 00:11:51 +0000320<input type="hidden" name="report" value="%(report)s">
321
322<table class="form">
323<tr><td>
324<table class="form_group">
325<tr>
326 <td class="form_clabel">Title:</td>
327 <td class="form_value">
328 <input type="text" name="title" size="50" value="%(initialTitle)s">
329 </td>
330</tr>
331<tr>
332 <td class="form_label">Description:</td>
333 <td class="form_value">
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000334<textarea rows="10" cols="80" name="description">
335%(initialDescription)s
Daniel Dunbar0174b282008-09-22 00:11:51 +0000336</textarea>
337 </td>
338</table>
339<br>
340<table class="form_group">
341<tr>
342 <td class="form_clabel">Method:</td>
343 <td class="form_value">
344 <select id="reporter" name="reporter" onChange="updateReporterOptions()">
345 %(reporterSelections)s
346 </select>
347 </td>
348</tr>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000349%(reporterOptionsDivs)s
Daniel Dunbar0174b282008-09-22 00:11:51 +0000350</table>
351<br>
352</td></tr>
353<tr><td class="form_submit">
354 <input align="right" type="submit" name="Submit" value="Submit">
355</td></tr>
356</table>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000357</form>
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000358
Daniel Dunbar0174b282008-09-22 00:11:51 +0000359
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000360<iframe src="/report-%(report)s.html#EndPath" width="100%%" height="40%%"
361 scrolling="auto" frameborder="1">
362 <a href="/report-%(report)s.html#EndPath">View Bug Report</a>
363</iframe>
364
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000365</body>
366</html>"""%locals()
367 return self.send_string(result)
368
369 def send_head(self, fields=None):
370 if fields is None:
371 fields = {}
372 self.fields = fields
373
374 o = urlparse.urlparse(self.path)
375 self.fields = parse_query(o.query, fields)
376 path = posixpath.normpath(urllib.unquote(o.path))
377
378 # Split the components and strip the root prefix.
379 components = path.split('/')[1:]
380
381 # Special case some top-level entries.
382 if components:
383 name = components[0]
Daniel Dunbar0174b282008-09-22 00:11:51 +0000384 if len(components)==2:
385 if name=='report':
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000386 return self.send_report(components[1])
Daniel Dunbar0174b282008-09-22 00:11:51 +0000387 elif len(components)==1:
388 if name=='quit':
389 self.server.halt()
390 return self.send_string('Goodbye.', 'text/plain')
391 elif name=='report_submit':
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000392 return self.send_report_submit()
Daniel Dunbar0174b282008-09-22 00:11:51 +0000393 elif name=='favicon.ico':
Daniel Dunbar2b353482008-09-21 23:02:25 +0000394 return self.send_path(posixpath.join(kResources,'bugcatcher.ico'))
Daniel Dunbar0174b282008-09-22 00:11:51 +0000395 elif name=='scanview.css':
396 return self.send_path(posixpath.join(kResources,'scanview.css'))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000397
398 # Match directory entries.
399 if components[-1] == '':
400 components[-1] = 'index.html'
Daniel Dunbar2b353482008-09-21 23:02:25 +0000401
402 suffix = '/'.join(components)
403
404 # The summary may reference source files on disk using rooted
405 # paths. Make sure these resolve correctly for now.
406 # FIXME: This isn't a very good idea... we should probably
407 # mark rooted paths somehow.
408 if os.path.exists(posixpath.join('/', suffix)):
409 path = posixpath.join('/', suffix)
410 else:
411 path = posixpath.join(self.server.root, suffix)
412
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000413 if self.server.options.debug > 1:
414 print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
415 path)
416 return self.send_path(path)
417
418 def send_404(self):
419 self.send_error(404, "File not found")
420 return None
421
422 def send_path(self, path):
423 ctype = self.guess_type(path)
424 if ctype.startswith('text/'):
425 # Patch file instead
426 return self.send_patched_file(path, ctype)
427 else:
428 mode = 'rb'
429 try:
430 f = open(path, mode)
431 except IOError:
432 return self.send_404()
433 return self.send_file(f, ctype)
434
435 def send_file(self, f, ctype):
436 # Patch files to add links, but skip binary files.
437 self.send_response(200)
438 self.send_header("Content-type", ctype)
439 fs = os.fstat(f.fileno())
440 self.send_header("Content-Length", str(fs[6]))
441 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
442 self.end_headers()
443 return f
444
445 def send_string(self, s, ctype='text/html', headers=True, mtime=None):
446 if headers:
447 self.send_response(200)
448 self.send_header("Content-type", ctype)
449 self.send_header("Content-Length", str(len(s)))
Daniel Dunbar14cad832008-09-21 20:34:58 +0000450 if mtime is None:
451 mtime = self.dynamic_mtime
452 self.send_header("Last-Modified", self.date_time_string(mtime))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000453 self.end_headers()
454 return StringIO.StringIO(s)
455
456 def send_patched_file(self, path, ctype):
457 f = open(path,'r')
458 fs = os.fstat(f.fileno())
459 data = f.read()
Daniel Dunbar14cad832008-09-21 20:34:58 +0000460 for a,b in kReportReplacements:
461 data = a.sub(b, data)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000462 return self.send_string(data, ctype, mtime=fs.st_mtime)
463
464
465def create_server(options, root):
466 import Reporter
467
468 reporters = Reporter.getReporters()
469
470 return ScanViewServer((options.host, options.port),
471 ScanViewRequestHandler,
472 root,
473 reporters,
474 options)