ScanView.py 25 KB

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