Home | History | Annotate | Download | only in nacl
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """A tiny web server.
      7 
      8 This is intended to be used for testing, and only run from within the examples
      9 directory.
     10 """
     11 
     12 import BaseHTTPServer
     13 import logging
     14 import optparse
     15 import os
     16 import SimpleHTTPServer
     17 import SocketServer
     18 import sys
     19 import urlparse
     20 
     21 
     22 EXAMPLE_PATH=os.path.dirname(os.path.abspath(__file__))
     23 NACL_SDK_ROOT = os.getenv('NACL_SDK_ROOT', os.path.dirname(EXAMPLE_PATH))
     24 
     25 
     26 if os.path.exists(NACL_SDK_ROOT):
     27   sys.path.append(os.path.join(NACL_SDK_ROOT, 'tools'))
     28   import decode_dump
     29   import getos
     30 else:
     31   NACL_SDK_ROOT=None
     32 
     33 last_nexe = None
     34 last_nmf = None
     35 
     36 logging.getLogger().setLevel(logging.INFO)
     37 
     38 # Using 'localhost' means that we only accept connections
     39 # via the loop back interface.
     40 SERVER_PORT = 5103
     41 SERVER_HOST = ''
     42 
     43 # We only run from the examples directory so that not too much is exposed
     44 # via this HTTP server.  Everything in the directory is served, so there should
     45 # never be anything potentially sensitive in the serving directory, especially
     46 # if the machine might be a multi-user machine and not all users are trusted.
     47 # We only serve via the loopback interface.
     48 def SanityCheckDirectory():
     49   httpd_path = os.path.abspath(os.path.dirname(__file__))
     50   serve_path = os.path.abspath(os.getcwd())
     51 
     52   # Verify we are serving from the directory this script came from, or bellow
     53   if serve_path[:len(httpd_path)] == httpd_path:
     54     return
     55   logging.error('For security, httpd.py should only be run from within the')
     56   logging.error('example directory tree.')
     57   logging.error('We are currently in %s.' % serve_path)
     58   sys.exit(1)
     59 
     60 
     61 # An HTTP server that will quit when |is_running| is set to False.  We also use
     62 # SocketServer.ThreadingMixIn in order to handle requests asynchronously for
     63 # faster responses.
     64 class QuittableHTTPServer(SocketServer.ThreadingMixIn,
     65                           BaseHTTPServer.HTTPServer):
     66   def serve_forever(self, timeout=0.5):
     67     self.is_running = True
     68     self.timeout = timeout
     69     while self.is_running:
     70       self.handle_request()
     71 
     72   def shutdown(self):
     73     self.is_running = False
     74     return 1
     75 
     76 
     77 # "Safely" split a string at |sep| into a [key, value] pair.  If |sep| does not
     78 # exist in |str|, then the entire |str| is the key and the value is set to an
     79 # empty string.
     80 def KeyValuePair(str, sep='='):
     81   if sep in str:
     82     return str.split(sep)
     83   else:
     84     return [str, '']
     85 
     86 
     87 # A small handler that looks for '?quit=1' query in the path and shuts itself
     88 # down if it finds that parameter.
     89 class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
     90   def send_head(self):
     91     """Common code for GET and HEAD commands.
     92 
     93     This sends the response code and MIME headers.
     94 
     95     Return value is either a file object (which has to be copied
     96     to the outputfile by the caller unless the command was HEAD,
     97     and must be closed by the caller under all circumstances), or
     98     None, in which case the caller has nothing further to do.
     99 
    100     """
    101     path = self.translate_path(self.path)
    102     f = None
    103     if os.path.isdir(path):
    104         if not self.path.endswith('/'):
    105             # redirect browser - doing basically what apache does
    106             self.send_response(301)
    107             self.send_header("Location", self.path + "/")
    108             self.end_headers()
    109             return None
    110         for index in "index.html", "index.htm":
    111             index = os.path.join(path, index)
    112             if os.path.exists(index):
    113                 path = index
    114                 break
    115         else:
    116             return self.list_directory(path)
    117     ctype = self.guess_type(path)
    118     try:
    119         # Always read in binary mode. Opening files in text mode may cause
    120         # newline translations, making the actual size of the content
    121         # transmitted *less* than the content-length!
    122         f = open(path, 'rb')
    123     except IOError:
    124         self.send_error(404, "File not found")
    125         return None
    126     self.send_response(200)
    127     self.send_header("Content-type", ctype)
    128     fs = os.fstat(f.fileno())
    129     self.send_header("Content-Length", str(fs[6]))
    130     self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
    131     self.send_header('Cache-Control','no-cache, must-revalidate')
    132     self.send_header('Expires','-1')
    133     self.end_headers()
    134     return f
    135 
    136   def do_GET(self):
    137     global last_nexe, last_nmf
    138     (_, _, path, query, _) = urlparse.urlsplit(self.path)
    139     url_params = dict([KeyValuePair(key_value)
    140                       for key_value in query.split('&')])
    141     if 'quit' in url_params and '1' in url_params['quit']:
    142       self.send_response(200, 'OK')
    143       self.send_header('Content-type', 'text/html')
    144       self.send_header('Content-length', '0')
    145       self.end_headers()
    146       self.server.shutdown()
    147       return
    148 
    149     if path.endswith('.nexe'):
    150       last_nexe = path
    151     if path.endswith('.nmf'):
    152       last_nmf = path
    153 
    154     SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
    155 
    156   def do_POST(self):
    157     (_, _,path, query, _) = urlparse.urlsplit(self.path)
    158     if 'Content-Length' in self.headers:
    159       if not NACL_SDK_ROOT:
    160         self.wfile('Could not find NACL_SDK_ROOT to decode trace.')
    161         return
    162       data = self.rfile.read(int(self.headers['Content-Length']))
    163       nexe = '.' + last_nexe
    164       nmf = '.' + last_nmf
    165       addr = os.path.join(NACL_SDK_ROOT, 'toolchain', 
    166                           getos.GetPlatform() + '_x86_newlib', 
    167                           'bin', 'x86_64-nacl-addr2line')
    168       decoder = decode_dump.CoreDecoder(nexe, nmf, addr, None, None)
    169       info = decoder.Decode(data)
    170       trace = decoder.StackTrace(info)
    171       decoder.PrintTrace(trace, sys.stdout)
    172       decoder.PrintTrace(trace, self.wfile)
    173 
    174 
    175 def Run(server_address,
    176         server_class=QuittableHTTPServer,
    177         handler_class=QuittableHTTPHandler):
    178   httpd = server_class(server_address, handler_class)
    179   logging.info("Starting local server on port %d", server_address[1])
    180   logging.info("To shut down send http://localhost:%d?quit=1",
    181                server_address[1])
    182   try:
    183     httpd.serve_forever()
    184   except KeyboardInterrupt:
    185     logging.info("Received keyboard interrupt.")
    186     httpd.server_close()
    187 
    188   logging.info("Shutting down local server on port %d", server_address[1])
    189 
    190 
    191 def main():
    192   usage_str = "usage: %prog [options] [optional_portnum]"
    193   parser = optparse.OptionParser(usage=usage_str)
    194   parser.add_option(
    195     '--no_dir_check', dest='do_safe_check',
    196     action='store_false', default=True,
    197     help='Do not ensure that httpd.py is being run from a safe directory.')
    198   (options, args) = parser.parse_args(sys.argv)
    199   if options.do_safe_check:
    200     SanityCheckDirectory()
    201   if len(args) > 2:
    202     print 'Too many arguments specified.'
    203     parser.print_help()
    204   elif len(args) == 2:
    205     Run((SERVER_HOST, int(args[1])))
    206   else:
    207     Run((SERVER_HOST, SERVER_PORT))
    208   return 0
    209 
    210 
    211 if __name__ == '__main__':
    212   sys.exit(main())
    213