ScanView.py 25 KB

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