blob: b9839eab45e07c1bebbf1c130de673aec574a470 [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 Dunbar2b0662c2008-09-19 23:32:11 +000027###
28
29__version__ = "0.1"
30
31__all__ = ["create_server"]
32
33class ReporterThread(threading.Thread):
34 def __init__(self, report, reporter, parameters, server):
35 threading.Thread.__init__(self)
36 self.report = report
37 self.server = server
38 self.reporter = reporter
39 self.parameters = parameters
40 self.status = None
41
42 def run(self):
43 result = None
44 try:
45 if self.server.options.debug:
46 print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
47 result = self.reporter.fileReport(self.report, self.parameters)
48 time.sleep(3)
49 if self.server.options.debug:
50 print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
Daniel Dunbar61717b32008-09-20 01:43:16 +000051 except Reporter.ReportFailure,e:
52 s = StringIO.StringIO()
53 print >>s,'Submission Failed<br><pre>'
54 print >>s,e.value
55 print >>s,'</pre>'
56 self.status = s.getvalue()
57 return
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000058 except Exception,e:
59 s = StringIO.StringIO()
60 import traceback
61 print >>s,'Submission Failed<br><pre>'
62 traceback.print_exc(e,file=s)
63 print >>s,'</pre>'
64 self.status = s.getvalue()
65 return
66
67 s = StringIO.StringIO()
68 print >>s, 'Submission Complete!'
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000069 if result is not None:
Daniel Dunbar61717b32008-09-20 01:43:16 +000070 print >>s, '<hr>'
Daniel Dunbar2b0662c2008-09-19 23:32:11 +000071 print >>s, result
72 self.status = s.getvalue()
73
74class ScanViewServer(BaseHTTPServer.HTTPServer):
75 def __init__(self, address, handler, root, reporters, options):
76 BaseHTTPServer.HTTPServer.__init__(self, address, handler)
77 self.root = root
78 self.reporters = reporters
79 self.options = options
80 self.halted = False
81
82 def halt(self):
83 self.halted = True
84 if self.options.debug:
85 print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
86
87 def serve_forever(self):
88 while not self.halted:
89 if self.options.debug > 1:
90 print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
91 try:
92 self.handle_request()
93 except OSError,e:
94 print 'OSError',e.errno
95
Daniel Dunbar14cad832008-09-21 20:34:58 +000096 def finish_request(self, request, client_address):
97 if self.options.autoReload:
98 import ScanView
99 self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
100 BaseHTTPServer.HTTPServer.finish_request(self, request, client_address)
101
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000102 def handle_error(self, request, client_address):
103 # Ignore socket errors
104 info = sys.exc_info()
105 if info and isinstance(info[1], socket.error):
106 if self.options.debug > 1:
107 print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
108 return
Daniel Dunbar14cad832008-09-21 20:34:58 +0000109 BaseHTTPServer.HTTPServer.handle_error(self, request, client_address)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000110
111# Borrowed from Quixote, with simplifications.
112def parse_query(qs, fields=None):
113 if fields is None:
114 fields = {}
115 for chunk in filter(None, qs.split('&')):
116 if '=' not in chunk:
117 name = chunk
118 value = ''
119 else:
120 name, value = chunk.split('=', 1)
121 name = urllib.unquote(name.replace('+', ' '))
122 value = urllib.unquote(value.replace('+', ' '))
123 fields[name] = value
124 return fields
125
126class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
127 server_version = "ScanViewServer/" + __version__
Daniel Dunbar14cad832008-09-21 20:34:58 +0000128 dynamic_mtime = time.time()
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000129
130 def do_HEAD(self):
131 try:
132 SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
133 except Exception,e:
134 self.handle_exception(e)
135
136 def do_GET(self):
137 try:
138 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
139 except Exception,e:
140 self.handle_exception(e)
141
142 def do_POST(self):
143 """Serve a POST request."""
144 try:
145 length = self.headers.getheader('content-length') or "0"
146 try:
147 length = int(length)
148 except:
149 length = 0
150 content = self.rfile.read(length)
151 fields = parse_query(content)
152 f = self.send_head(fields)
153 if f:
154 self.copyfile(f, self.wfile)
155 f.close()
156 except Exception,e:
157 self.handle_exception(e)
158
159 def log_message(self, format, *args):
160 if self.server.options.debug:
161 sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
162 (sys.argv[0],
163 self.address_string(),
164 self.log_date_time_string(),
165 format%args))
166
167 def load_report(self, report):
168 path = os.path.join(self.server.root, 'report-%s.html'%report)
169 data = open(path).read()
170 keys = {}
171 for item in kBugKeyValueRE.finditer(data):
172 k,v = item.groups()
173 keys[k] = v
174 return keys
175
176 def handle_exception(self, exc):
177 import traceback
178 s = StringIO.StringIO()
179 print >>s, "INTERNAL ERROR\n"
180 traceback.print_exc(exc, s)
181 f = self.send_string(s.getvalue(), 'text/plain')
182 if f:
183 self.copyfile(f, self.wfile)
184 f.close()
185
186 def send_internal_error(self, message):
187 return self.send_string('ERROR: %s'%(message,), 'text/plain')
188
189 def send_report_submit(self):
190 s = StringIO.StringIO()
191 report = self.fields.get('report')
192 reporter = self.fields.get('reporter')
193 title = self.fields.get('title')
194 description = self.fields.get('description')
195
196 # Get the reporter and parameters.
197 reporter = self.server.reporters[int(reporter)]
198 parameters = {}
199 for o in reporter.getParameterNames():
200 name = '%s_%s'%(reporter.getName(),o)
201 parameters[o] = self.fields.get(name)
202
203 # Create the report.
204 path = os.path.join(self.server.root, 'report-%s.html'%report)
205 files = [path]
Daniel Dunbar61717b32008-09-20 01:43:16 +0000206 br = Reporter.BugReport(title, description, files)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000207
208 # Send back an initial response and wait for the report to
209 # finish.
210 initial_response = """<html>
211<head><title>Filing Report</title></head>
212<body>
213<h1>Filing Report</h1>
214<b>Report</b>: %(report)s<br>
215<b>Title</b>: %(title)s<br>
216<b>Description</b>: %(description)s<br>
217<hr>
218Submission in progress."""%locals()
219
220 self.send_response(200)
221 self.send_header("Content-type", 'text/html')
222 self.end_headers()
223 self.wfile.write(initial_response)
224 self.wfile.flush()
225
226 # Kick off a reporting thread.
227 t = ReporterThread(br, reporter, parameters, self.server)
228 t.start()
229
230 # Wait for thread to die...
231 while t.isAlive():
232 self.wfile.write('.')
233 self.wfile.flush()
234 time.sleep(.25)
235 submitStatus = t.status
236
237 end_response = """<br>
238%(submitStatus)s
239<hr>
240<a href="/">Home</a>
241</body>
242</html>
243"""%locals()
244 return self.send_string(end_response, headers=False)
245
246 def send_report(self, report):
247 try:
248 keys = self.load_report(report)
249 except IOError:
250 return self.send_internal_error('Invalid report.')
251
252 initialTitle = keys.get('DESC','')
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000253 initialDescription = """\
254Bug generated by the clang static analyzer.
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000255
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000256Description: %s
257File: %s
258Line: %s
259"""%(initialTitle,
260 keys.get('FILE','<unknown>'),
261 keys.get('LINE','<unknown>'))
262
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000263 reporterSelections = []
264 reporterOptions = []
265
266 for i,r in enumerate(self.server.reporters):
267 reporterSelections.append('<option value="%d">%s</option>'%(i,r.getName()))
268 options = '\n'.join(['%s: <input type="text" name="%s_%s"><br>'%(o,r.getName(),o) for o in r.getParameterNames()])
269 if i==0:
270 display = 'inline'
271 else:
272 display = 'none'
273 reporterOptions.append('<div id="%sReporterOptions" style="display:%s">\n<h3>%s Options</h3>%s\n</div>'%(r.getName(),display,r.getName(),options))
274 reporterSelections = '\n'.join(reporterSelections)
275 reporterOptionsDivs = '\n'.join(reporterOptions)
276 reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
277
278 result = """<html>
279<head>
280 <title>File Report</title>
281</head>
282<script language="javascript" type="text/javascript">
283var reporters = %(reportersArray)s;
284function updateReporterOptions() {
285 index = document.getElementById('reporter').selectedIndex;
286 for (var i=0; i < reporters.length; ++i) {
287 o = document.getElementById(reporters[i] + "ReporterOptions");
288 if (i == index) {
289 o.style.display = "inline";
290 } else {
291 o.style.display = "none";
292 }
293 }
294}
295</script>
296<body>
297<h1>File Report</h1>
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000298<hr>
299<form name="form" action="/report_submit" method="post">
300Title:
301<input type="text" name="title" size="50" value="%(initialTitle)s"><br>
302Description:<br>
303<textarea rows="10" cols="80" name="description">
304%(initialDescription)s
305</textarea><br>
306<hr>
307<input type="hidden" name="report" value="%(report)s">
308Method: <select id="reporter" name="reporter" onChange="updateReporterOptions()">
309%(reporterSelections)s
310</select><br>
311<hr>
312%(reporterOptionsDivs)s
313<hr>
314<input type="submit" name="Submit" value="Submit">
315</form>
Daniel Dunbar8b24edb2008-09-21 19:08:54 +0000316
317<iframe src="/report-%(report)s.html#EndPath" width="100%%" height="40%%"
318 scrolling="auto" frameborder="1">
319 <a href="/report-%(report)s.html#EndPath">View Bug Report</a>
320</iframe>
321
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000322</body>
323</html>"""%locals()
324 return self.send_string(result)
325
326 def send_head(self, fields=None):
327 if fields is None:
328 fields = {}
329 self.fields = fields
330
331 o = urlparse.urlparse(self.path)
332 self.fields = parse_query(o.query, fields)
333 path = posixpath.normpath(urllib.unquote(o.path))
334
335 # Split the components and strip the root prefix.
336 components = path.split('/')[1:]
337
338 # Special case some top-level entries.
339 if components:
340 name = components[0]
341 if name=='quit':
342 self.server.halt()
343 return self.send_string('Goodbye.', 'text/plain')
344 elif name=='report':
345 if len(components)==2:
346 return self.send_report(components[1])
347 else:
348 return self.send_404()
349 elif name=='report_submit':
350 if len(components)==1:
351 return self.send_report_submit()
352 else:
353 return self.send_404()
354
355 # Match directory entries.
356 if components[-1] == '':
357 components[-1] = 'index.html'
358
359 path = posixpath.join(self.server.root, '/'.join(components))
360 if self.server.options.debug > 1:
361 print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
362 path)
363 return self.send_path(path)
364
365 def send_404(self):
366 self.send_error(404, "File not found")
367 return None
368
369 def send_path(self, path):
370 ctype = self.guess_type(path)
371 if ctype.startswith('text/'):
372 # Patch file instead
373 return self.send_patched_file(path, ctype)
374 else:
375 mode = 'rb'
376 try:
377 f = open(path, mode)
378 except IOError:
379 return self.send_404()
380 return self.send_file(f, ctype)
381
382 def send_file(self, f, ctype):
383 # Patch files to add links, but skip binary files.
384 self.send_response(200)
385 self.send_header("Content-type", ctype)
386 fs = os.fstat(f.fileno())
387 self.send_header("Content-Length", str(fs[6]))
388 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
389 self.end_headers()
390 return f
391
392 def send_string(self, s, ctype='text/html', headers=True, mtime=None):
393 if headers:
394 self.send_response(200)
395 self.send_header("Content-type", ctype)
396 self.send_header("Content-Length", str(len(s)))
Daniel Dunbar14cad832008-09-21 20:34:58 +0000397 if mtime is None:
398 mtime = self.dynamic_mtime
399 self.send_header("Last-Modified", self.date_time_string(mtime))
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000400 self.end_headers()
401 return StringIO.StringIO(s)
402
403 def send_patched_file(self, path, ctype):
404 f = open(path,'r')
405 fs = os.fstat(f.fileno())
406 data = f.read()
Daniel Dunbar14cad832008-09-21 20:34:58 +0000407 for a,b in kReportReplacements:
408 data = a.sub(b, data)
Daniel Dunbar2b0662c2008-09-19 23:32:11 +0000409 return self.send_string(data, ctype, mtime=fs.st_mtime)
410
411
412def create_server(options, root):
413 import Reporter
414
415 reporters = Reporter.getReporters()
416
417 return ScanViewServer((options.host, options.port),
418 ScanViewRequestHandler,
419 root,
420 reporters,
421 options)