Home | History | Annotate | Download | only in scan-view
      1 import BaseHTTPServer
      2 import SimpleHTTPServer
      3 import os
      4 import sys
      5 import urllib, urlparse
      6 import posixpath
      7 import StringIO
      8 import re
      9 import shutil
     10 import threading
     11 import time
     12 import socket
     13 import itertools
     14 
     15 import Reporter
     16 import ConfigParser
     17 
     18 ###
     19 # Various patterns matched or replaced by server.
     20 
     21 kReportFileRE = re.compile('(.*/)?report-(.*)\\.html')
     22 
     23 kBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
     24 
     25 #  <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" -->
     26 
     27 kReportCrashEntryRE = re.compile('<!-- REPORTPROBLEM (.*?)-->')
     28 kReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"')
     29 
     30 kReportReplacements = []
     31 
     32 # Add custom javascript.
     33 kReportReplacements.append((re.compile('<!-- SUMMARYENDHEAD -->'), """\
     34 <script language="javascript" type="text/javascript">
     35 function 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.
     49 kReportReplacements.append((re.compile('<!-- REPORTBUGCOL -->'), 
     50                             '<td></td><td></td>'))
     51 
     52 # Insert report bug and open file links.
     53 kReportReplacements.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 
     57 kReportReplacements.append((re.compile('<!-- REPORTHEADER -->'),
     58                                        '<h3><a href="/">Summary</a> > Report %(report)s</h3>'))
     59 
     60 kReportReplacements.append((re.compile('<!-- REPORTSUMMARYEXTRA -->'),
     61                             '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>'))
     62 
     63 # Insert report crashes link.
     64 
     65 # 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>.'))
     72 
     73 ###
     74 # Other simple parameters
     75 
     76 kResources = posixpath.join(posixpath.dirname(__file__), 'Resources')
     77 kConfigPath = os.path.expanduser('~/.scanview.cfg')
     78 
     79 ###
     80 
     81 __version__ = "0.1"
     82 
     83 __all__ = ["create_server"]
     84 
     85 class 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
     92         self.success = False
     93         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],)
    100             self.status = self.reporter.fileReport(self.report, self.parameters)
    101             self.success = True
    102             time.sleep(3)
    103             if self.server.options.debug:
    104                 print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
    105         except Reporter.ReportFailure,e:
    106             self.status = e.value
    107         except Exception,e:
    108             s = StringIO.StringIO()
    109             import traceback
    110             print >>s,'<b>Unhandled Exception</b><br><pre>'
    111             traceback.print_exc(e,file=s)
    112             print >>s,'</pre>'
    113             self.status = s.getvalue()
    114 
    115 class 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
    122         self.config = None
    123         self.load_config()
    124 
    125     def load_config(self):
    126         self.config = ConfigParser.RawConfigParser()
    127 
    128         # Add defaults
    129         self.config.add_section('ScanView')
    130         for r in self.reporters:
    131             self.config.add_section(r.getName())
    132             for p in r.getParameters():
    133               if p.saveConfigValue():
    134                 self.config.set(r.getName(), p.getName(), '')
    135 
    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         
    155     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 
    169     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 
    175     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
    182         BaseHTTPServer.HTTPServer.handle_error(self, request, client_address)
    183 
    184 # Borrowed from Quixote, with simplifications.
    185 def 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('+', ' '))
    196         item = fields.get(name)
    197         if item is None:
    198             fields[name] = [value]
    199         else:
    200             item.append(value)
    201     return fields
    202 
    203 class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    204     server_version = "ScanViewServer/" + __version__
    205     dynamic_mtime = time.time()
    206 
    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 
    253     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 
    264     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()        
    273             
    274     def get_scalar_field(self, name):
    275         if name in self.fields:
    276             return self.fields[name][0]
    277         else:
    278             return None
    279 
    280     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])
    294         
    295         if not title:
    296             return (False, "Missing title.")
    297         if not description:
    298             return (False, "Missing description.")
    299         try:
    300             reporterIndex = int(reporterIndex)
    301         except:
    302             return (False, "Invalid report method.")
    303         
    304         # Get the reporter and parameters.
    305         reporter = self.server.reporters[reporterIndex]
    306         parameters = {}
    307         for o in reporter.getParameters():
    308             name = '%s_%s'%(reporter.getName(),o.getName())
    309             if name not in self.fields:
    310                 return (False, 
    311                         'Missing field "%s" for %s report method.'%(name,
    312                                                                     reporter.getName()))
    313             parameters[o.getName()] = self.get_scalar_field(name)
    314 
    315         # Update config defaults.
    316         if report != 'None':
    317             self.server.config.set('ScanView', 'reporter', reporterIndex)
    318             for o in reporter.getParameters():
    319               if o.saveConfigValue():
    320                 name = o.getName()
    321                 self.server.config.set(reporter.getName(), name, parameters[name])
    322 
    323         # Create the report.
    324         bug = Reporter.BugReport(title, description, files)
    325 
    326         # Kick off a reporting thread.
    327         t = ReporterThread(bug, reporter, parameters, self.server)
    328         t.start()
    329 
    330         # Wait for thread to die...
    331         while t.isAlive():
    332             time.sleep(.25)
    333         submitStatus = t.status
    334 
    335         return (t.success, t.status)
    336 
    337     def send_report_submit(self):
    338         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')
    350 
    351         res,message = self.submit_bug(c)
    352 
    353         if res:
    354             statusClass = 'SubmitOk'
    355             statusName = 'Succeeded'
    356         else:
    357             statusClass = 'SubmitFail'
    358             statusName = 'Failed'
    359 
    360         result = """
    361 <head>
    362   <title>Bug Submission</title>
    363   <link rel="stylesheet" type="text/css" href="/scanview.css" />
    364 </head>
    365 <body>
    366 <h3>
    367 <a href="/">Summary</a> > 
    368 %(reportingFor)s
    369 %(fileBug)s
    370 Submit</h3>
    371 <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>
    395 <hr>
    396 <a href="/">Return to Summary</a>
    397 </body>
    398 </html>"""%locals()
    399         return self.send_string(result)
    400 
    401     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 
    424     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'
    434 
    435             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 = """\
    446 The clang static analyzer failed on these inputs:
    447 %s
    448 
    449 STDERR 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 = """\
    480 Bug reported by the clang static analyzer.
    481 
    482 Description: %s
    483 File: %s
    484 Line: %s
    485 """%(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)
    518 
    519         reporterSelections = []
    520         reporterOptions = []
    521 
    522         try:
    523             active = int(getConfigOption('ScanView','reporter'))
    524         except:
    525             active = 0
    526         for i,r in enumerate(self.server.reporters):
    527             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()))
    533             options = '\n'.join([ o.getHTML(r,title,getConfigOption) for o in r.getParameters()])
    534             display = ('none','')[selected]
    535             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))
    545         reporterSelections = '\n'.join(reporterSelections)
    546         reporterOptionsDivs = '\n'.join(reporterOptions)
    547         reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
    548 
    549         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 
    566         result = """<html>
    567 <head>
    568   <title>File Bug</title>
    569   <link rel="stylesheet" type="text/css" href="/scanview.css" />
    570 </head>
    571 <script language="javascript" type="text/javascript">
    572 var reporters = %(reportersArray)s;
    573 function 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) {
    578       o.style.display = "";
    579     } else {
    580       o.style.display = "none";
    581     }
    582   }
    583 }
    584 </script>
    585 <body onLoad="updateReporterOptions()">
    586 <h3>
    587 <a href="/">Summary</a> > 
    588 %(reportingFor)s
    589 File Bug</h3>
    590 <form name="form" action="/report_submit" method="post">
    591 <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">
    599     <input type="text" name="title" size="50" value="%(title)s">
    600   </td>
    601 </tr>
    602 <tr>
    603   <td class="form_label">Description:</td>
    604   <td class="form_value">
    605 <textarea rows="10" cols="80" name="description">
    606 %(description)s
    607 </textarea>
    608   </td>
    609 </tr>
    610 
    611 %(attachFileRow)s
    612 
    613 </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>
    624 %(reporterOptionsDivs)s
    625 </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>
    632 </form>
    633 
    634 %(extraIFrame)s
    635 
    636 </body>
    637 </html>"""%locals()
    638 
    639         return self.send_string(result)
    640 
    641     def send_head(self, fields=None):
    642         if (self.server.options.onlyServeLocal and
    643             self.client_address[0] != '127.0.0.1'):
    644             return self.send_error(401, 'Unauthorized host.')
    645 
    646         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]
    660             if len(components)==2:
    661                 if name=='report':
    662                     return self.send_report(components[1])
    663                 elif name=='open':
    664                     return self.send_open_report(components[1])
    665             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':
    670                     return self.send_report_submit()
    671                 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)
    682                 elif name=='favicon.ico':
    683                     return self.send_path(posixpath.join(kResources,'bugcatcher.ico'))
    684         
    685         # Match directory entries.
    686         if components[-1] == '':
    687             components[-1] = 'index.html'
    688 
    689         relpath = '/'.join(components)
    690         path = posixpath.join(self.server.root, relpath)
    691 
    692         if self.server.options.debug > 1:
    693             print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
    694                                                                  path)
    695         return self.send_path(path)
    696 
    697     def send_404(self):
    698         self.send_error(404, "File not found")
    699         return None
    700 
    701     def send_path(self, path):
    702         # If the requested path is outside the root directory, do not open it
    703         rel = os.path.abspath(path)
    704         if not rel.startswith(os.path.abspath(self.server.root)):
    705           return self.send_404()
    706         
    707         ctype = self.guess_type(path)
    708         if ctype.startswith('text/'):
    709             # Patch file instead
    710             return self.send_patched_file(path, ctype)
    711         else:
    712             mode = 'rb'
    713         try:
    714             f = open(path, mode)
    715         except IOError:
    716             return self.send_404()
    717         return self.send_file(f, ctype)
    718 
    719     def send_file(self, f, ctype):
    720         # Patch files to add links, but skip binary files.
    721         self.send_response(200)
    722         self.send_header("Content-type", ctype)
    723         fs = os.fstat(f.fileno())
    724         self.send_header("Content-Length", str(fs[6]))
    725         self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
    726         self.end_headers()
    727         return f
    728 
    729     def send_string(self, s, ctype='text/html', headers=True, mtime=None):
    730         if headers:
    731             self.send_response(200)
    732             self.send_header("Content-type", ctype)
    733             self.send_header("Content-Length", str(len(s)))
    734             if mtime is None:
    735                 mtime = self.dynamic_mtime
    736             self.send_header("Last-Modified", self.date_time_string(mtime))
    737             self.end_headers()
    738         return StringIO.StringIO(s)
    739 
    740     def send_patched_file(self, path, ctype):
    741         # Allow a very limited set of variables. This is pretty gross.
    742         variables = {}
    743         variables['report'] = ''
    744         m = kReportFileRE.match(path)
    745         if m:
    746             variables['report'] = m.group(2)
    747 
    748         try:
    749             f = open(path,'r')
    750         except IOError:
    751             return self.send_404()
    752         fs = os.fstat(f.fileno())
    753         data = f.read()
    754         for a,b in kReportReplacements:
    755             data = a.sub(b % variables, data)
    756         return self.send_string(data, ctype, mtime=fs.st_mtime)
    757 
    758 
    759 def create_server(address, options, root):
    760     import Reporter
    761 
    762     reporters = Reporter.getReporters()
    763 
    764     return ScanViewServer(address, ScanViewRequestHandler,
    765                           root,
    766                           reporters,
    767                           options)
    768