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 8This is intended to be used for testing, and only run from within the examples 9directory. 10""" 11 12import BaseHTTPServer 13import logging 14import optparse 15import os 16import SimpleHTTPServer 17import SocketServer 18import sys 19import urlparse 20 21 22EXAMPLE_PATH=os.path.dirname(os.path.abspath(__file__)) 23NACL_SDK_ROOT = os.getenv('NACL_SDK_ROOT', os.path.dirname(EXAMPLE_PATH)) 24 25 26if os.path.exists(NACL_SDK_ROOT): 27 sys.path.append(os.path.join(NACL_SDK_ROOT, 'tools')) 28 import decode_dump 29 import getos 30else: 31 NACL_SDK_ROOT=None 32 33last_nexe = None 34last_nmf = None 35 36logging.getLogger().setLevel(logging.INFO) 37 38# Using 'localhost' means that we only accept connections 39# via the loop back interface. 40SERVER_PORT = 5103 41SERVER_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. 48def 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. 64class 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. 80def 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. 89class 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 175def 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 191def 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 211if __name__ == '__main__': 212 sys.exit(main()) 213