blob: 837adae0f76356db9c1a01b7d46cfe6a1a1f6029 [file] [log] [blame]
Daniel Dunbare33d3682008-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
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +000013import itertools
Daniel Dunbare33d3682008-09-19 23:32:11 +000014
Daniel Dunbar54722492008-09-20 01:43:16 +000015import Reporter
Daniel Dunbar8dcd9402008-09-22 02:27:45 +000016import ConfigParser
Daniel Dunbare33d3682008-09-19 23:32:11 +000017
Daniel Dunbar17fded62008-09-22 21:43:43 +000018###
19# Various patterns matched or replaced by server.
20
21kReportFileRE = re.compile('(.*/)?report-(.*)\\.html')
Daniel Dunbare33d3682008-09-19 23:32:11 +000022
Daniel Dunbare33d3682008-09-19 23:32:11 +000023kBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
24
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +000025# <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" -->
26
27kReportCrashEntryRE = re.compile('<!-- REPORTPROBLEM (.*?)-->')
28kReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"')
29
Daniel Dunbar4d20cf72008-09-22 18:44:46 +000030kReportReplacements = []
Daniel Dunbarcb028b02008-09-21 20:34:58 +000031
Daniel Dunbar4d20cf72008-09-22 18:44:46 +000032# Add custom javascript.
33kReportReplacements.append((re.compile('<!-- SUMMARYENDHEAD -->'), """\
34<script language="javascript" type="text/javascript">
35function load(url) {
36 if (window.XMLHttpRequest) {
37 req = new XMLHttpRequest();
38 } else if (window.ActiveXObject) {
39 req = new ActiveXObject("Microsoft.XMLHTTP");
40 }
41 if (req != undefined) {
42 req.open("GET", url, true);
43 req.send("");
44 }
45}
46</script>"""))
47
48# Insert additional columns.
49kReportReplacements.append((re.compile('<!-- REPORTBUGCOL -->'),
50 '<td></td><td></td>'))
51
52# Insert report bug and open file links.
53kReportReplacements.append((re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'),
54 ('<td class="Button"><a href="report/\\1">Report Bug</a></td>' +
55 '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>')))
56
Daniel Dunbar17fded62008-09-22 21:43:43 +000057kReportReplacements.append((re.compile('<!-- REPORTHEADER -->'),
58 '<h3><a href="/">Summary</a> > Report %(report)s</h3>'))
59
60kReportReplacements.append((re.compile('<!-- REPORTSUMMARYEXTRA -->'),
61 '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>'))
62
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +000063# Insert report crashes link.
64
Daniel Dunbarb5f9a4e2008-09-25 19:59:17 +000065# Disabled for the time being until we decide exactly when this should
66# be enabled. Also the radar reporter needs to be fixed to report
67# multiple files.
68
69#kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'),
70# '<br>These files will automatically be attached to ' +
71# 'reports filed here: <a href="report_crashes">Report Crashes</a>.'))
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +000072
Daniel Dunbar4d20cf72008-09-22 18:44:46 +000073###
Daniel Dunbarb131c8a2008-09-21 23:02:25 +000074# Other simple parameters
75
76kResources = posixpath.join(posixpath.dirname(__file__), 'Resources')
Daniel Dunbar8dcd9402008-09-22 02:27:45 +000077kConfigPath = os.path.expanduser('~/.scanview.cfg')
Daniel Dunbarb131c8a2008-09-21 23:02:25 +000078
Daniel Dunbare33d3682008-09-19 23:32:11 +000079###
80
81__version__ = "0.1"
82
83__all__ = ["create_server"]
84
85class ReporterThread(threading.Thread):
86 def __init__(self, report, reporter, parameters, server):
87 threading.Thread.__init__(self)
88 self.report = report
89 self.server = server
90 self.reporter = reporter
91 self.parameters = parameters
Daniel Dunbar47cceff2008-09-22 01:21:30 +000092 self.success = False
Daniel Dunbare33d3682008-09-19 23:32:11 +000093 self.status = None
94
95 def run(self):
96 result = None
97 try:
98 if self.server.options.debug:
99 print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000100 self.status = self.reporter.fileReport(self.report, self.parameters)
101 self.success = True
Daniel Dunbare33d3682008-09-19 23:32:11 +0000102 time.sleep(3)
103 if self.server.options.debug:
104 print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
Daniel Dunbar54722492008-09-20 01:43:16 +0000105 except Reporter.ReportFailure,e:
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000106 self.status = e.value
Daniel Dunbare33d3682008-09-19 23:32:11 +0000107 except Exception,e:
108 s = StringIO.StringIO()
109 import traceback
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000110 print >>s,'<b>Unhandled Exception</b><br><pre>'
Daniel Dunbare33d3682008-09-19 23:32:11 +0000111 traceback.print_exc(e,file=s)
112 print >>s,'</pre>'
113 self.status = s.getvalue()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000114
115class ScanViewServer(BaseHTTPServer.HTTPServer):
116 def __init__(self, address, handler, root, reporters, options):
117 BaseHTTPServer.HTTPServer.__init__(self, address, handler)
118 self.root = root
119 self.reporters = reporters
120 self.options = options
121 self.halted = False
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000122 self.config = None
123 self.load_config()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000124
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000125 def load_config(self):
126 self.config = ConfigParser.RawConfigParser()
127
128 # Add defaults
129 self.config.add_section('ScanView')
Daniel Dunbar7ad535d2008-09-22 02:53:12 +0000130 for r in self.reporters:
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000131 self.config.add_section(r.getName())
Ted Kremenek19c88202008-09-30 16:08:13 +0000132 for p in r.getParameters():
Ted Kremenek480bc342008-09-30 16:11:33 +0000133 if p.saveConfigValue():
Ted Kremenek19c88202008-09-30 16:08:13 +0000134 self.config.set(r.getName(), p.getName(), '')
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000135
136 # Ignore parse errors
137 try:
138 self.config.read([kConfigPath])
139 except:
140 pass
141
142 # Save on exit
143 import atexit
144 atexit.register(lambda: self.save_config())
145
146 def save_config(self):
147 # Ignore errors (only called on exit).
148 try:
149 f = open(kConfigPath,'w')
150 self.config.write(f)
151 f.close()
152 except:
153 pass
154
Daniel Dunbare33d3682008-09-19 23:32:11 +0000155 def halt(self):
156 self.halted = True
157 if self.options.debug:
158 print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
159
160 def serve_forever(self):
161 while not self.halted:
162 if self.options.debug > 1:
163 print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
164 try:
165 self.handle_request()
166 except OSError,e:
167 print 'OSError',e.errno
168
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000169 def finish_request(self, request, client_address):
170 if self.options.autoReload:
171 import ScanView
172 self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
173 BaseHTTPServer.HTTPServer.finish_request(self, request, client_address)
174
Daniel Dunbare33d3682008-09-19 23:32:11 +0000175 def handle_error(self, request, client_address):
176 # Ignore socket errors
177 info = sys.exc_info()
178 if info and isinstance(info[1], socket.error):
179 if self.options.debug > 1:
180 print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
181 return
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000182 BaseHTTPServer.HTTPServer.handle_error(self, request, client_address)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000183
184# Borrowed from Quixote, with simplifications.
185def parse_query(qs, fields=None):
186 if fields is None:
187 fields = {}
188 for chunk in filter(None, qs.split('&')):
189 if '=' not in chunk:
190 name = chunk
191 value = ''
192 else:
193 name, value = chunk.split('=', 1)
194 name = urllib.unquote(name.replace('+', ' '))
195 value = urllib.unquote(value.replace('+', ' '))
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000196 item = fields.get(name)
197 if item is None:
198 fields[name] = [value]
199 else:
200 item.append(value)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000201 return fields
202
203class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
204 server_version = "ScanViewServer/" + __version__
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000205 dynamic_mtime = time.time()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000206
207 def do_HEAD(self):
208 try:
209 SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
210 except Exception,e:
211 self.handle_exception(e)
212
213 def do_GET(self):
214 try:
215 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
216 except Exception,e:
217 self.handle_exception(e)
218
219 def do_POST(self):
220 """Serve a POST request."""
221 try:
222 length = self.headers.getheader('content-length') or "0"
223 try:
224 length = int(length)
225 except:
226 length = 0
227 content = self.rfile.read(length)
228 fields = parse_query(content)
229 f = self.send_head(fields)
230 if f:
231 self.copyfile(f, self.wfile)
232 f.close()
233 except Exception,e:
234 self.handle_exception(e)
235
236 def log_message(self, format, *args):
237 if self.server.options.debug:
238 sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
239 (sys.argv[0],
240 self.address_string(),
241 self.log_date_time_string(),
242 format%args))
243
244 def load_report(self, report):
245 path = os.path.join(self.server.root, 'report-%s.html'%report)
246 data = open(path).read()
247 keys = {}
248 for item in kBugKeyValueRE.finditer(data):
249 k,v = item.groups()
250 keys[k] = v
251 return keys
252
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000253 def load_crashes(self):
254 path = posixpath.join(self.server.root, 'index.html')
255 data = open(path).read()
256 problems = []
257 for item in kReportCrashEntryRE.finditer(data):
258 fieldData = item.group(1)
259 fields = dict([i.groups() for i in
260 kReportCrashEntryKeyValueRE.finditer(fieldData)])
261 problems.append(fields)
262 return problems
263
Daniel Dunbare33d3682008-09-19 23:32:11 +0000264 def handle_exception(self, exc):
265 import traceback
266 s = StringIO.StringIO()
267 print >>s, "INTERNAL ERROR\n"
268 traceback.print_exc(exc, s)
269 f = self.send_string(s.getvalue(), 'text/plain')
270 if f:
271 self.copyfile(f, self.wfile)
272 f.close()
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000273
274 def get_scalar_field(self, name):
275 if name in self.fields:
276 return self.fields[name][0]
277 else:
278 return None
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000279
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000280 def submit_bug(self, c):
281 title = self.get_scalar_field('title')
282 description = self.get_scalar_field('description')
283 report = self.get_scalar_field('report')
284 reporterIndex = self.get_scalar_field('reporter')
285 files = []
286 for fileID in self.fields.get('files',[]):
287 try:
288 i = int(fileID)
289 except:
290 i = None
291 if i is None or i<0 or i>=len(c.files):
292 return (False, 'Invalid file ID')
293 files.append(c.files[i])
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000294
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000295 if not title:
296 return (False, "Missing title.")
297 if not description:
298 return (False, "Missing description.")
299 try:
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000300 reporterIndex = int(reporterIndex)
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000301 except:
302 return (False, "Invalid report method.")
Daniel Dunbare33d3682008-09-19 23:32:11 +0000303
304 # Get the reporter and parameters.
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000305 reporter = self.server.reporters[reporterIndex]
Daniel Dunbare33d3682008-09-19 23:32:11 +0000306 parameters = {}
Ted Kremenek19c88202008-09-30 16:08:13 +0000307 for o in reporter.getParameters():
308 name = '%s_%s'%(reporter.getName(),o.getName())
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000309 if name not in self.fields:
310 return (False,
311 'Missing field "%s" for %s report method.'%(name,
312 reporter.getName()))
Ted Kremenek19c88202008-09-30 16:08:13 +0000313 parameters[o.getName()] = self.get_scalar_field(name)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000314
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000315 # Update config defaults.
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000316 if report != 'None':
317 self.server.config.set('ScanView', 'reporter', reporterIndex)
Ted Kremenek19c88202008-09-30 16:08:13 +0000318 for o in reporter.getParameters():
Ted Kremenek480bc342008-09-30 16:11:33 +0000319 if o.saveConfigValue():
Ted Kremenek19c88202008-09-30 16:08:13 +0000320 name = o.getName()
321 self.server.config.set(reporter.getName(), name, parameters[name])
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000322
Daniel Dunbare33d3682008-09-19 23:32:11 +0000323 # Create the report.
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000324 bug = Reporter.BugReport(title, description, files)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000325
Daniel Dunbare33d3682008-09-19 23:32:11 +0000326 # Kick off a reporting thread.
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000327 t = ReporterThread(bug, reporter, parameters, self.server)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000328 t.start()
329
330 # Wait for thread to die...
331 while t.isAlive():
Daniel Dunbare33d3682008-09-19 23:32:11 +0000332 time.sleep(.25)
333 submitStatus = t.status
334
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000335 return (t.success, t.status)
336
337 def send_report_submit(self):
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000338 report = self.get_scalar_field('report')
339 c = self.get_report_context(report)
340 if c.reportSource is None:
341 reportingFor = "Report Crashes > "
342 fileBug = """\
343<a href="/report_crashes">File Bug</a> > """%locals()
344 else:
345 reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource,
346 report)
347 fileBug = '<a href="/report/%s">File Bug</a> > ' % report
348 title = self.get_scalar_field('title')
349 description = self.get_scalar_field('description')
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000350
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000351 res,message = self.submit_bug(c)
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000352
353 if res:
354 statusClass = 'SubmitOk'
355 statusName = 'Succeeded'
356 else:
357 statusClass = 'SubmitFail'
358 statusName = 'Failed'
359
360 result = """
361<head>
Daniel Dunbarc2dd3452008-09-22 03:08:32 +0000362 <title>Bug Submission</title>
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000363 <link rel="stylesheet" type="text/css" href="/scanview.css" />
364</head>
365<body>
Daniel Dunbarc2dd3452008-09-22 03:08:32 +0000366<h3>
367<a href="/">Summary</a> >
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000368%(reportingFor)s
369%(fileBug)s
Daniel Dunbarc2dd3452008-09-22 03:08:32 +0000370Submit</h3>
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000371<form name="form" action="">
372<table class="form">
373<tr><td>
374<table class="form_group">
375<tr>
376 <td class="form_clabel">Title:</td>
377 <td class="form_value">
378 <input type="text" name="title" size="50" value="%(title)s" disabled>
379 </td>
380</tr>
381<tr>
382 <td class="form_label">Description:</td>
383 <td class="form_value">
384<textarea rows="10" cols="80" name="description" disabled>
385%(description)s
386</textarea>
387 </td>
388</table>
389</td></tr>
390</table>
391</form>
392<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
393%(message)s
394<p>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000395<hr>
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000396<a href="/">Return to Summary</a>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000397</body>
Daniel Dunbar47cceff2008-09-22 01:21:30 +0000398</html>"""%locals()
399 return self.send_string(result)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000400
Daniel Dunbar28870e12008-09-22 18:05:49 +0000401 def send_open_report(self, report):
402 try:
403 keys = self.load_report(report)
404 except IOError:
405 return self.send_error(400, 'Invalid report.')
406
407 file = keys.get('FILE')
408 if not file or not posixpath.exists(file):
409 return self.send_error(400, 'File does not exist: "%s"' % file)
410
411 import startfile
412 if self.server.options.debug:
413 print >>sys.stderr, '%s: SERVER: opening "%s"'%(sys.argv[0],
414 file)
415
416 status = startfile.open(file)
417 if status:
418 res = 'Opened: "%s"' % file
419 else:
420 res = 'Open failed: "%s"' % file
421
422 return self.send_string(res, 'text/plain')
423
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000424 def get_report_context(self, report):
425 class Context:
426 pass
427 if report is None or report == 'None':
428 data = self.load_crashes()
429 # Don't allow empty reports.
430 if not data:
431 raise ValueError, 'No crashes detected!'
432 c = Context()
433 c.title = 'clang static analyzer failures'
Daniel Dunbare33d3682008-09-19 23:32:11 +0000434
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000435 stderrSummary = ""
436 for item in data:
437 if 'stderr' in item:
438 path = posixpath.join(self.server.root, item['stderr'])
439 if os.path.exists(path):
440 lns = itertools.islice(open(path), 0, 10)
441 stderrSummary += '%s\n--\n%s' % (item.get('src',
442 '<unknown>'),
443 ''.join(lns))
444
445 c.description = """\
446The clang static analyzer failed on these inputs:
447%s
448
449STDERR Summary
450--------------
451%s
452""" % ('\n'.join([item.get('src','<unknown>') for item in data]),
453 stderrSummary)
454 c.reportSource = None
455 c.navMarkup = "Report Crashes > "
456 c.files = []
457 for item in data:
458 c.files.append(item.get('src',''))
459 c.files.append(posixpath.join(self.server.root,
460 item.get('file','')))
461 c.files.append(posixpath.join(self.server.root,
462 item.get('clangfile','')))
463 c.files.append(posixpath.join(self.server.root,
464 item.get('stderr','')))
465 c.files.append(posixpath.join(self.server.root,
466 item.get('info','')))
467 # Just in case something failed, ignore files which don't
468 # exist.
469 c.files = [f for f in c.files
470 if os.path.exists(f) and os.path.isfile(f)]
471 else:
472 # Check that this is a valid report.
473 path = posixpath.join(self.server.root, 'report-%s.html' % report)
474 if not posixpath.exists(path):
475 raise ValueError, 'Invalid report ID'
476 keys = self.load_report(report)
477 c = Context()
478 c.title = keys.get('DESC','clang error (unrecognized')
479 c.description = """\
480Bug reported by the clang static analyzer.
Daniel Dunbare33d3682008-09-19 23:32:11 +0000481
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000482Description: %s
483File: %s
484Line: %s
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000485"""%(c.title, keys.get('FILE','<unknown>'), keys.get('LINE', '<unknown>'))
486 c.reportSource = 'report-%s.html' % report
487 c.navMarkup = """<a href="/%s">Report %s</a> > """ % (c.reportSource,
488 report)
489
490 c.files = [path]
491 return c
492
493 def send_report(self, report, configOverrides=None):
494 def getConfigOption(section, field):
495 if (configOverrides is not None and
496 section in configOverrides and
497 field in configOverrides[section]):
498 return configOverrides[section][field]
499 return self.server.config.get(section, field)
500
501 # report is None is used for crashes
502 try:
503 c = self.get_report_context(report)
504 except ValueError, e:
505 return self.send_error(400, e.message)
506
507 title = c.title
508 description= c.description
509 reportingFor = c.navMarkup
510 if c.reportSource is None:
511 extraIFrame = ""
512 else:
513 extraIFrame = """\
514<iframe src="/%s" width="100%%" height="40%%"
515 scrolling="auto" frameborder="1">
516 <a href="/%s">View Bug Report</a>
517</iframe>""" % (c.reportSource, c.reportSource)
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000518
Daniel Dunbare33d3682008-09-19 23:32:11 +0000519 reporterSelections = []
520 reporterOptions = []
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000521
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000522 try:
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000523 active = int(getConfigOption('ScanView','reporter'))
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000524 except:
525 active = 0
Daniel Dunbare33d3682008-09-19 23:32:11 +0000526 for i,r in enumerate(self.server.reporters):
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000527 selected = (i == active)
528 if selected:
529 selectedStr = ' selected'
530 else:
531 selectedStr = ''
532 reporterSelections.append('<option value="%d"%s>%s</option>'%(i,selectedStr,r.getName()))
Ted Kremenek19c88202008-09-30 16:08:13 +0000533 options = '\n'.join([ o.getHTML(r,title,getConfigOption) for o in r.getParameters()])
Daniel Dunbar8dcd9402008-09-22 02:27:45 +0000534 display = ('none','')[selected]
Daniel Dunbar78266292008-09-22 00:11:51 +0000535 reporterOptions.append("""\
536<tr id="%sReporterOptions" style="display:%s">
537 <td class="form_label">%s Options</td>
538 <td class="form_value">
539 <table class="form_inner_group">
540%s
541 </table>
542 </td>
543</tr>
544"""%(r.getName(),display,r.getName(),options))
Daniel Dunbare33d3682008-09-19 23:32:11 +0000545 reporterSelections = '\n'.join(reporterSelections)
546 reporterOptionsDivs = '\n'.join(reporterOptions)
547 reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
548
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000549 if c.files:
550 fieldSize = min(5, len(c.files))
551 attachFileOptions = '\n'.join(["""\
552<option value="%d" selected>%s</option>""" % (i,v) for i,v in enumerate(c.files)])
553 attachFileRow = """\
554<tr>
555 <td class="form_label">Attach:</td>
556 <td class="form_value">
557<select style="width:100%%" name="files" multiple size=%d>
558%s
559</select>
560 </td>
561</tr>
562""" % (min(5, len(c.files)), attachFileOptions)
563 else:
564 attachFileRow = ""
565
Daniel Dunbare33d3682008-09-19 23:32:11 +0000566 result = """<html>
567<head>
Daniel Dunbarc2dd3452008-09-22 03:08:32 +0000568 <title>File Bug</title>
Daniel Dunbar78266292008-09-22 00:11:51 +0000569 <link rel="stylesheet" type="text/css" href="/scanview.css" />
Daniel Dunbare33d3682008-09-19 23:32:11 +0000570</head>
571<script language="javascript" type="text/javascript">
572var reporters = %(reportersArray)s;
573function updateReporterOptions() {
574 index = document.getElementById('reporter').selectedIndex;
575 for (var i=0; i < reporters.length; ++i) {
576 o = document.getElementById(reporters[i] + "ReporterOptions");
577 if (i == index) {
Daniel Dunbar78266292008-09-22 00:11:51 +0000578 o.style.display = "";
Daniel Dunbare33d3682008-09-19 23:32:11 +0000579 } else {
580 o.style.display = "none";
581 }
582 }
583}
584</script>
Daniel Dunbar7345e472008-09-22 01:40:14 +0000585<body onLoad="updateReporterOptions()">
Daniel Dunbarc2dd3452008-09-22 03:08:32 +0000586<h3>
587<a href="/">Summary</a> >
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000588%(reportingFor)s
Daniel Dunbarc2dd3452008-09-22 03:08:32 +0000589File Bug</h3>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000590<form name="form" action="/report_submit" method="post">
Daniel Dunbar78266292008-09-22 00:11:51 +0000591<input type="hidden" name="report" value="%(report)s">
592
593<table class="form">
594<tr><td>
595<table class="form_group">
596<tr>
597 <td class="form_clabel">Title:</td>
598 <td class="form_value">
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000599 <input type="text" name="title" size="50" value="%(title)s">
Daniel Dunbar78266292008-09-22 00:11:51 +0000600 </td>
601</tr>
602<tr>
603 <td class="form_label">Description:</td>
604 <td class="form_value">
Daniel Dunbare33d3682008-09-19 23:32:11 +0000605<textarea rows="10" cols="80" name="description">
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000606%(description)s
Daniel Dunbar78266292008-09-22 00:11:51 +0000607</textarea>
608 </td>
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000609</tr>
610
611%(attachFileRow)s
612
Daniel Dunbar78266292008-09-22 00:11:51 +0000613</table>
614<br>
615<table class="form_group">
616<tr>
617 <td class="form_clabel">Method:</td>
618 <td class="form_value">
619 <select id="reporter" name="reporter" onChange="updateReporterOptions()">
620 %(reporterSelections)s
621 </select>
622 </td>
623</tr>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000624%(reporterOptionsDivs)s
Daniel Dunbar78266292008-09-22 00:11:51 +0000625</table>
626<br>
627</td></tr>
628<tr><td class="form_submit">
629 <input align="right" type="submit" name="Submit" value="Submit">
630</td></tr>
631</table>
Daniel Dunbare33d3682008-09-19 23:32:11 +0000632</form>
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000633
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000634%(extraIFrame)s
Daniel Dunbar25b51d02008-09-21 19:08:54 +0000635
Daniel Dunbare33d3682008-09-19 23:32:11 +0000636</body>
637</html>"""%locals()
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000638
Daniel Dunbare33d3682008-09-19 23:32:11 +0000639 return self.send_string(result)
640
641 def send_head(self, fields=None):
Daniel Dunbarf6a415f2008-09-24 17:59:41 +0000642 if (self.server.options.onlyServeLocal and
643 self.client_address[0] != '127.0.0.1'):
644 return self.send_error('401', 'Unauthorized host.')
645
Daniel Dunbare33d3682008-09-19 23:32:11 +0000646 if fields is None:
647 fields = {}
648 self.fields = fields
649
650 o = urlparse.urlparse(self.path)
651 self.fields = parse_query(o.query, fields)
652 path = posixpath.normpath(urllib.unquote(o.path))
653
654 # Split the components and strip the root prefix.
655 components = path.split('/')[1:]
656
657 # Special case some top-level entries.
658 if components:
659 name = components[0]
Daniel Dunbar78266292008-09-22 00:11:51 +0000660 if len(components)==2:
661 if name=='report':
Daniel Dunbare33d3682008-09-19 23:32:11 +0000662 return self.send_report(components[1])
Daniel Dunbar28870e12008-09-22 18:05:49 +0000663 elif name=='open':
664 return self.send_open_report(components[1])
Daniel Dunbar78266292008-09-22 00:11:51 +0000665 elif len(components)==1:
666 if name=='quit':
667 self.server.halt()
668 return self.send_string('Goodbye.', 'text/plain')
669 elif name=='report_submit':
Daniel Dunbare33d3682008-09-19 23:32:11 +0000670 return self.send_report_submit()
Daniel Dunbar7ebe0ed2008-09-25 06:05:31 +0000671 elif name=='report_crashes':
672 overrides = { 'ScanView' : {},
673 'Radar' : {},
674 'Email' : {} }
675 for i,r in enumerate(self.server.reporters):
676 if r.getName() == 'Radar':
677 overrides['ScanView']['reporter'] = i
678 break
679 overrides['Radar']['Component'] = 'llvm - checker'
680 overrides['Radar']['Component Version'] = 'X'
681 return self.send_report(None, overrides)
Daniel Dunbar78266292008-09-22 00:11:51 +0000682 elif name=='favicon.ico':
Daniel Dunbarb131c8a2008-09-21 23:02:25 +0000683 return self.send_path(posixpath.join(kResources,'bugcatcher.ico'))
Daniel Dunbare33d3682008-09-19 23:32:11 +0000684
685 # Match directory entries.
686 if components[-1] == '':
687 components[-1] = 'index.html'
Daniel Dunbarb131c8a2008-09-21 23:02:25 +0000688
689 suffix = '/'.join(components)
690
691 # The summary may reference source files on disk using rooted
692 # paths. Make sure these resolve correctly for now.
693 # FIXME: This isn't a very good idea... we should probably
694 # mark rooted paths somehow.
695 if os.path.exists(posixpath.join('/', suffix)):
696 path = posixpath.join('/', suffix)
697 else:
698 path = posixpath.join(self.server.root, suffix)
699
Daniel Dunbare33d3682008-09-19 23:32:11 +0000700 if self.server.options.debug > 1:
701 print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
702 path)
703 return self.send_path(path)
704
705 def send_404(self):
706 self.send_error(404, "File not found")
707 return None
708
709 def send_path(self, path):
710 ctype = self.guess_type(path)
711 if ctype.startswith('text/'):
712 # Patch file instead
713 return self.send_patched_file(path, ctype)
714 else:
715 mode = 'rb'
716 try:
717 f = open(path, mode)
718 except IOError:
719 return self.send_404()
720 return self.send_file(f, ctype)
721
722 def send_file(self, f, ctype):
723 # Patch files to add links, but skip binary files.
724 self.send_response(200)
725 self.send_header("Content-type", ctype)
726 fs = os.fstat(f.fileno())
727 self.send_header("Content-Length", str(fs[6]))
728 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
729 self.end_headers()
730 return f
731
732 def send_string(self, s, ctype='text/html', headers=True, mtime=None):
733 if headers:
734 self.send_response(200)
735 self.send_header("Content-type", ctype)
736 self.send_header("Content-Length", str(len(s)))
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000737 if mtime is None:
738 mtime = self.dynamic_mtime
739 self.send_header("Last-Modified", self.date_time_string(mtime))
Daniel Dunbare33d3682008-09-19 23:32:11 +0000740 self.end_headers()
741 return StringIO.StringIO(s)
742
743 def send_patched_file(self, path, ctype):
Daniel Dunbar17fded62008-09-22 21:43:43 +0000744 # Allow a very limited set of variables. This is pretty gross.
745 variables = {}
746 variables['report'] = ''
747 m = kReportFileRE.match(path)
748 if m:
749 variables['report'] = m.group(2)
750
Daniel Dunbar7345e472008-09-22 01:40:14 +0000751 try:
752 f = open(path,'r')
753 except IOError:
754 return self.send_404()
Daniel Dunbare33d3682008-09-19 23:32:11 +0000755 fs = os.fstat(f.fileno())
756 data = f.read()
Daniel Dunbarcb028b02008-09-21 20:34:58 +0000757 for a,b in kReportReplacements:
Daniel Dunbar17fded62008-09-22 21:43:43 +0000758 data = a.sub(b % variables, data)
Daniel Dunbare33d3682008-09-19 23:32:11 +0000759 return self.send_string(data, ctype, mtime=fs.st_mtime)
760
761
Daniel Dunbar7ad535d2008-09-22 02:53:12 +0000762def create_server(address, options, root):
Daniel Dunbare33d3682008-09-19 23:32:11 +0000763 import Reporter
764
765 reporters = Reporter.getReporters()
766
Daniel Dunbar7ad535d2008-09-22 02:53:12 +0000767 return ScanViewServer(address, ScanViewRequestHandler,
Daniel Dunbare33d3682008-09-19 23:32:11 +0000768 root,
769 reporters,
770 options)