blob: 90437772a5d780af0aa6bc295f1c15b888a348c7 [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" -->')
Daniel Dunbar55c5aa42008-09-22 01:21:30 +000021kReportBugRepl = '<td class="View"><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
Daniel Dunbar55c5aa42008-09-22 01:21:30 +000044 self.success = False
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000045 self.status = None
46
47 def run(self):
48 result = None
49 try:
50 if self.server.options.debug:
51 print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
Daniel Dunbar55c5aa42008-09-22 01:21:30 +000052 self.status = self.reporter.fileReport(self.report, self.parameters)
53 self.success = True
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000054 time.sleep(3)
55 if self.server.options.debug:
56 print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
Daniel Dunbar61717b32008-09-20 01:43:16 +000057 except Reporter.ReportFailure,e:
Daniel Dunbar55c5aa42008-09-22 01:21:30 +000058 self.status = e.value
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000059 except Exception,e:
60 s = StringIO.StringIO()
61 import traceback
Daniel Dunbar55c5aa42008-09-22 01:21:30 +000062 print >>s,'<b>Unhandled Exception</b><br><pre>'
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000063 traceback.print_exc(e,file=s)
64 print >>s,'</pre>'
65 self.status = s.getvalue()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000066
67class ScanViewServer(BaseHTTPServer.HTTPServer):
68 def __init__(self, address, handler, root, reporters, options):
69 BaseHTTPServer.HTTPServer.__init__(self, address, handler)
70 self.root = root
71 self.reporters = reporters
72 self.options = options
73 self.halted = False
74
75 def halt(self):
76 self.halted = True
77 if self.options.debug:
78 print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
79
80 def serve_forever(self):
81 while not self.halted:
82 if self.options.debug > 1:
83 print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
84 try:
85 self.handle_request()
86 except OSError,e:
87 print 'OSError',e.errno
88
Daniel Dunbar14cad832008-09-21 20:34:58 +000089 def finish_request(self, request, client_address):
90 if self.options.autoReload:
91 import ScanView
92 self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
93 BaseHTTPServer.HTTPServer.finish_request(self, request, client_address)
94
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000095 def handle_error(self, request, client_address):
96 # Ignore socket errors
97 info = sys.exc_info()
98 if info and isinstance(info[1], socket.error):
99 if self.options.debug > 1:
100 print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
101 return
Daniel Dunbar14cad832008-09-21 20:34:58 +0000102 BaseHTTPServer.HTTPServer.handle_error(self, request, client_address)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000103
104# Borrowed from Quixote, with simplifications.
105def parse_query(qs, fields=None):
106 if fields is None:
107 fields = {}
108 for chunk in filter(None, qs.split('&')):
109 if '=' not in chunk:
110 name = chunk
111 value = ''
112 else:
113 name, value = chunk.split('=', 1)
114 name = urllib.unquote(name.replace('+', ' '))
115 value = urllib.unquote(value.replace('+', ' '))
116 fields[name] = value
117 return fields
118
119class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
120 server_version = "ScanViewServer/" + __version__
Daniel Dunbar14cad832008-09-21 20:34:58 +0000121 dynamic_mtime = time.time()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000122
123 def do_HEAD(self):
124 try:
125 SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
126 except Exception,e:
127 self.handle_exception(e)
128
129 def do_GET(self):
130 try:
131 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
132 except Exception,e:
133 self.handle_exception(e)
134
135 def do_POST(self):
136 """Serve a POST request."""
137 try:
138 length = self.headers.getheader('content-length') or "0"
139 try:
140 length = int(length)
141 except:
142 length = 0
143 content = self.rfile.read(length)
144 fields = parse_query(content)
145 f = self.send_head(fields)
146 if f:
147 self.copyfile(f, self.wfile)
148 f.close()
149 except Exception,e:
150 self.handle_exception(e)
151
152 def log_message(self, format, *args):
153 if self.server.options.debug:
154 sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
155 (sys.argv[0],
156 self.address_string(),
157 self.log_date_time_string(),
158 format%args))
159
160 def load_report(self, report):
161 path = os.path.join(self.server.root, 'report-%s.html'%report)
162 data = open(path).read()
163 keys = {}
164 for item in kBugKeyValueRE.finditer(data):
165 k,v = item.groups()
166 keys[k] = v
167 return keys
168
169 def handle_exception(self, exc):
170 import traceback
171 s = StringIO.StringIO()
172 print >>s, "INTERNAL ERROR\n"
173 traceback.print_exc(exc, s)
174 f = self.send_string(s.getvalue(), 'text/plain')
175 if f:
176 self.copyfile(f, self.wfile)
177 f.close()
178
179 def send_internal_error(self, message):
180 return self.send_string('ERROR: %s'%(message,), 'text/plain')
181
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000182 def submit_bug(self):
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000183 title = self.fields.get('title')
184 description = self.fields.get('description')
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000185 report = self.fields.get('report')
186 reporter = self.fields.get('reporter')
187
188 # Type check form parameters.
189 reportPath = posixpath.join(self.server.root,
190 'report-%s.html' % report)
191 if not posixpath.exists(reportPath):
192 return (False, "Invalid report ID.")
193 if not title:
194 return (False, "Missing title.")
195 if not description:
196 return (False, "Missing description.")
197 try:
198 reporter = int(reporter)
199 except:
200 return (False, "Invalid report method.")
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000201
202 # Get the reporter and parameters.
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000203 reporter = self.server.reporters[reporter]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000204 parameters = {}
205 for o in reporter.getParameterNames():
206 name = '%s_%s'%(reporter.getName(),o)
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000207 if name not in self.fields:
208 return (False,
209 'Missing field "%s" for %s report method.'%(name,
210 reporter.getName()))
211 parameters[o] = self.fields[name]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000212
213 # Create the report.
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000214 bug = Reporter.BugReport(title, description, [reportPath])
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000215
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000216 # Kick off a reporting thread.
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000217 t = ReporterThread(bug, reporter, parameters, self.server)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000218 t.start()
219
220 # Wait for thread to die...
221 while t.isAlive():
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000222 time.sleep(.25)
223 submitStatus = t.status
224
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000225 return (t.success, t.status)
226
227 def send_report_submit(self):
228 title = self.fields.get('title')
229 description = self.fields.get('description')
230
231 res,message = self.submit_bug()
232
233 if res:
234 statusClass = 'SubmitOk'
235 statusName = 'Succeeded'
236 else:
237 statusClass = 'SubmitFail'
238 statusName = 'Failed'
239
240 result = """
241<head>
242 <title>Report Submission</title>
243 <link rel="stylesheet" type="text/css" href="/scanview.css" />
244</head>
245<body>
246<h1>Report Submission</h1>
247<form name="form" action="">
248<table class="form">
249<tr><td>
250<table class="form_group">
251<tr>
252 <td class="form_clabel">Title:</td>
253 <td class="form_value">
254 <input type="text" name="title" size="50" value="%(title)s" disabled>
255 </td>
256</tr>
257<tr>
258 <td class="form_label">Description:</td>
259 <td class="form_value">
260<textarea rows="10" cols="80" name="description" disabled>
261%(description)s
262</textarea>
263 </td>
264</table>
265</td></tr>
266</table>
267</form>
268<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
269%(message)s
270<p>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000271<hr>
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000272<a href="/">Return to Summary</a>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000273</body>
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000274</html>"""%locals()
275 return self.send_string(result)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000276
277 def send_report(self, report):
278 try:
279 keys = self.load_report(report)
280 except IOError:
281 return self.send_internal_error('Invalid report.')
282
283 initialTitle = keys.get('DESC','')
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000284 initialDescription = """\
285Bug generated by the clang static analyzer.
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000286
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000287Description: %s
288File: %s
289Line: %s
290"""%(initialTitle,
291 keys.get('FILE','<unknown>'),
292 keys.get('LINE','<unknown>'))
293
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000294 reporterSelections = []
295 reporterOptions = []
296
297 for i,r in enumerate(self.server.reporters):
298 reporterSelections.append('<option value="%d">%s</option>'%(i,r.getName()))
Daniel Dunbar0174b282008-09-22 00:11:51 +0000299 options = '\n'.join(["""\
300<tr>
301 <td class="form_clabel">%s:</td>
302 <td class="form_value"><input type="text" name="%s_%s"></td>
303</tr>"""%(o,r.getName(),o) for o in r.getParameterNames()])
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000304 if i==0:
Daniel Dunbar0174b282008-09-22 00:11:51 +0000305 display = ''
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000306 else:
307 display = 'none'
Daniel Dunbar0174b282008-09-22 00:11:51 +0000308 reporterOptions.append("""\
309<tr id="%sReporterOptions" style="display:%s">
310 <td class="form_label">%s Options</td>
311 <td class="form_value">
312 <table class="form_inner_group">
313%s
314 </table>
315 </td>
316</tr>
317"""%(r.getName(),display,r.getName(),options))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000318 reporterSelections = '\n'.join(reporterSelections)
319 reporterOptionsDivs = '\n'.join(reporterOptions)
320 reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
321
322 result = """<html>
323<head>
324 <title>File Report</title>
Daniel Dunbar0174b282008-09-22 00:11:51 +0000325 <link rel="stylesheet" type="text/css" href="/scanview.css" />
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000326</head>
327<script language="javascript" type="text/javascript">
328var reporters = %(reportersArray)s;
329function updateReporterOptions() {
330 index = document.getElementById('reporter').selectedIndex;
331 for (var i=0; i < reporters.length; ++i) {
332 o = document.getElementById(reporters[i] + "ReporterOptions");
333 if (i == index) {
Daniel Dunbar0174b282008-09-22 00:11:51 +0000334 o.style.display = "";
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000335 } else {
336 o.style.display = "none";
337 }
338 }
339}
340</script>
341<body>
342<h1>File Report</h1>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000343<form name="form" action="/report_submit" method="post">
Daniel Dunbar0174b282008-09-22 00:11:51 +0000344<input type="hidden" name="report" value="%(report)s">
345
346<table class="form">
347<tr><td>
348<table class="form_group">
349<tr>
350 <td class="form_clabel">Title:</td>
351 <td class="form_value">
352 <input type="text" name="title" size="50" value="%(initialTitle)s">
353 </td>
354</tr>
355<tr>
356 <td class="form_label">Description:</td>
357 <td class="form_value">
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000358<textarea rows="10" cols="80" name="description">
359%(initialDescription)s
Daniel Dunbar0174b282008-09-22 00:11:51 +0000360</textarea>
361 </td>
362</table>
363<br>
364<table class="form_group">
365<tr>
366 <td class="form_clabel">Method:</td>
367 <td class="form_value">
368 <select id="reporter" name="reporter" onChange="updateReporterOptions()">
369 %(reporterSelections)s
370 </select>
371 </td>
372</tr>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000373%(reporterOptionsDivs)s
Daniel Dunbar0174b282008-09-22 00:11:51 +0000374</table>
375<br>
376</td></tr>
377<tr><td class="form_submit">
378 <input align="right" type="submit" name="Submit" value="Submit">
379</td></tr>
380</table>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000381</form>
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000382
Daniel Dunbar0174b282008-09-22 00:11:51 +0000383
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000384<iframe src="/report-%(report)s.html#EndPath" width="100%%" height="40%%"
385 scrolling="auto" frameborder="1">
386 <a href="/report-%(report)s.html#EndPath">View Bug Report</a>
387</iframe>
388
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000389</body>
390</html>"""%locals()
391 return self.send_string(result)
392
393 def send_head(self, fields=None):
394 if fields is None:
395 fields = {}
396 self.fields = fields
397
398 o = urlparse.urlparse(self.path)
399 self.fields = parse_query(o.query, fields)
400 path = posixpath.normpath(urllib.unquote(o.path))
401
402 # Split the components and strip the root prefix.
403 components = path.split('/')[1:]
404
405 # Special case some top-level entries.
406 if components:
407 name = components[0]
Daniel Dunbar0174b282008-09-22 00:11:51 +0000408 if len(components)==2:
409 if name=='report':
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000410 return self.send_report(components[1])
Daniel Dunbar0174b282008-09-22 00:11:51 +0000411 elif len(components)==1:
412 if name=='quit':
413 self.server.halt()
414 return self.send_string('Goodbye.', 'text/plain')
415 elif name=='report_submit':
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000416 return self.send_report_submit()
Daniel Dunbar0174b282008-09-22 00:11:51 +0000417 elif name=='favicon.ico':
Daniel Dunbar2b353482008-09-21 23:02:25 +0000418 return self.send_path(posixpath.join(kResources,'bugcatcher.ico'))
Daniel Dunbar0174b282008-09-22 00:11:51 +0000419 elif name=='scanview.css':
420 return self.send_path(posixpath.join(kResources,'scanview.css'))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000421
422 # Match directory entries.
423 if components[-1] == '':
424 components[-1] = 'index.html'
Daniel Dunbar2b353482008-09-21 23:02:25 +0000425
426 suffix = '/'.join(components)
427
428 # The summary may reference source files on disk using rooted
429 # paths. Make sure these resolve correctly for now.
430 # FIXME: This isn't a very good idea... we should probably
431 # mark rooted paths somehow.
432 if os.path.exists(posixpath.join('/', suffix)):
433 path = posixpath.join('/', suffix)
434 else:
435 path = posixpath.join(self.server.root, suffix)
436
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000437 if self.server.options.debug > 1:
438 print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
439 path)
440 return self.send_path(path)
441
442 def send_404(self):
443 self.send_error(404, "File not found")
444 return None
445
446 def send_path(self, path):
447 ctype = self.guess_type(path)
448 if ctype.startswith('text/'):
449 # Patch file instead
450 return self.send_patched_file(path, ctype)
451 else:
452 mode = 'rb'
453 try:
454 f = open(path, mode)
455 except IOError:
456 return self.send_404()
457 return self.send_file(f, ctype)
458
459 def send_file(self, f, ctype):
460 # Patch files to add links, but skip binary files.
461 self.send_response(200)
462 self.send_header("Content-type", ctype)
463 fs = os.fstat(f.fileno())
464 self.send_header("Content-Length", str(fs[6]))
465 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
466 self.end_headers()
467 return f
468
469 def send_string(self, s, ctype='text/html', headers=True, mtime=None):
470 if headers:
471 self.send_response(200)
472 self.send_header("Content-type", ctype)
473 self.send_header("Content-Length", str(len(s)))
Daniel Dunbar14cad832008-09-21 20:34:58 +0000474 if mtime is None:
475 mtime = self.dynamic_mtime
476 self.send_header("Last-Modified", self.date_time_string(mtime))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000477 self.end_headers()
478 return StringIO.StringIO(s)
479
480 def send_patched_file(self, path, ctype):
481 f = open(path,'r')
482 fs = os.fstat(f.fileno())
483 data = f.read()
Daniel Dunbar14cad832008-09-21 20:34:58 +0000484 for a,b in kReportReplacements:
485 data = a.sub(b, data)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000486 return self.send_string(data, ctype, mtime=fs.st_mtime)
487
488
489def create_server(options, root):
490 import Reporter
491
492 reporters = Reporter.getReporters()
493
494 return ScanViewServer((options.host, options.port),
495 ScanViewRequestHandler,
496 root,
497 reporters,
498 options)