ScanView.py 25 KB

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