blob: 9e43ddf881c1d02dc322660d8eb5bfcc05b15ca1 [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>
215<head><title>Filing Report</title></head>
216<body>
217<h1>Filing Report</h1>
218<b>Report</b>: %(report)s<br>
219<b>Title</b>: %(title)s<br>
220<b>Description</b>: %(description)s<br>
221<hr>
222Submission in progress."""%locals()
223
224 self.send_response(200)
225 self.send_header("Content-type", 'text/html')
226 self.end_headers()
227 self.wfile.write(initial_response)
228 self.wfile.flush()
229
230 # Kick off a reporting thread.
231 t = ReporterThread(br, reporter, parameters, self.server)
232 t.start()
233
234 # Wait for thread to die...
235 while t.isAlive():
236 self.wfile.write('.')
237 self.wfile.flush()
238 time.sleep(.25)
239 submitStatus = t.status
240
241 end_response = """<br>
242%(submitStatus)s
243<hr>
244<a href="/">Home</a>
245</body>
246</html>
247"""%locals()
248 return self.send_string(end_response, headers=False)
249
250 def send_report(self, report):
251 try:
252 keys = self.load_report(report)
253 except IOError:
254 return self.send_internal_error('Invalid report.')
255
256 initialTitle = keys.get('DESC','')
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000257 initialDescription = """\
258Bug generated by the clang static analyzer.
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000259
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000260Description: %s
261File: %s
262Line: %s
263"""%(initialTitle,
264 keys.get('FILE','<unknown>'),
265 keys.get('LINE','<unknown>'))
266
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000267 reporterSelections = []
268 reporterOptions = []
269
270 for i,r in enumerate(self.server.reporters):
271 reporterSelections.append('<option value="%d">%s</option>'%(i,r.getName()))
272 options = '\n'.join(['%s: <input type="text" name="%s_%s"><br>'%(o,r.getName(),o) for o in r.getParameterNames()])
273 if i==0:
274 display = 'inline'
275 else:
276 display = 'none'
277 reporterOptions.append('<div id="%sReporterOptions" style="display:%s">\n<h3>%s Options</h3>%s\n</div>'%(r.getName(),display,r.getName(),options))
278 reporterSelections = '\n'.join(reporterSelections)
279 reporterOptionsDivs = '\n'.join(reporterOptions)
280 reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
281
282 result = """<html>
283<head>
284 <title>File Report</title>
285</head>
286<script language="javascript" type="text/javascript">
287var reporters = %(reportersArray)s;
288function updateReporterOptions() {
289 index = document.getElementById('reporter').selectedIndex;
290 for (var i=0; i < reporters.length; ++i) {
291 o = document.getElementById(reporters[i] + "ReporterOptions");
292 if (i == index) {
293 o.style.display = "inline";
294 } else {
295 o.style.display = "none";
296 }
297 }
298}
299</script>
300<body>
301<h1>File Report</h1>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000302<hr>
303<form name="form" action="/report_submit" method="post">
304Title:
305<input type="text" name="title" size="50" value="%(initialTitle)s"><br>
306Description:<br>
307<textarea rows="10" cols="80" name="description">
308%(initialDescription)s
309</textarea><br>
310<hr>
311<input type="hidden" name="report" value="%(report)s">
312Method: <select id="reporter" name="reporter" onChange="updateReporterOptions()">
313%(reporterSelections)s
314</select><br>
315<hr>
316%(reporterOptionsDivs)s
317<hr>
318<input type="submit" name="Submit" value="Submit">
319</form>
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000320
321<iframe src="/report-%(report)s.html#EndPath" width="100%%" height="40%%"
322 scrolling="auto" frameborder="1">
323 <a href="/report-%(report)s.html#EndPath">View Bug Report</a>
324</iframe>
325
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000326</body>
327</html>"""%locals()
328 return self.send_string(result)
329
330 def send_head(self, fields=None):
331 if fields is None:
332 fields = {}
333 self.fields = fields
334
335 o = urlparse.urlparse(self.path)
336 self.fields = parse_query(o.query, fields)
337 path = posixpath.normpath(urllib.unquote(o.path))
338
339 # Split the components and strip the root prefix.
340 components = path.split('/')[1:]
341
342 # Special case some top-level entries.
343 if components:
344 name = components[0]
345 if name=='quit':
346 self.server.halt()
347 return self.send_string('Goodbye.', 'text/plain')
348 elif name=='report':
349 if len(components)==2:
350 return self.send_report(components[1])
351 else:
352 return self.send_404()
353 elif name=='report_submit':
354 if len(components)==1:
355 return self.send_report_submit()
356 else:
357 return self.send_404()
Daniel Dunbar2b353482008-09-21 23:02:25 +0000358 elif name=='favicon.ico':
359 if len(components)==1:
360 return self.send_path(posixpath.join(kResources,'bugcatcher.ico'))
361 else:
362 return self.send_404()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000363
364 # Match directory entries.
365 if components[-1] == '':
366 components[-1] = 'index.html'
Daniel Dunbar2b353482008-09-21 23:02:25 +0000367
368 suffix = '/'.join(components)
369
370 # The summary may reference source files on disk using rooted
371 # paths. Make sure these resolve correctly for now.
372 # FIXME: This isn't a very good idea... we should probably
373 # mark rooted paths somehow.
374 if os.path.exists(posixpath.join('/', suffix)):
375 path = posixpath.join('/', suffix)
376 else:
377 path = posixpath.join(self.server.root, suffix)
378
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000379 if self.server.options.debug > 1:
380 print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
381 path)
382 return self.send_path(path)
383
384 def send_404(self):
385 self.send_error(404, "File not found")
386 return None
387
388 def send_path(self, path):
389 ctype = self.guess_type(path)
390 if ctype.startswith('text/'):
391 # Patch file instead
392 return self.send_patched_file(path, ctype)
393 else:
394 mode = 'rb'
395 try:
396 f = open(path, mode)
397 except IOError:
398 return self.send_404()
399 return self.send_file(f, ctype)
400
401 def send_file(self, f, ctype):
402 # Patch files to add links, but skip binary files.
403 self.send_response(200)
404 self.send_header("Content-type", ctype)
405 fs = os.fstat(f.fileno())
406 self.send_header("Content-Length", str(fs[6]))
407 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
408 self.end_headers()
409 return f
410
411 def send_string(self, s, ctype='text/html', headers=True, mtime=None):
412 if headers:
413 self.send_response(200)
414 self.send_header("Content-type", ctype)
415 self.send_header("Content-Length", str(len(s)))
Daniel Dunbar14cad832008-09-21 20:34:58 +0000416 if mtime is None:
417 mtime = self.dynamic_mtime
418 self.send_header("Last-Modified", self.date_time_string(mtime))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000419 self.end_headers()
420 return StringIO.StringIO(s)
421
422 def send_patched_file(self, path, ctype):
423 f = open(path,'r')
424 fs = os.fstat(f.fileno())
425 data = f.read()
Daniel Dunbar14cad832008-09-21 20:34:58 +0000426 for a,b in kReportReplacements:
427 data = a.sub(b, data)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000428 return self.send_string(data, ctype, mtime=fs.st_mtime)
429
430
431def create_server(options, root):
432 import Reporter
433
434 reporters = Reporter.getReporters()
435
436 return ScanViewServer((options.host, options.port),
437 ScanViewRequestHandler,
438 root,
439 reporters,
440 options)