1*67e74705SXin Liimport BaseHTTPServer 2*67e74705SXin Liimport SimpleHTTPServer 3*67e74705SXin Liimport os 4*67e74705SXin Liimport sys 5*67e74705SXin Liimport urllib, urlparse 6*67e74705SXin Liimport posixpath 7*67e74705SXin Liimport StringIO 8*67e74705SXin Liimport re 9*67e74705SXin Liimport shutil 10*67e74705SXin Liimport threading 11*67e74705SXin Liimport time 12*67e74705SXin Liimport socket 13*67e74705SXin Liimport itertools 14*67e74705SXin Li 15*67e74705SXin Liimport Reporter 16*67e74705SXin Liimport ConfigParser 17*67e74705SXin Li 18*67e74705SXin Li### 19*67e74705SXin Li# Various patterns matched or replaced by server. 20*67e74705SXin Li 21*67e74705SXin LikReportFileRE = re.compile('(.*/)?report-(.*)\\.html') 22*67e74705SXin Li 23*67e74705SXin LikBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->') 24*67e74705SXin Li 25*67e74705SXin Li# <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" --> 26*67e74705SXin Li 27*67e74705SXin LikReportCrashEntryRE = re.compile('<!-- REPORTPROBLEM (.*?)-->') 28*67e74705SXin LikReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"') 29*67e74705SXin Li 30*67e74705SXin LikReportReplacements = [] 31*67e74705SXin Li 32*67e74705SXin Li# Add custom javascript. 33*67e74705SXin LikReportReplacements.append((re.compile('<!-- SUMMARYENDHEAD -->'), """\ 34*67e74705SXin Li<script language="javascript" type="text/javascript"> 35*67e74705SXin Lifunction load(url) { 36*67e74705SXin Li if (window.XMLHttpRequest) { 37*67e74705SXin Li req = new XMLHttpRequest(); 38*67e74705SXin Li } else if (window.ActiveXObject) { 39*67e74705SXin Li req = new ActiveXObject("Microsoft.XMLHTTP"); 40*67e74705SXin Li } 41*67e74705SXin Li if (req != undefined) { 42*67e74705SXin Li req.open("GET", url, true); 43*67e74705SXin Li req.send(""); 44*67e74705SXin Li } 45*67e74705SXin Li} 46*67e74705SXin Li</script>""")) 47*67e74705SXin Li 48*67e74705SXin Li# Insert additional columns. 49*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTBUGCOL -->'), 50*67e74705SXin Li '<td></td><td></td>')) 51*67e74705SXin Li 52*67e74705SXin Li# Insert report bug and open file links. 53*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'), 54*67e74705SXin Li ('<td class="Button"><a href="report/\\1">Report Bug</a></td>' + 55*67e74705SXin Li '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>'))) 56*67e74705SXin Li 57*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTHEADER -->'), 58*67e74705SXin Li '<h3><a href="/">Summary</a> > Report %(report)s</h3>')) 59*67e74705SXin Li 60*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTSUMMARYEXTRA -->'), 61*67e74705SXin Li '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>')) 62*67e74705SXin Li 63*67e74705SXin Li# Insert report crashes link. 64*67e74705SXin Li 65*67e74705SXin Li# Disabled for the time being until we decide exactly when this should 66*67e74705SXin Li# be enabled. Also the radar reporter needs to be fixed to report 67*67e74705SXin Li# multiple files. 68*67e74705SXin Li 69*67e74705SXin Li#kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'), 70*67e74705SXin Li# '<br>These files will automatically be attached to ' + 71*67e74705SXin Li# 'reports filed here: <a href="report_crashes">Report Crashes</a>.')) 72*67e74705SXin Li 73*67e74705SXin Li### 74*67e74705SXin Li# Other simple parameters 75*67e74705SXin Li 76*67e74705SXin LikShare = posixpath.join(posixpath.dirname(__file__), '../share/scan-view') 77*67e74705SXin LikConfigPath = os.path.expanduser('~/.scanview.cfg') 78*67e74705SXin Li 79*67e74705SXin Li### 80*67e74705SXin Li 81*67e74705SXin Li__version__ = "0.1" 82*67e74705SXin Li 83*67e74705SXin Li__all__ = ["create_server"] 84*67e74705SXin Li 85*67e74705SXin Liclass ReporterThread(threading.Thread): 86*67e74705SXin Li def __init__(self, report, reporter, parameters, server): 87*67e74705SXin Li threading.Thread.__init__(self) 88*67e74705SXin Li self.report = report 89*67e74705SXin Li self.server = server 90*67e74705SXin Li self.reporter = reporter 91*67e74705SXin Li self.parameters = parameters 92*67e74705SXin Li self.success = False 93*67e74705SXin Li self.status = None 94*67e74705SXin Li 95*67e74705SXin Li def run(self): 96*67e74705SXin Li result = None 97*67e74705SXin Li try: 98*67e74705SXin Li if self.server.options.debug: 99*67e74705SXin Li print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],) 100*67e74705SXin Li self.status = self.reporter.fileReport(self.report, self.parameters) 101*67e74705SXin Li self.success = True 102*67e74705SXin Li time.sleep(3) 103*67e74705SXin Li if self.server.options.debug: 104*67e74705SXin Li print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],) 105*67e74705SXin Li except Reporter.ReportFailure,e: 106*67e74705SXin Li self.status = e.value 107*67e74705SXin Li except Exception,e: 108*67e74705SXin Li s = StringIO.StringIO() 109*67e74705SXin Li import traceback 110*67e74705SXin Li print >>s,'<b>Unhandled Exception</b><br><pre>' 111*67e74705SXin Li traceback.print_exc(e,file=s) 112*67e74705SXin Li print >>s,'</pre>' 113*67e74705SXin Li self.status = s.getvalue() 114*67e74705SXin Li 115*67e74705SXin Liclass ScanViewServer(BaseHTTPServer.HTTPServer): 116*67e74705SXin Li def __init__(self, address, handler, root, reporters, options): 117*67e74705SXin Li BaseHTTPServer.HTTPServer.__init__(self, address, handler) 118*67e74705SXin Li self.root = root 119*67e74705SXin Li self.reporters = reporters 120*67e74705SXin Li self.options = options 121*67e74705SXin Li self.halted = False 122*67e74705SXin Li self.config = None 123*67e74705SXin Li self.load_config() 124*67e74705SXin Li 125*67e74705SXin Li def load_config(self): 126*67e74705SXin Li self.config = ConfigParser.RawConfigParser() 127*67e74705SXin Li 128*67e74705SXin Li # Add defaults 129*67e74705SXin Li self.config.add_section('ScanView') 130*67e74705SXin Li for r in self.reporters: 131*67e74705SXin Li self.config.add_section(r.getName()) 132*67e74705SXin Li for p in r.getParameters(): 133*67e74705SXin Li if p.saveConfigValue(): 134*67e74705SXin Li self.config.set(r.getName(), p.getName(), '') 135*67e74705SXin Li 136*67e74705SXin Li # Ignore parse errors 137*67e74705SXin Li try: 138*67e74705SXin Li self.config.read([kConfigPath]) 139*67e74705SXin Li except: 140*67e74705SXin Li pass 141*67e74705SXin Li 142*67e74705SXin Li # Save on exit 143*67e74705SXin Li import atexit 144*67e74705SXin Li atexit.register(lambda: self.save_config()) 145*67e74705SXin Li 146*67e74705SXin Li def save_config(self): 147*67e74705SXin Li # Ignore errors (only called on exit). 148*67e74705SXin Li try: 149*67e74705SXin Li f = open(kConfigPath,'w') 150*67e74705SXin Li self.config.write(f) 151*67e74705SXin Li f.close() 152*67e74705SXin Li except: 153*67e74705SXin Li pass 154*67e74705SXin Li 155*67e74705SXin Li def halt(self): 156*67e74705SXin Li self.halted = True 157*67e74705SXin Li if self.options.debug: 158*67e74705SXin Li print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],) 159*67e74705SXin Li 160*67e74705SXin Li def serve_forever(self): 161*67e74705SXin Li while not self.halted: 162*67e74705SXin Li if self.options.debug > 1: 163*67e74705SXin Li print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],) 164*67e74705SXin Li try: 165*67e74705SXin Li self.handle_request() 166*67e74705SXin Li except OSError,e: 167*67e74705SXin Li print 'OSError',e.errno 168*67e74705SXin Li 169*67e74705SXin Li def finish_request(self, request, client_address): 170*67e74705SXin Li if self.options.autoReload: 171*67e74705SXin Li import ScanView 172*67e74705SXin Li self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler 173*67e74705SXin Li BaseHTTPServer.HTTPServer.finish_request(self, request, client_address) 174*67e74705SXin Li 175*67e74705SXin Li def handle_error(self, request, client_address): 176*67e74705SXin Li # Ignore socket errors 177*67e74705SXin Li info = sys.exc_info() 178*67e74705SXin Li if info and isinstance(info[1], socket.error): 179*67e74705SXin Li if self.options.debug > 1: 180*67e74705SXin Li print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],) 181*67e74705SXin Li return 182*67e74705SXin Li BaseHTTPServer.HTTPServer.handle_error(self, request, client_address) 183*67e74705SXin Li 184*67e74705SXin Li# Borrowed from Quixote, with simplifications. 185*67e74705SXin Lidef parse_query(qs, fields=None): 186*67e74705SXin Li if fields is None: 187*67e74705SXin Li fields = {} 188*67e74705SXin Li for chunk in filter(None, qs.split('&')): 189*67e74705SXin Li if '=' not in chunk: 190*67e74705SXin Li name = chunk 191*67e74705SXin Li value = '' 192*67e74705SXin Li else: 193*67e74705SXin Li name, value = chunk.split('=', 1) 194*67e74705SXin Li name = urllib.unquote(name.replace('+', ' ')) 195*67e74705SXin Li value = urllib.unquote(value.replace('+', ' ')) 196*67e74705SXin Li item = fields.get(name) 197*67e74705SXin Li if item is None: 198*67e74705SXin Li fields[name] = [value] 199*67e74705SXin Li else: 200*67e74705SXin Li item.append(value) 201*67e74705SXin Li return fields 202*67e74705SXin Li 203*67e74705SXin Liclass ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 204*67e74705SXin Li server_version = "ScanViewServer/" + __version__ 205*67e74705SXin Li dynamic_mtime = time.time() 206*67e74705SXin Li 207*67e74705SXin Li def do_HEAD(self): 208*67e74705SXin Li try: 209*67e74705SXin Li SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self) 210*67e74705SXin Li except Exception,e: 211*67e74705SXin Li self.handle_exception(e) 212*67e74705SXin Li 213*67e74705SXin Li def do_GET(self): 214*67e74705SXin Li try: 215*67e74705SXin Li SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) 216*67e74705SXin Li except Exception,e: 217*67e74705SXin Li self.handle_exception(e) 218*67e74705SXin Li 219*67e74705SXin Li def do_POST(self): 220*67e74705SXin Li """Serve a POST request.""" 221*67e74705SXin Li try: 222*67e74705SXin Li length = self.headers.getheader('content-length') or "0" 223*67e74705SXin Li try: 224*67e74705SXin Li length = int(length) 225*67e74705SXin Li except: 226*67e74705SXin Li length = 0 227*67e74705SXin Li content = self.rfile.read(length) 228*67e74705SXin Li fields = parse_query(content) 229*67e74705SXin Li f = self.send_head(fields) 230*67e74705SXin Li if f: 231*67e74705SXin Li self.copyfile(f, self.wfile) 232*67e74705SXin Li f.close() 233*67e74705SXin Li except Exception,e: 234*67e74705SXin Li self.handle_exception(e) 235*67e74705SXin Li 236*67e74705SXin Li def log_message(self, format, *args): 237*67e74705SXin Li if self.server.options.debug: 238*67e74705SXin Li sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" % 239*67e74705SXin Li (sys.argv[0], 240*67e74705SXin Li self.address_string(), 241*67e74705SXin Li self.log_date_time_string(), 242*67e74705SXin Li format%args)) 243*67e74705SXin Li 244*67e74705SXin Li def load_report(self, report): 245*67e74705SXin Li path = os.path.join(self.server.root, 'report-%s.html'%report) 246*67e74705SXin Li data = open(path).read() 247*67e74705SXin Li keys = {} 248*67e74705SXin Li for item in kBugKeyValueRE.finditer(data): 249*67e74705SXin Li k,v = item.groups() 250*67e74705SXin Li keys[k] = v 251*67e74705SXin Li return keys 252*67e74705SXin Li 253*67e74705SXin Li def load_crashes(self): 254*67e74705SXin Li path = posixpath.join(self.server.root, 'index.html') 255*67e74705SXin Li data = open(path).read() 256*67e74705SXin Li problems = [] 257*67e74705SXin Li for item in kReportCrashEntryRE.finditer(data): 258*67e74705SXin Li fieldData = item.group(1) 259*67e74705SXin Li fields = dict([i.groups() for i in 260*67e74705SXin Li kReportCrashEntryKeyValueRE.finditer(fieldData)]) 261*67e74705SXin Li problems.append(fields) 262*67e74705SXin Li return problems 263*67e74705SXin Li 264*67e74705SXin Li def handle_exception(self, exc): 265*67e74705SXin Li import traceback 266*67e74705SXin Li s = StringIO.StringIO() 267*67e74705SXin Li print >>s, "INTERNAL ERROR\n" 268*67e74705SXin Li traceback.print_exc(exc, s) 269*67e74705SXin Li f = self.send_string(s.getvalue(), 'text/plain') 270*67e74705SXin Li if f: 271*67e74705SXin Li self.copyfile(f, self.wfile) 272*67e74705SXin Li f.close() 273*67e74705SXin Li 274*67e74705SXin Li def get_scalar_field(self, name): 275*67e74705SXin Li if name in self.fields: 276*67e74705SXin Li return self.fields[name][0] 277*67e74705SXin Li else: 278*67e74705SXin Li return None 279*67e74705SXin Li 280*67e74705SXin Li def submit_bug(self, c): 281*67e74705SXin Li title = self.get_scalar_field('title') 282*67e74705SXin Li description = self.get_scalar_field('description') 283*67e74705SXin Li report = self.get_scalar_field('report') 284*67e74705SXin Li reporterIndex = self.get_scalar_field('reporter') 285*67e74705SXin Li files = [] 286*67e74705SXin Li for fileID in self.fields.get('files',[]): 287*67e74705SXin Li try: 288*67e74705SXin Li i = int(fileID) 289*67e74705SXin Li except: 290*67e74705SXin Li i = None 291*67e74705SXin Li if i is None or i<0 or i>=len(c.files): 292*67e74705SXin Li return (False, 'Invalid file ID') 293*67e74705SXin Li files.append(c.files[i]) 294*67e74705SXin Li 295*67e74705SXin Li if not title: 296*67e74705SXin Li return (False, "Missing title.") 297*67e74705SXin Li if not description: 298*67e74705SXin Li return (False, "Missing description.") 299*67e74705SXin Li try: 300*67e74705SXin Li reporterIndex = int(reporterIndex) 301*67e74705SXin Li except: 302*67e74705SXin Li return (False, "Invalid report method.") 303*67e74705SXin Li 304*67e74705SXin Li # Get the reporter and parameters. 305*67e74705SXin Li reporter = self.server.reporters[reporterIndex] 306*67e74705SXin Li parameters = {} 307*67e74705SXin Li for o in reporter.getParameters(): 308*67e74705SXin Li name = '%s_%s'%(reporter.getName(),o.getName()) 309*67e74705SXin Li if name not in self.fields: 310*67e74705SXin Li return (False, 311*67e74705SXin Li 'Missing field "%s" for %s report method.'%(name, 312*67e74705SXin Li reporter.getName())) 313*67e74705SXin Li parameters[o.getName()] = self.get_scalar_field(name) 314*67e74705SXin Li 315*67e74705SXin Li # Update config defaults. 316*67e74705SXin Li if report != 'None': 317*67e74705SXin Li self.server.config.set('ScanView', 'reporter', reporterIndex) 318*67e74705SXin Li for o in reporter.getParameters(): 319*67e74705SXin Li if o.saveConfigValue(): 320*67e74705SXin Li name = o.getName() 321*67e74705SXin Li self.server.config.set(reporter.getName(), name, parameters[name]) 322*67e74705SXin Li 323*67e74705SXin Li # Create the report. 324*67e74705SXin Li bug = Reporter.BugReport(title, description, files) 325*67e74705SXin Li 326*67e74705SXin Li # Kick off a reporting thread. 327*67e74705SXin Li t = ReporterThread(bug, reporter, parameters, self.server) 328*67e74705SXin Li t.start() 329*67e74705SXin Li 330*67e74705SXin Li # Wait for thread to die... 331*67e74705SXin Li while t.isAlive(): 332*67e74705SXin Li time.sleep(.25) 333*67e74705SXin Li submitStatus = t.status 334*67e74705SXin Li 335*67e74705SXin Li return (t.success, t.status) 336*67e74705SXin Li 337*67e74705SXin Li def send_report_submit(self): 338*67e74705SXin Li report = self.get_scalar_field('report') 339*67e74705SXin Li c = self.get_report_context(report) 340*67e74705SXin Li if c.reportSource is None: 341*67e74705SXin Li reportingFor = "Report Crashes > " 342*67e74705SXin Li fileBug = """\ 343*67e74705SXin Li<a href="/report_crashes">File Bug</a> > """%locals() 344*67e74705SXin Li else: 345*67e74705SXin Li reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource, 346*67e74705SXin Li report) 347*67e74705SXin Li fileBug = '<a href="/report/%s">File Bug</a> > ' % report 348*67e74705SXin Li title = self.get_scalar_field('title') 349*67e74705SXin Li description = self.get_scalar_field('description') 350*67e74705SXin Li 351*67e74705SXin Li res,message = self.submit_bug(c) 352*67e74705SXin Li 353*67e74705SXin Li if res: 354*67e74705SXin Li statusClass = 'SubmitOk' 355*67e74705SXin Li statusName = 'Succeeded' 356*67e74705SXin Li else: 357*67e74705SXin Li statusClass = 'SubmitFail' 358*67e74705SXin Li statusName = 'Failed' 359*67e74705SXin Li 360*67e74705SXin Li result = """ 361*67e74705SXin Li<head> 362*67e74705SXin Li <title>Bug Submission</title> 363*67e74705SXin Li <link rel="stylesheet" type="text/css" href="/scanview.css" /> 364*67e74705SXin Li</head> 365*67e74705SXin Li<body> 366*67e74705SXin Li<h3> 367*67e74705SXin Li<a href="/">Summary</a> > 368*67e74705SXin Li%(reportingFor)s 369*67e74705SXin Li%(fileBug)s 370*67e74705SXin LiSubmit</h3> 371*67e74705SXin Li<form name="form" action=""> 372*67e74705SXin Li<table class="form"> 373*67e74705SXin Li<tr><td> 374*67e74705SXin Li<table class="form_group"> 375*67e74705SXin Li<tr> 376*67e74705SXin Li <td class="form_clabel">Title:</td> 377*67e74705SXin Li <td class="form_value"> 378*67e74705SXin Li <input type="text" name="title" size="50" value="%(title)s" disabled> 379*67e74705SXin Li </td> 380*67e74705SXin Li</tr> 381*67e74705SXin Li<tr> 382*67e74705SXin Li <td class="form_label">Description:</td> 383*67e74705SXin Li <td class="form_value"> 384*67e74705SXin Li<textarea rows="10" cols="80" name="description" disabled> 385*67e74705SXin Li%(description)s 386*67e74705SXin Li</textarea> 387*67e74705SXin Li </td> 388*67e74705SXin Li</table> 389*67e74705SXin Li</td></tr> 390*67e74705SXin Li</table> 391*67e74705SXin Li</form> 392*67e74705SXin Li<h1 class="%(statusClass)s">Submission %(statusName)s</h1> 393*67e74705SXin Li%(message)s 394*67e74705SXin Li<p> 395*67e74705SXin Li<hr> 396*67e74705SXin Li<a href="/">Return to Summary</a> 397*67e74705SXin Li</body> 398*67e74705SXin Li</html>"""%locals() 399*67e74705SXin Li return self.send_string(result) 400*67e74705SXin Li 401*67e74705SXin Li def send_open_report(self, report): 402*67e74705SXin Li try: 403*67e74705SXin Li keys = self.load_report(report) 404*67e74705SXin Li except IOError: 405*67e74705SXin Li return self.send_error(400, 'Invalid report.') 406*67e74705SXin Li 407*67e74705SXin Li file = keys.get('FILE') 408*67e74705SXin Li if not file or not posixpath.exists(file): 409*67e74705SXin Li return self.send_error(400, 'File does not exist: "%s"' % file) 410*67e74705SXin Li 411*67e74705SXin Li import startfile 412*67e74705SXin Li if self.server.options.debug: 413*67e74705SXin Li print >>sys.stderr, '%s: SERVER: opening "%s"'%(sys.argv[0], 414*67e74705SXin Li file) 415*67e74705SXin Li 416*67e74705SXin Li status = startfile.open(file) 417*67e74705SXin Li if status: 418*67e74705SXin Li res = 'Opened: "%s"' % file 419*67e74705SXin Li else: 420*67e74705SXin Li res = 'Open failed: "%s"' % file 421*67e74705SXin Li 422*67e74705SXin Li return self.send_string(res, 'text/plain') 423*67e74705SXin Li 424*67e74705SXin Li def get_report_context(self, report): 425*67e74705SXin Li class Context: 426*67e74705SXin Li pass 427*67e74705SXin Li if report is None or report == 'None': 428*67e74705SXin Li data = self.load_crashes() 429*67e74705SXin Li # Don't allow empty reports. 430*67e74705SXin Li if not data: 431*67e74705SXin Li raise ValueError, 'No crashes detected!' 432*67e74705SXin Li c = Context() 433*67e74705SXin Li c.title = 'clang static analyzer failures' 434*67e74705SXin Li 435*67e74705SXin Li stderrSummary = "" 436*67e74705SXin Li for item in data: 437*67e74705SXin Li if 'stderr' in item: 438*67e74705SXin Li path = posixpath.join(self.server.root, item['stderr']) 439*67e74705SXin Li if os.path.exists(path): 440*67e74705SXin Li lns = itertools.islice(open(path), 0, 10) 441*67e74705SXin Li stderrSummary += '%s\n--\n%s' % (item.get('src', 442*67e74705SXin Li '<unknown>'), 443*67e74705SXin Li ''.join(lns)) 444*67e74705SXin Li 445*67e74705SXin Li c.description = """\ 446*67e74705SXin LiThe clang static analyzer failed on these inputs: 447*67e74705SXin Li%s 448*67e74705SXin Li 449*67e74705SXin LiSTDERR Summary 450*67e74705SXin Li-------------- 451*67e74705SXin Li%s 452*67e74705SXin Li""" % ('\n'.join([item.get('src','<unknown>') for item in data]), 453*67e74705SXin Li stderrSummary) 454*67e74705SXin Li c.reportSource = None 455*67e74705SXin Li c.navMarkup = "Report Crashes > " 456*67e74705SXin Li c.files = [] 457*67e74705SXin Li for item in data: 458*67e74705SXin Li c.files.append(item.get('src','')) 459*67e74705SXin Li c.files.append(posixpath.join(self.server.root, 460*67e74705SXin Li item.get('file',''))) 461*67e74705SXin Li c.files.append(posixpath.join(self.server.root, 462*67e74705SXin Li item.get('clangfile',''))) 463*67e74705SXin Li c.files.append(posixpath.join(self.server.root, 464*67e74705SXin Li item.get('stderr',''))) 465*67e74705SXin Li c.files.append(posixpath.join(self.server.root, 466*67e74705SXin Li item.get('info',''))) 467*67e74705SXin Li # Just in case something failed, ignore files which don't 468*67e74705SXin Li # exist. 469*67e74705SXin Li c.files = [f for f in c.files 470*67e74705SXin Li if os.path.exists(f) and os.path.isfile(f)] 471*67e74705SXin Li else: 472*67e74705SXin Li # Check that this is a valid report. 473*67e74705SXin Li path = posixpath.join(self.server.root, 'report-%s.html' % report) 474*67e74705SXin Li if not posixpath.exists(path): 475*67e74705SXin Li raise ValueError, 'Invalid report ID' 476*67e74705SXin Li keys = self.load_report(report) 477*67e74705SXin Li c = Context() 478*67e74705SXin Li c.title = keys.get('DESC','clang error (unrecognized') 479*67e74705SXin Li c.description = """\ 480*67e74705SXin LiBug reported by the clang static analyzer. 481*67e74705SXin Li 482*67e74705SXin LiDescription: %s 483*67e74705SXin LiFile: %s 484*67e74705SXin LiLine: %s 485*67e74705SXin Li"""%(c.title, keys.get('FILE','<unknown>'), keys.get('LINE', '<unknown>')) 486*67e74705SXin Li c.reportSource = 'report-%s.html' % report 487*67e74705SXin Li c.navMarkup = """<a href="/%s">Report %s</a> > """ % (c.reportSource, 488*67e74705SXin Li report) 489*67e74705SXin Li 490*67e74705SXin Li c.files = [path] 491*67e74705SXin Li return c 492*67e74705SXin Li 493*67e74705SXin Li def send_report(self, report, configOverrides=None): 494*67e74705SXin Li def getConfigOption(section, field): 495*67e74705SXin Li if (configOverrides is not None and 496*67e74705SXin Li section in configOverrides and 497*67e74705SXin Li field in configOverrides[section]): 498*67e74705SXin Li return configOverrides[section][field] 499*67e74705SXin Li return self.server.config.get(section, field) 500*67e74705SXin Li 501*67e74705SXin Li # report is None is used for crashes 502*67e74705SXin Li try: 503*67e74705SXin Li c = self.get_report_context(report) 504*67e74705SXin Li except ValueError, e: 505*67e74705SXin Li return self.send_error(400, e.message) 506*67e74705SXin Li 507*67e74705SXin Li title = c.title 508*67e74705SXin Li description= c.description 509*67e74705SXin Li reportingFor = c.navMarkup 510*67e74705SXin Li if c.reportSource is None: 511*67e74705SXin Li extraIFrame = "" 512*67e74705SXin Li else: 513*67e74705SXin Li extraIFrame = """\ 514*67e74705SXin Li<iframe src="/%s" width="100%%" height="40%%" 515*67e74705SXin Li scrolling="auto" frameborder="1"> 516*67e74705SXin Li <a href="/%s">View Bug Report</a> 517*67e74705SXin Li</iframe>""" % (c.reportSource, c.reportSource) 518*67e74705SXin Li 519*67e74705SXin Li reporterSelections = [] 520*67e74705SXin Li reporterOptions = [] 521*67e74705SXin Li 522*67e74705SXin Li try: 523*67e74705SXin Li active = int(getConfigOption('ScanView','reporter')) 524*67e74705SXin Li except: 525*67e74705SXin Li active = 0 526*67e74705SXin Li for i,r in enumerate(self.server.reporters): 527*67e74705SXin Li selected = (i == active) 528*67e74705SXin Li if selected: 529*67e74705SXin Li selectedStr = ' selected' 530*67e74705SXin Li else: 531*67e74705SXin Li selectedStr = '' 532*67e74705SXin Li reporterSelections.append('<option value="%d"%s>%s</option>'%(i,selectedStr,r.getName())) 533*67e74705SXin Li options = '\n'.join([ o.getHTML(r,title,getConfigOption) for o in r.getParameters()]) 534*67e74705SXin Li display = ('none','')[selected] 535*67e74705SXin Li reporterOptions.append("""\ 536*67e74705SXin Li<tr id="%sReporterOptions" style="display:%s"> 537*67e74705SXin Li <td class="form_label">%s Options</td> 538*67e74705SXin Li <td class="form_value"> 539*67e74705SXin Li <table class="form_inner_group"> 540*67e74705SXin Li%s 541*67e74705SXin Li </table> 542*67e74705SXin Li </td> 543*67e74705SXin Li</tr> 544*67e74705SXin Li"""%(r.getName(),display,r.getName(),options)) 545*67e74705SXin Li reporterSelections = '\n'.join(reporterSelections) 546*67e74705SXin Li reporterOptionsDivs = '\n'.join(reporterOptions) 547*67e74705SXin Li reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters])) 548*67e74705SXin Li 549*67e74705SXin Li if c.files: 550*67e74705SXin Li fieldSize = min(5, len(c.files)) 551*67e74705SXin Li attachFileOptions = '\n'.join(["""\ 552*67e74705SXin Li<option value="%d" selected>%s</option>""" % (i,v) for i,v in enumerate(c.files)]) 553*67e74705SXin Li attachFileRow = """\ 554*67e74705SXin Li<tr> 555*67e74705SXin Li <td class="form_label">Attach:</td> 556*67e74705SXin Li <td class="form_value"> 557*67e74705SXin Li<select style="width:100%%" name="files" multiple size=%d> 558*67e74705SXin Li%s 559*67e74705SXin Li</select> 560*67e74705SXin Li </td> 561*67e74705SXin Li</tr> 562*67e74705SXin Li""" % (min(5, len(c.files)), attachFileOptions) 563*67e74705SXin Li else: 564*67e74705SXin Li attachFileRow = "" 565*67e74705SXin Li 566*67e74705SXin Li result = """<html> 567*67e74705SXin Li<head> 568*67e74705SXin Li <title>File Bug</title> 569*67e74705SXin Li <link rel="stylesheet" type="text/css" href="/scanview.css" /> 570*67e74705SXin Li</head> 571*67e74705SXin Li<script language="javascript" type="text/javascript"> 572*67e74705SXin Livar reporters = %(reportersArray)s; 573*67e74705SXin Lifunction updateReporterOptions() { 574*67e74705SXin Li index = document.getElementById('reporter').selectedIndex; 575*67e74705SXin Li for (var i=0; i < reporters.length; ++i) { 576*67e74705SXin Li o = document.getElementById(reporters[i] + "ReporterOptions"); 577*67e74705SXin Li if (i == index) { 578*67e74705SXin Li o.style.display = ""; 579*67e74705SXin Li } else { 580*67e74705SXin Li o.style.display = "none"; 581*67e74705SXin Li } 582*67e74705SXin Li } 583*67e74705SXin Li} 584*67e74705SXin Li</script> 585*67e74705SXin Li<body onLoad="updateReporterOptions()"> 586*67e74705SXin Li<h3> 587*67e74705SXin Li<a href="/">Summary</a> > 588*67e74705SXin Li%(reportingFor)s 589*67e74705SXin LiFile Bug</h3> 590*67e74705SXin Li<form name="form" action="/report_submit" method="post"> 591*67e74705SXin Li<input type="hidden" name="report" value="%(report)s"> 592*67e74705SXin Li 593*67e74705SXin Li<table class="form"> 594*67e74705SXin Li<tr><td> 595*67e74705SXin Li<table class="form_group"> 596*67e74705SXin Li<tr> 597*67e74705SXin Li <td class="form_clabel">Title:</td> 598*67e74705SXin Li <td class="form_value"> 599*67e74705SXin Li <input type="text" name="title" size="50" value="%(title)s"> 600*67e74705SXin Li </td> 601*67e74705SXin Li</tr> 602*67e74705SXin Li<tr> 603*67e74705SXin Li <td class="form_label">Description:</td> 604*67e74705SXin Li <td class="form_value"> 605*67e74705SXin Li<textarea rows="10" cols="80" name="description"> 606*67e74705SXin Li%(description)s 607*67e74705SXin Li</textarea> 608*67e74705SXin Li </td> 609*67e74705SXin Li</tr> 610*67e74705SXin Li 611*67e74705SXin Li%(attachFileRow)s 612*67e74705SXin Li 613*67e74705SXin Li</table> 614*67e74705SXin Li<br> 615*67e74705SXin Li<table class="form_group"> 616*67e74705SXin Li<tr> 617*67e74705SXin Li <td class="form_clabel">Method:</td> 618*67e74705SXin Li <td class="form_value"> 619*67e74705SXin Li <select id="reporter" name="reporter" onChange="updateReporterOptions()"> 620*67e74705SXin Li %(reporterSelections)s 621*67e74705SXin Li </select> 622*67e74705SXin Li </td> 623*67e74705SXin Li</tr> 624*67e74705SXin Li%(reporterOptionsDivs)s 625*67e74705SXin Li</table> 626*67e74705SXin Li<br> 627*67e74705SXin Li</td></tr> 628*67e74705SXin Li<tr><td class="form_submit"> 629*67e74705SXin Li <input align="right" type="submit" name="Submit" value="Submit"> 630*67e74705SXin Li</td></tr> 631*67e74705SXin Li</table> 632*67e74705SXin Li</form> 633*67e74705SXin Li 634*67e74705SXin Li%(extraIFrame)s 635*67e74705SXin Li 636*67e74705SXin Li</body> 637*67e74705SXin Li</html>"""%locals() 638*67e74705SXin Li 639*67e74705SXin Li return self.send_string(result) 640*67e74705SXin Li 641*67e74705SXin Li def send_head(self, fields=None): 642*67e74705SXin Li if (self.server.options.onlyServeLocal and 643*67e74705SXin Li self.client_address[0] != '127.0.0.1'): 644*67e74705SXin Li return self.send_error(401, 'Unauthorized host.') 645*67e74705SXin Li 646*67e74705SXin Li if fields is None: 647*67e74705SXin Li fields = {} 648*67e74705SXin Li self.fields = fields 649*67e74705SXin Li 650*67e74705SXin Li o = urlparse.urlparse(self.path) 651*67e74705SXin Li self.fields = parse_query(o.query, fields) 652*67e74705SXin Li path = posixpath.normpath(urllib.unquote(o.path)) 653*67e74705SXin Li 654*67e74705SXin Li # Split the components and strip the root prefix. 655*67e74705SXin Li components = path.split('/')[1:] 656*67e74705SXin Li 657*67e74705SXin Li # Special case some top-level entries. 658*67e74705SXin Li if components: 659*67e74705SXin Li name = components[0] 660*67e74705SXin Li if len(components)==2: 661*67e74705SXin Li if name=='report': 662*67e74705SXin Li return self.send_report(components[1]) 663*67e74705SXin Li elif name=='open': 664*67e74705SXin Li return self.send_open_report(components[1]) 665*67e74705SXin Li elif len(components)==1: 666*67e74705SXin Li if name=='quit': 667*67e74705SXin Li self.server.halt() 668*67e74705SXin Li return self.send_string('Goodbye.', 'text/plain') 669*67e74705SXin Li elif name=='report_submit': 670*67e74705SXin Li return self.send_report_submit() 671*67e74705SXin Li elif name=='report_crashes': 672*67e74705SXin Li overrides = { 'ScanView' : {}, 673*67e74705SXin Li 'Radar' : {}, 674*67e74705SXin Li 'Email' : {} } 675*67e74705SXin Li for i,r in enumerate(self.server.reporters): 676*67e74705SXin Li if r.getName() == 'Radar': 677*67e74705SXin Li overrides['ScanView']['reporter'] = i 678*67e74705SXin Li break 679*67e74705SXin Li overrides['Radar']['Component'] = 'llvm - checker' 680*67e74705SXin Li overrides['Radar']['Component Version'] = 'X' 681*67e74705SXin Li return self.send_report(None, overrides) 682*67e74705SXin Li elif name=='favicon.ico': 683*67e74705SXin Li return self.send_path(posixpath.join(kShare,'bugcatcher.ico')) 684*67e74705SXin Li 685*67e74705SXin Li # Match directory entries. 686*67e74705SXin Li if components[-1] == '': 687*67e74705SXin Li components[-1] = 'index.html' 688*67e74705SXin Li 689*67e74705SXin Li relpath = '/'.join(components) 690*67e74705SXin Li path = posixpath.join(self.server.root, relpath) 691*67e74705SXin Li 692*67e74705SXin Li if self.server.options.debug > 1: 693*67e74705SXin Li print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0], 694*67e74705SXin Li path) 695*67e74705SXin Li return self.send_path(path) 696*67e74705SXin Li 697*67e74705SXin Li def send_404(self): 698*67e74705SXin Li self.send_error(404, "File not found") 699*67e74705SXin Li return None 700*67e74705SXin Li 701*67e74705SXin Li def send_path(self, path): 702*67e74705SXin Li # If the requested path is outside the root directory, do not open it 703*67e74705SXin Li rel = os.path.abspath(path) 704*67e74705SXin Li if not rel.startswith(os.path.abspath(self.server.root)): 705*67e74705SXin Li return self.send_404() 706*67e74705SXin Li 707*67e74705SXin Li ctype = self.guess_type(path) 708*67e74705SXin Li if ctype.startswith('text/'): 709*67e74705SXin Li # Patch file instead 710*67e74705SXin Li return self.send_patched_file(path, ctype) 711*67e74705SXin Li else: 712*67e74705SXin Li mode = 'rb' 713*67e74705SXin Li try: 714*67e74705SXin Li f = open(path, mode) 715*67e74705SXin Li except IOError: 716*67e74705SXin Li return self.send_404() 717*67e74705SXin Li return self.send_file(f, ctype) 718*67e74705SXin Li 719*67e74705SXin Li def send_file(self, f, ctype): 720*67e74705SXin Li # Patch files to add links, but skip binary files. 721*67e74705SXin Li self.send_response(200) 722*67e74705SXin Li self.send_header("Content-type", ctype) 723*67e74705SXin Li fs = os.fstat(f.fileno()) 724*67e74705SXin Li self.send_header("Content-Length", str(fs[6])) 725*67e74705SXin Li self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) 726*67e74705SXin Li self.end_headers() 727*67e74705SXin Li return f 728*67e74705SXin Li 729*67e74705SXin Li def send_string(self, s, ctype='text/html', headers=True, mtime=None): 730*67e74705SXin Li if headers: 731*67e74705SXin Li self.send_response(200) 732*67e74705SXin Li self.send_header("Content-type", ctype) 733*67e74705SXin Li self.send_header("Content-Length", str(len(s))) 734*67e74705SXin Li if mtime is None: 735*67e74705SXin Li mtime = self.dynamic_mtime 736*67e74705SXin Li self.send_header("Last-Modified", self.date_time_string(mtime)) 737*67e74705SXin Li self.end_headers() 738*67e74705SXin Li return StringIO.StringIO(s) 739*67e74705SXin Li 740*67e74705SXin Li def send_patched_file(self, path, ctype): 741*67e74705SXin Li # Allow a very limited set of variables. This is pretty gross. 742*67e74705SXin Li variables = {} 743*67e74705SXin Li variables['report'] = '' 744*67e74705SXin Li m = kReportFileRE.match(path) 745*67e74705SXin Li if m: 746*67e74705SXin Li variables['report'] = m.group(2) 747*67e74705SXin Li 748*67e74705SXin Li try: 749*67e74705SXin Li f = open(path,'r') 750*67e74705SXin Li except IOError: 751*67e74705SXin Li return self.send_404() 752*67e74705SXin Li fs = os.fstat(f.fileno()) 753*67e74705SXin Li data = f.read() 754*67e74705SXin Li for a,b in kReportReplacements: 755*67e74705SXin Li data = a.sub(b % variables, data) 756*67e74705SXin Li return self.send_string(data, ctype, mtime=fs.st_mtime) 757*67e74705SXin Li 758*67e74705SXin Li 759*67e74705SXin Lidef create_server(address, options, root): 760*67e74705SXin Li import Reporter 761*67e74705SXin Li 762*67e74705SXin Li reporters = Reporter.getReporters() 763*67e74705SXin Li 764*67e74705SXin Li return ScanViewServer(address, ScanViewRequestHandler, 765*67e74705SXin Li root, 766*67e74705SXin Li reporters, 767*67e74705SXin Li options) 768