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