#!/usr/bin/env python import BaseHTTPServer import cgi import glob import json import mimetypes import os import os.path import urlparse # Various config settings for the python server SETTINGS = { 'port': 8080, 'logging': False, 'api-save': '/lib/weltmeister/api/save.php', 'api-browse': '/lib/weltmeister/api/browse.php', 'api-glob': '/lib/weltmeister/api/glob.php', 'image-types': ['.png', '.jpg', '.gif', '.jpeg'], 'mimetypes': { 'ogg': 'audio/ogg' } } # Override port if we are on a Heroku server if os.environ.get('PORT'): SETTINGS['port'] = int(os.environ.get('PORT')) # Get the current directory BASE_DIR = os.path.dirname(os.path.abspath(__file__)) if BASE_DIR[-1] != '/': BASE_DIR += '/' # Blank favicon - prevents silly 404s from occuring if no favicon is supplied FAVICON_GIF = 'GIF89a\x01\x00\x01\x00\xf0\x00\x00\xff\xff\xff\x00\x00\x00!\xff\x0bXMP DataXMP\x02?x\x00!\xf9\x04\x05\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00@\x02\x02D\x01\x00;' class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): def send_json(self, obj, code=200, headers=None): 'Send response as JSON' if not headers: headers = {} headers['Content-Type'] = 'application/json' self.send_response(json.dumps(obj), code, headers) def send_response(self, mesg, code=200, headers=None): 'Wraps sending a response down' if not headers: headers = {} if 'Content-Type' not in headers: headers['Content-Type'] = 'text/html' BaseHTTPServer.BaseHTTPRequestHandler.send_response(self, code) self.send_header('Content-Length', len(mesg)) if headers: for k, v in headers.iteritems(): self.send_header(k, v) self.end_headers() self.wfile.write(mesg) def log_request(self, *args, **kwargs): 'If logging is disabled ' if SETTINGS['logging']: BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, *args, **kwargs) def init_request(self): parts = self.path.split('?', 1) self.post_params = {} if len(parts) == 1: self.file_path = parts[0] self.query_params = {} else: self.file_path = parts[0] self.query_params = urlparse.parse_qs(parts[1]) def do_GET(self): self.init_request() self.route_request('GET') def do_HEAD(self): self.init_request() self.send_response('') def do_POST(self): self.init_request() # From http://stackoverflow.com/questions/4233218/python-basehttprequesthandler-post-variables ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) if ctype == 'multipart/form-data': self.post_params = cgi.parse_multipart(self.rfile, pdict) elif ctype == 'application/x-www-form-urlencoded': length = int(self.headers.getheader('content-length')) self.post_params = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1) self.route_request('POST') def route_request(self, method='GET'): if self.file_path == SETTINGS['api-save']: self.save() elif self.file_path == SETTINGS['api-browse']: self.browse() elif self.file_path == SETTINGS['api-glob']: self.glob() elif method == 'GET': self.serve_file() else: self.barf() def save(self): resp = {'error': 0} if 'path' in self.post_params and 'data' in self.post_params: path = self.post_params['path'][0] path = os.path.join(BASE_DIR, path.replace('..', '')) data = self.post_params['data'][0] if path.endswith('.js'): try: open(path, 'w').write(data) except: resp['error'] = 2 resp['msg'] = 'Couldn\'t write to file %d' % path else: resp['error'] = 3 resp['msg'] = 'File must have a .js suffix' else: resp['error'] = 1 resp['msg'] = 'No Data or Path specified' return self.send_json(resp) def browse(self): # Get the directory to scan dir = '' if 'dir' in self.query_params: dir = self.query_params['dir'][0].replace('..', '') if dir[-1] != '/': dir += '/' # Get the dir and files dirs = [os.path.join(dir, d) for d in os.listdir(os.path.join(BASE_DIR, dir)) if os.path.isdir(os.path.join(dir, d))] files = glob.glob(dir + '*.*') # Filter on file types if 'type' in self.query_params: types = self.query_params['type'] if 'images' in types: files = [f for f in files if os.path.splitext(f)[1] in SETTINGS['image-types']] elif 'scripts' in types: files = [f for f in files if os.path.splitext(f)[1] == '.js'] if os.name == 'nt': files = [f.replace('\\', '/') for f in files] dirs = [d.replace('\\', '/') for d in dirs] response = { 'files': files, 'dirs': dirs, 'parent': False if dir == './' else os.path.dirname(os.path.dirname(dir)) } return self.send_json(response) def glob(self): globs = self.query_params['glob[]'] files = [] for g in globs: g = g.replace('..', '') more = glob.glob(g) files.extend(more) if os.name == 'nt': files = [f.replace('\\', '/') for f in files] return self.send_json(files) def guess_type(self, path): type, _ = mimetypes.guess_type(path) if not type: ext = path.split('.')[-1] if ext in SETTINGS['mimetypes'].keys(): type = SETTINGS['mimetypes'][ext] # Winblows hack if os.name == "nt" and type.startswith("image"): type = type.replace("x-", "") return type def serve_file(self): path = self.file_path if path == '/': path = 'index.html' elif path == '/editor': path = 'weltmeister.html' # Remove the leading forward slash if path[0] == '/': path = path[1:] # Security, remove the .. path = path.replace('..', '') # Determine the fullpath path = os.path.join(BASE_DIR, path) try: data = open(path, 'rb').read() type = self.guess_type(path) self.send_response(data, 200, headers={'Content-Type': type}) except: if '/favicon.ico' in path: self.send_response(FAVICON_GIF, 200, headers={'Content-Type': 'image/gif'}) else: self.send_response('', 404) def barf(self): self.send_response('barf', 405) def main(): addr = ('', SETTINGS['port']) server = BaseHTTPServer.HTTPServer(addr, HTTPHandler) print 'Running ImpactJS Server\nGame: http://localhost:%d\nEditor: http://localhost:%d/editor' % (addr[1], addr[1]) server.serve_forever() if __name__ == '__main__': main()