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