blob: 3c605211ff22a6531dcee3c0876627a2595ea1d3 [file] [log] [blame]
Daniel Dunbare33d3682008-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 Dunbar54722492008-09-20 01:43:16 +000014import Reporter
Daniel Dunbar8dcd9402008-09-22 02:27:45 +000015import ConfigParser
Daniel Dunbare33d3682008-09-19 23:32:11 +000016
17# Keys replaced by server.
18
Daniel Dunbarcb028b02008-09-21 20:34:58 +000019kReportColRE = re.compile('<!-- REPORTBUGCOL -->')
20kReportColRepl = '<td></td>'
Daniel Dunbare33d3682008-09-19 23:32:11 +000021kReportBugRE = re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->')
Daniel Dunbar47cceff2008-09-22 01:21:30 +000022kReportBugRepl = '<td class="View"><a href="report/\\1">Report Bug</a></td>'
Daniel Dunbare33d3682008-09-19 23:32:11 +000023kBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
24
Daniel Dunbarcb028b02008-09-21 20:34:58 +000025kReportReplacements = [(kReportColRE, kReportColRepl),
26 (kReportBugRE, kReportBugRepl)]
27
Daniel Dunbarb131c8a2008-09-21 23:02:25 +000028# Other simple parameters
29
30kResources = posixpath.join(posixpath.dirname(__file__), 'Resources')
Daniel Dunbar8dcd9402008-09-22 02:27:45 +000031kConfigPath = os.path.expanduser('~/.scanview.cfg')
Daniel Dunbarb131c8a2008-09-21 23:02:25 +000032
Daniel Dunbare33d3682008-09-19 23:32:11 +000033###
34
35__version__ = "0.1"
36
37__all__ = ["create_server"]
38
39class ReporterThread(threading.Thread):
40 def __init__(self, report, reporter, parameters, server):
41 threading.Thread.__init__(self)
42 self.report = report
43 self.server = server
44 self.reporter = reporter
45 self.parameters = parameters
Daniel Dunbar47cceff2008-09-22 01:21:30 +000046 self.success = False
Daniel Dunbare33d3682008-09-19 23:32:11 +000047 self.status = None
48
49 def run(self):
50 result = None
51 try:
52 if self.server.options.debug:
53 print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
Daniel Dunbar47cceff2008-09-22 01:21:30 +000054 self.status = self.reporter.fileReport(self.report, self.parameters)
55 self.success = True
Daniel Dunbare33d3682008-09-19 23:32:11 +000056 time.sleep(3)
57 if self.server.options.debug:
58 print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
Daniel Dunbar54722492008-09-20 01:43:16 +000059 except Reporter.ReportFailure,e:
Daniel Dunbar47cceff2008-09-22 01:21:30 +000060 self.status = e.value
Daniel Dunbare33d3682008-09-19 23:32:11 +000061 except Exception,e:
62 s = StringIO.StringIO()
63 import traceback
Daniel Dunbar47cceff2008-09-22 01:21:30 +000064 print >>s,'<b>Unhandled Exception</b><br><pre>'
Daniel Dunbare33d3682008-09-19 23:32:11 +000065 traceback.print_exc(e,file=s)
66 print >>s,'</pre>'
67 self.status = s.getvalue()
Daniel Dunbare33d3682008-09-19 23:32:11 +000068
69class ScanViewServer(BaseHTTPServer.HTTPServer):
70 def __init__(self, address, handler, root, reporters, options):
71 BaseHTTPServer.HTTPServer.__init__(self, address, handler)
72 self.root = root
73 self.reporters = reporters
74 self.options = options
75 self.halted = False
Daniel Dunbar8dcd9402008-09-22 02:27:45 +000076 self.config = None
77 self.load_config()
Daniel Dunbare33d3682008-09-19 23:32:11 +000078
Daniel Dunbar8dcd9402008-09-22 02:27:45 +000079 def load_config(self):
80 self.config = ConfigParser.RawConfigParser()
81
82 # Add defaults
83 self.config.add_section('ScanView')
Daniel Dunbar7ad535d2008-09-22 02:53:12 +000084 for r in self.reporters:
Daniel Dunbar8dcd9402008-09-22 02:27:45 +000085 self.config.add_section(r.getName())
86 for p in r.getParameterNames():
87 self.config.set(r.getName(), p, '')
88
89 # Ignore parse errors
90 try:
91 self.config.read([kConfigPath])
92 except:
93 pass
94
95 # Save on exit
96 import atexit
97 atexit.register(lambda: self.save_config())
98
99 def save_config(self):
100 # Ignore errors (only called on exit).
101 try:
102 f = open(kConfigPath,'w')
103 self.config.write(f)
104 f.close()
105 except:
106 pass
107
Daniel Dunbare33d3682008-09-19 23:32:11 +0000108 def halt(self):
109 self.halted = True
110 if self.options.debug:
111 print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
112
113 def serve_forever(self):
114 while not self.halted:
115 if self.options.debug > 1:
116 print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
117 try:
118 self.handle_request()
119 except OSError,e:
120 print 'OSError',e.errno
121
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000122 def finish_request(self, request, client_address):
123 if self.options.autoReload:
124 import ScanView
125 self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
126 BaseHTTPServer.HTTPServer.finish_request(self, request, client_address)
127
Daniel Dunbare33d3682008-09-19 23:32:11 +0000128 def handle_error(self, request, client_address):
129 # Ignore socket errors
130 info = sys.exc_info()
131 if info and isinstance(info[1], socket.error):
132 if self.options.debug > 1:
133 print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
134 return
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000135 BaseHTTPServer.HTTPServer.handle_error(self, request, client_address)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000136
137# Borrowed from Quixote, with simplifications.
138def parse_query(qs, fields=None):
139 if fields is None:
140 fields = {}
141 for chunk in filter(None, qs.split('&')):
142 if '=' not in chunk:
143 name = chunk
144 value = ''
145 else:
146 name, value = chunk.split('=', 1)
147 name = urllib.unquote(name.replace('+', ' '))
148 value = urllib.unquote(value.replace('+', ' '))
149 fields[name] = value
150 return fields
151
152class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
153 server_version = "ScanViewServer/" + __version__
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000154 dynamic_mtime = time.time()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000155
156 def do_HEAD(self):
157 try:
158 SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
159 except Exception,e:
160 self.handle_exception(e)
161
162 def do_GET(self):
163 try:
164 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
165 except Exception,e:
166 self.handle_exception(e)
167
168 def do_POST(self):
169 """Serve a POST request."""
170 try:
171 length = self.headers.getheader('content-length') or "0"
172 try:
173 length = int(length)
174 except:
175 length = 0
176 content = self.rfile.read(length)
177 fields = parse_query(content)
178 f = self.send_head(fields)
179 if f:
180 self.copyfile(f, self.wfile)
181 f.close()
182 except Exception,e:
183 self.handle_exception(e)
184
185 def log_message(self, format, *args):
186 if self.server.options.debug:
187 sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
188 (sys.argv[0],
189 self.address_string(),
190 self.log_date_time_string(),
191 format%args))
192
193 def load_report(self, report):
194 path = os.path.join(self.server.root, 'report-%s.html'%report)
195 data = open(path).read()
196 keys = {}
197 for item in kBugKeyValueRE.finditer(data):
198 k,v = item.groups()
199 keys[k] = v
200 return keys
201
202 def handle_exception(self, exc):
203 import traceback
204 s = StringIO.StringIO()
205 print >>s, "INTERNAL ERROR\n"
206 traceback.print_exc(exc, s)
207 f = self.send_string(s.getvalue(), 'text/plain')
208 if f:
209 self.copyfile(f, self.wfile)
210 f.close()
211
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000212 def submit_bug(self):
Daniel Dunbare33d3682008-09-19 23:32:11 +0000213 title = self.fields.get('title')
214 description = self.fields.get('description')
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000215 report = self.fields.get('report')
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000216 reporterIndex = self.fields.get('reporter')
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000217
218 # Type check form parameters.
219 reportPath = posixpath.join(self.server.root,
220 'report-%s.html' % report)
221 if not posixpath.exists(reportPath):
222 return (False, "Invalid report ID.")
223 if not title:
224 return (False, "Missing title.")
225 if not description:
226 return (False, "Missing description.")
227 try:
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000228 reporterIndex = int(reporterIndex)
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000229 except:
230 return (False, "Invalid report method.")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000231
232 # Get the reporter and parameters.
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000233 reporter = self.server.reporters[reporterIndex]
Daniel Dunbare33d3682008-09-19 23:32:11 +0000234 parameters = {}
235 for o in reporter.getParameterNames():
236 name = '%s_%s'%(reporter.getName(),o)
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000237 if name not in self.fields:
238 return (False,
239 'Missing field "%s" for %s report method.'%(name,
240 reporter.getName()))
241 parameters[o] = self.fields[name]
Daniel Dunbare33d3682008-09-19 23:32:11 +0000242
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000243 # Update config defaults.
244 self.server.config.set('ScanView', 'reporter', reporterIndex)
245 for o in reporter.getParameterNames():
246 self.server.config.set(reporter.getName(), o, parameters[o])
247
Daniel Dunbare33d3682008-09-19 23:32:11 +0000248 # Create the report.
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000249 bug = Reporter.BugReport(title, description, [reportPath])
Daniel Dunbare33d3682008-09-19 23:32:11 +0000250
Daniel Dunbare33d3682008-09-19 23:32:11 +0000251 # Kick off a reporting thread.
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000252 t = ReporterThread(bug, reporter, parameters, self.server)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000253 t.start()
254
255 # Wait for thread to die...
256 while t.isAlive():
Daniel Dunbare33d3682008-09-19 23:32:11 +0000257 time.sleep(.25)
258 submitStatus = t.status
259
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000260 return (t.success, t.status)
261
262 def send_report_submit(self):
263 title = self.fields.get('title')
264 description = self.fields.get('description')
265
266 res,message = self.submit_bug()
267
268 if res:
269 statusClass = 'SubmitOk'
270 statusName = 'Succeeded'
271 else:
272 statusClass = 'SubmitFail'
273 statusName = 'Failed'
274
275 result = """
276<head>
277 <title>Report Submission</title>
278 <link rel="stylesheet" type="text/css" href="/scanview.css" />
279</head>
280<body>
281<h1>Report Submission</h1>
282<form name="form" action="">
283<table class="form">
284<tr><td>
285<table class="form_group">
286<tr>
287 <td class="form_clabel">Title:</td>
288 <td class="form_value">
289 <input type="text" name="title" size="50" value="%(title)s" disabled>
290 </td>
291</tr>
292<tr>
293 <td class="form_label">Description:</td>
294 <td class="form_value">
295<textarea rows="10" cols="80" name="description" disabled>
296%(description)s
297</textarea>
298 </td>
299</table>
300</td></tr>
301</table>
302</form>
303<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
304%(message)s
305<p>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000306<hr>
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000307<a href="/">Return to Summary</a>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000308</body>
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000309</html>"""%locals()
310 return self.send_string(result)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000311
312 def send_report(self, report):
313 try:
314 keys = self.load_report(report)
315 except IOError:
Daniel Dunbar7345e472008-09-22 01:40:14 +0000316 return self.send_error(400, 'Invalid report.')
Daniel Dunbare33d3682008-09-19 23:32:11 +0000317
318 initialTitle = keys.get('DESC','')
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000319 initialDescription = """\
320Bug generated by the clang static analyzer.
Daniel Dunbare33d3682008-09-19 23:32:11 +0000321
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000322Description: %s
323File: %s
324Line: %s
325"""%(initialTitle,
326 keys.get('FILE','<unknown>'),
327 keys.get('LINE','<unknown>'))
328
Daniel Dunbare33d3682008-09-19 23:32:11 +0000329 reporterSelections = []
330 reporterOptions = []
331
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000332 try:
333 active = self.server.config.getint('ScanView','reporter')
334 except:
335 active = 0
Daniel Dunbare33d3682008-09-19 23:32:11 +0000336 for i,r in enumerate(self.server.reporters):
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000337 selected = (i == active)
338 if selected:
339 selectedStr = ' selected'
340 else:
341 selectedStr = ''
342 reporterSelections.append('<option value="%d"%s>%s</option>'%(i,selectedStr,r.getName()))
Daniel Dunbar78266292008-09-22 00:11:51 +0000343 options = '\n'.join(["""\
344<tr>
345 <td class="form_clabel">%s:</td>
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000346 <td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
347</tr>"""%(o,r.getName(),o,self.server.config.get(r.getName(), o)) for o in r.getParameterNames()])
348 display = ('none','')[selected]
Daniel Dunbar78266292008-09-22 00:11:51 +0000349 reporterOptions.append("""\
350<tr id="%sReporterOptions" style="display:%s">
351 <td class="form_label">%s Options</td>
352 <td class="form_value">
353 <table class="form_inner_group">
354%s
355 </table>
356 </td>
357</tr>
358"""%(r.getName(),display,r.getName(),options))
Daniel Dunbare33d3682008-09-19 23:32:11 +0000359 reporterSelections = '\n'.join(reporterSelections)
360 reporterOptionsDivs = '\n'.join(reporterOptions)
361 reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
362
363 result = """<html>
364<head>
365 <title>File Report</title>
Daniel Dunbar78266292008-09-22 00:11:51 +0000366 <link rel="stylesheet" type="text/css" href="/scanview.css" />
Daniel Dunbare33d3682008-09-19 23:32:11 +0000367</head>
368<script language="javascript" type="text/javascript">
369var reporters = %(reportersArray)s;
370function updateReporterOptions() {
371 index = document.getElementById('reporter').selectedIndex;
372 for (var i=0; i < reporters.length; ++i) {
373 o = document.getElementById(reporters[i] + "ReporterOptions");
374 if (i == index) {
Daniel Dunbar78266292008-09-22 00:11:51 +0000375 o.style.display = "";
Daniel Dunbare33d3682008-09-19 23:32:11 +0000376 } else {
377 o.style.display = "none";
378 }
379 }
380}
381</script>
Daniel Dunbar7345e472008-09-22 01:40:14 +0000382<body onLoad="updateReporterOptions()">
Daniel Dunbare33d3682008-09-19 23:32:11 +0000383<h1>File Report</h1>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000384<form name="form" action="/report_submit" method="post">
Daniel Dunbar78266292008-09-22 00:11:51 +0000385<input type="hidden" name="report" value="%(report)s">
386
387<table class="form">
388<tr><td>
389<table class="form_group">
390<tr>
391 <td class="form_clabel">Title:</td>
392 <td class="form_value">
393 <input type="text" name="title" size="50" value="%(initialTitle)s">
394 </td>
395</tr>
396<tr>
397 <td class="form_label">Description:</td>
398 <td class="form_value">
Daniel Dunbare33d3682008-09-19 23:32:11 +0000399<textarea rows="10" cols="80" name="description">
400%(initialDescription)s
Daniel Dunbar78266292008-09-22 00:11:51 +0000401</textarea>
402 </td>
403</table>
404<br>
405<table class="form_group">
406<tr>
407 <td class="form_clabel">Method:</td>
408 <td class="form_value">
409 <select id="reporter" name="reporter" onChange="updateReporterOptions()">
410 %(reporterSelections)s
411 </select>
412 </td>
413</tr>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000414%(reporterOptionsDivs)s
Daniel Dunbar78266292008-09-22 00:11:51 +0000415</table>
416<br>
417</td></tr>
418<tr><td class="form_submit">
419 <input align="right" type="submit" name="Submit" value="Submit">
420</td></tr>
421</table>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000422</form>
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000423
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000424<iframe src="/report-%(report)s.html" width="100%%" height="70%%"
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000425 scrolling="auto" frameborder="1">
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000426 <a href="/report-%(report)s.html">View Bug Report</a>
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000427</iframe>
428
Daniel Dunbare33d3682008-09-19 23:32:11 +0000429</body>
430</html>"""%locals()
431 return self.send_string(result)
432
433 def send_head(self, fields=None):
434 if fields is None:
435 fields = {}
436 self.fields = fields
437
438 o = urlparse.urlparse(self.path)
439 self.fields = parse_query(o.query, fields)
440 path = posixpath.normpath(urllib.unquote(o.path))
441
442 # Split the components and strip the root prefix.
443 components = path.split('/')[1:]
444
445 # Special case some top-level entries.
446 if components:
447 name = components[0]
Daniel Dunbar78266292008-09-22 00:11:51 +0000448 if len(components)==2:
449 if name=='report':
Daniel Dunbare33d3682008-09-19 23:32:11 +0000450 return self.send_report(components[1])
Daniel Dunbar78266292008-09-22 00:11:51 +0000451 elif len(components)==1:
452 if name=='quit':
453 self.server.halt()
454 return self.send_string('Goodbye.', 'text/plain')
455 elif name=='report_submit':
Daniel Dunbare33d3682008-09-19 23:32:11 +0000456 return self.send_report_submit()
Daniel Dunbar78266292008-09-22 00:11:51 +0000457 elif name=='favicon.ico':
Daniel Dunbarb131c8a2008-09-21 23:02:25 +0000458 return self.send_path(posixpath.join(kResources,'bugcatcher.ico'))
Daniel Dunbar78266292008-09-22 00:11:51 +0000459 elif name=='scanview.css':
460 return self.send_path(posixpath.join(kResources,'scanview.css'))
Daniel Dunbare33d3682008-09-19 23:32:11 +0000461
462 # Match directory entries.
463 if components[-1] == '':
464 components[-1] = 'index.html'
Daniel Dunbarb131c8a2008-09-21 23:02:25 +0000465
466 suffix = '/'.join(components)
467
468 # The summary may reference source files on disk using rooted
469 # paths. Make sure these resolve correctly for now.
470 # FIXME: This isn't a very good idea... we should probably
471 # mark rooted paths somehow.
472 if os.path.exists(posixpath.join('/', suffix)):
473 path = posixpath.join('/', suffix)
474 else:
475 path = posixpath.join(self.server.root, suffix)
476
Daniel Dunbare33d3682008-09-19 23:32:11 +0000477 if self.server.options.debug > 1:
478 print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
479 path)
480 return self.send_path(path)
481
482 def send_404(self):
483 self.send_error(404, "File not found")
484 return None
485
486 def send_path(self, path):
487 ctype = self.guess_type(path)
488 if ctype.startswith('text/'):
489 # Patch file instead
490 return self.send_patched_file(path, ctype)
491 else:
492 mode = 'rb'
493 try:
494 f = open(path, mode)
495 except IOError:
496 return self.send_404()
497 return self.send_file(f, ctype)
498
499 def send_file(self, f, ctype):
500 # Patch files to add links, but skip binary files.
501 self.send_response(200)
502 self.send_header("Content-type", ctype)
503 fs = os.fstat(f.fileno())
504 self.send_header("Content-Length", str(fs[6]))
505 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
506 self.end_headers()
507 return f
508
509 def send_string(self, s, ctype='text/html', headers=True, mtime=None):
510 if headers:
511 self.send_response(200)
512 self.send_header("Content-type", ctype)
513 self.send_header("Content-Length", str(len(s)))
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000514 if mtime is None:
515 mtime = self.dynamic_mtime
516 self.send_header("Last-Modified", self.date_time_string(mtime))
Daniel Dunbare33d3682008-09-19 23:32:11 +0000517 self.end_headers()
518 return StringIO.StringIO(s)
519
520 def send_patched_file(self, path, ctype):
Daniel Dunbar7345e472008-09-22 01:40:14 +0000521 try:
522 f = open(path,'r')
523 except IOError:
524 return self.send_404()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000525 fs = os.fstat(f.fileno())
526 data = f.read()
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000527 for a,b in kReportReplacements:
528 data = a.sub(b, data)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000529 return self.send_string(data, ctype, mtime=fs.st_mtime)
530
531
Daniel Dunbar7ad535d2008-09-22 02:53:12 +0000532def create_server(address, options, root):
Daniel Dunbare33d3682008-09-19 23:32:11 +0000533 import Reporter
534
535 reporters = Reporter.getReporters()
536
Daniel Dunbar7ad535d2008-09-22 02:53:12 +0000537 return ScanViewServer(address, ScanViewRequestHandler,
Daniel Dunbare33d3682008-09-19 23:32:11 +0000538 root,
539 reporters,
540 options)