blob: 6b70b283ba4bb4ee42431bd3d5f7206a38c66cb9 [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
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000179 def submit_bug(self):
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000180 title = self.fields.get('title')
181 description = self.fields.get('description')
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000182 report = self.fields.get('report')
183 reporter = self.fields.get('reporter')
184
185 # Type check form parameters.
186 reportPath = posixpath.join(self.server.root,
187 'report-%s.html' % report)
188 if not posixpath.exists(reportPath):
189 return (False, "Invalid report ID.")
190 if not title:
191 return (False, "Missing title.")
192 if not description:
193 return (False, "Missing description.")
194 try:
195 reporter = int(reporter)
196 except:
197 return (False, "Invalid report method.")
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000198
199 # Get the reporter and parameters.
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000200 reporter = self.server.reporters[reporter]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000201 parameters = {}
202 for o in reporter.getParameterNames():
203 name = '%s_%s'%(reporter.getName(),o)
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000204 if name not in self.fields:
205 return (False,
206 'Missing field "%s" for %s report method.'%(name,
207 reporter.getName()))
208 parameters[o] = self.fields[name]
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000209
210 # Create the report.
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000211 bug = Reporter.BugReport(title, description, [reportPath])
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000212
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000213 # Kick off a reporting thread.
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000214 t = ReporterThread(bug, reporter, parameters, self.server)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000215 t.start()
216
217 # Wait for thread to die...
218 while t.isAlive():
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000219 time.sleep(.25)
220 submitStatus = t.status
221
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000222 return (t.success, t.status)
223
224 def send_report_submit(self):
225 title = self.fields.get('title')
226 description = self.fields.get('description')
227
228 res,message = self.submit_bug()
229
230 if res:
231 statusClass = 'SubmitOk'
232 statusName = 'Succeeded'
233 else:
234 statusClass = 'SubmitFail'
235 statusName = 'Failed'
236
237 result = """
238<head>
239 <title>Report Submission</title>
240 <link rel="stylesheet" type="text/css" href="/scanview.css" />
241</head>
242<body>
243<h1>Report Submission</h1>
244<form name="form" action="">
245<table class="form">
246<tr><td>
247<table class="form_group">
248<tr>
249 <td class="form_clabel">Title:</td>
250 <td class="form_value">
251 <input type="text" name="title" size="50" value="%(title)s" disabled>
252 </td>
253</tr>
254<tr>
255 <td class="form_label">Description:</td>
256 <td class="form_value">
257<textarea rows="10" cols="80" name="description" disabled>
258%(description)s
259</textarea>
260 </td>
261</table>
262</td></tr>
263</table>
264</form>
265<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
266%(message)s
267<p>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000268<hr>
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000269<a href="/">Return to Summary</a>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000270</body>
Daniel Dunbar55c5aa42008-09-22 01:21:30 +0000271</html>"""%locals()
272 return self.send_string(result)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000273
274 def send_report(self, report):
275 try:
276 keys = self.load_report(report)
277 except IOError:
Daniel Dunbarfe421482008-09-22 01:40:14 +0000278 return self.send_error(400, 'Invalid report.')
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000279
280 initialTitle = keys.get('DESC','')
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000281 initialDescription = """\
282Bug generated by the clang static analyzer.
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000283
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000284Description: %s
285File: %s
286Line: %s
287"""%(initialTitle,
288 keys.get('FILE','<unknown>'),
289 keys.get('LINE','<unknown>'))
290
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000291 reporterSelections = []
292 reporterOptions = []
293
294 for i,r in enumerate(self.server.reporters):
295 reporterSelections.append('<option value="%d">%s</option>'%(i,r.getName()))
Daniel Dunbar0174b282008-09-22 00:11:51 +0000296 options = '\n'.join(["""\
297<tr>
298 <td class="form_clabel">%s:</td>
299 <td class="form_value"><input type="text" name="%s_%s"></td>
300</tr>"""%(o,r.getName(),o) for o in r.getParameterNames()])
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000301 if i==0:
Daniel Dunbar0174b282008-09-22 00:11:51 +0000302 display = ''
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000303 else:
304 display = 'none'
Daniel Dunbar0174b282008-09-22 00:11:51 +0000305 reporterOptions.append("""\
306<tr id="%sReporterOptions" style="display:%s">
307 <td class="form_label">%s Options</td>
308 <td class="form_value">
309 <table class="form_inner_group">
310%s
311 </table>
312 </td>
313</tr>
314"""%(r.getName(),display,r.getName(),options))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000315 reporterSelections = '\n'.join(reporterSelections)
316 reporterOptionsDivs = '\n'.join(reporterOptions)
317 reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
318
319 result = """<html>
320<head>
321 <title>File Report</title>
Daniel Dunbar0174b282008-09-22 00:11:51 +0000322 <link rel="stylesheet" type="text/css" href="/scanview.css" />
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000323</head>
324<script language="javascript" type="text/javascript">
325var reporters = %(reportersArray)s;
326function updateReporterOptions() {
327 index = document.getElementById('reporter').selectedIndex;
328 for (var i=0; i < reporters.length; ++i) {
329 o = document.getElementById(reporters[i] + "ReporterOptions");
330 if (i == index) {
Daniel Dunbar0174b282008-09-22 00:11:51 +0000331 o.style.display = "";
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000332 } else {
333 o.style.display = "none";
334 }
335 }
336}
337</script>
Daniel Dunbarfe421482008-09-22 01:40:14 +0000338<body onLoad="updateReporterOptions()">
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000339<h1>File Report</h1>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000340<form name="form" action="/report_submit" method="post">
Daniel Dunbar0174b282008-09-22 00:11:51 +0000341<input type="hidden" name="report" value="%(report)s">
342
343<table class="form">
344<tr><td>
345<table class="form_group">
346<tr>
347 <td class="form_clabel">Title:</td>
348 <td class="form_value">
349 <input type="text" name="title" size="50" value="%(initialTitle)s">
350 </td>
351</tr>
352<tr>
353 <td class="form_label">Description:</td>
354 <td class="form_value">
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000355<textarea rows="10" cols="80" name="description">
356%(initialDescription)s
Daniel Dunbar0174b282008-09-22 00:11:51 +0000357</textarea>
358 </td>
359</table>
360<br>
361<table class="form_group">
362<tr>
363 <td class="form_clabel">Method:</td>
364 <td class="form_value">
365 <select id="reporter" name="reporter" onChange="updateReporterOptions()">
366 %(reporterSelections)s
367 </select>
368 </td>
369</tr>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000370%(reporterOptionsDivs)s
Daniel Dunbar0174b282008-09-22 00:11:51 +0000371</table>
372<br>
373</td></tr>
374<tr><td class="form_submit">
375 <input align="right" type="submit" name="Submit" value="Submit">
376</td></tr>
377</table>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000378</form>
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000379
Daniel Dunbar0174b282008-09-22 00:11:51 +0000380
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000381<iframe src="/report-%(report)s.html#EndPath" width="100%%" height="40%%"
382 scrolling="auto" frameborder="1">
383 <a href="/report-%(report)s.html#EndPath">View Bug Report</a>
384</iframe>
385
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000386</body>
387</html>"""%locals()
388 return self.send_string(result)
389
390 def send_head(self, fields=None):
391 if fields is None:
392 fields = {}
393 self.fields = fields
394
395 o = urlparse.urlparse(self.path)
396 self.fields = parse_query(o.query, fields)
397 path = posixpath.normpath(urllib.unquote(o.path))
398
399 # Split the components and strip the root prefix.
400 components = path.split('/')[1:]
401
402 # Special case some top-level entries.
403 if components:
404 name = components[0]
Daniel Dunbar0174b282008-09-22 00:11:51 +0000405 if len(components)==2:
406 if name=='report':
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000407 return self.send_report(components[1])
Daniel Dunbar0174b282008-09-22 00:11:51 +0000408 elif len(components)==1:
409 if name=='quit':
410 self.server.halt()
411 return self.send_string('Goodbye.', 'text/plain')
412 elif name=='report_submit':
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000413 return self.send_report_submit()
Daniel Dunbar0174b282008-09-22 00:11:51 +0000414 elif name=='favicon.ico':
Daniel Dunbar2b353482008-09-21 23:02:25 +0000415 return self.send_path(posixpath.join(kResources,'bugcatcher.ico'))
Daniel Dunbar0174b282008-09-22 00:11:51 +0000416 elif name=='scanview.css':
417 return self.send_path(posixpath.join(kResources,'scanview.css'))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000418
419 # Match directory entries.
420 if components[-1] == '':
421 components[-1] = 'index.html'
Daniel Dunbar2b353482008-09-21 23:02:25 +0000422
423 suffix = '/'.join(components)
424
425 # The summary may reference source files on disk using rooted
426 # paths. Make sure these resolve correctly for now.
427 # FIXME: This isn't a very good idea... we should probably
428 # mark rooted paths somehow.
429 if os.path.exists(posixpath.join('/', suffix)):
430 path = posixpath.join('/', suffix)
431 else:
432 path = posixpath.join(self.server.root, suffix)
433
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000434 if self.server.options.debug > 1:
435 print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
436 path)
437 return self.send_path(path)
438
439 def send_404(self):
440 self.send_error(404, "File not found")
441 return None
442
443 def send_path(self, path):
444 ctype = self.guess_type(path)
445 if ctype.startswith('text/'):
446 # Patch file instead
447 return self.send_patched_file(path, ctype)
448 else:
449 mode = 'rb'
450 try:
451 f = open(path, mode)
452 except IOError:
453 return self.send_404()
454 return self.send_file(f, ctype)
455
456 def send_file(self, f, ctype):
457 # Patch files to add links, but skip binary files.
458 self.send_response(200)
459 self.send_header("Content-type", ctype)
460 fs = os.fstat(f.fileno())
461 self.send_header("Content-Length", str(fs[6]))
462 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
463 self.end_headers()
464 return f
465
466 def send_string(self, s, ctype='text/html', headers=True, mtime=None):
467 if headers:
468 self.send_response(200)
469 self.send_header("Content-type", ctype)
470 self.send_header("Content-Length", str(len(s)))
Daniel Dunbar14cad832008-09-21 20:34:58 +0000471 if mtime is None:
472 mtime = self.dynamic_mtime
473 self.send_header("Last-Modified", self.date_time_string(mtime))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000474 self.end_headers()
475 return StringIO.StringIO(s)
476
477 def send_patched_file(self, path, ctype):
Daniel Dunbarfe421482008-09-22 01:40:14 +0000478 try:
479 f = open(path,'r')
480 except IOError:
481 return self.send_404()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000482 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)