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