def add_mod(request: Request, game: dict[str, Any], version: dict[str, Any]): if request.POST: if not request.POST.get('id', True): DB.execute('INSERT INTO Mods (game, name, description, created, image) VALUES (?, ?, ?, ?, ?)', (game['id'], request.POST['name'], request.POST['description'], request.POST['created'], list(request.FILES['icon'].values())[0].read())) request.POST['id'] = DB.execute('SELECT last_insert_rowid() as row').fetchone()['row'] DB.execute('INSERT INTO ModVersions (mod, version, game_version, released) VALUES (?, ?, ?, ?)', (request.POST['id'], request.POST['version'], version['id'], request.POST['released'])) DB.commit() return '', 307, {'Location': f'/{game["name"]}/mods/{request.POST["name"]}/{request.POST["version"]}/'} return render(request, 'html/add_mod.html', {'game': game, 'version': version, 'mods': DB.execute('SELECT name, created, id FROM Mods WHERE game=? AND id NOT IN (SELECT mod FROM ModVersions WHERE game_version=?) ORDER BY name', (game['id'], version['id'])).fetchall()})
def add_item(request: Request, game: dict[str, Any], version: dict[str, Any]): if request.POST: if not request.POST.get('id', True): DB.execute('INSERT INTO GameItems (name, image) VALUES (?, ?)', (request.POST['name'], list(request.FILES['icon'].values())[0].read())) request.POST['id'] = DB.execute('SELECT last_insert_rowid() as row').fetchone()['row'] DB.execute('INSERT INTO GameVersionItemMap VALUES (?, ?)', (version['id'], request.POST['id'])) DB.commit() return '', 307, {'Location': f'/{game["name"]}/{version["version"]}/items/{request.POST["name"]}/'} return render(request, 'html/add_item.html', {'game': game, 'version': version, 'items': DB.execute('SELECT name, GameItems.id FROM GameItems INNER JOIN GameVersionItemMap GVIM ON GameItems.id = GVIM.item INNER JOIN GameVersions GV ON GV.id = GVIM.version WHERE game=? AND GameItems.id NOT IN (SELECT item FROM GameVersionItemMap WHERE version=?) ORDER BY name', (game['id'], version['id'])).fetchall()})
def error_output(self, environ: dict, start_response: Callable) -> List[bytes]: """Override to email ADMINS or send debug page.""" if environ: environ = Request(environ) er = ExceptionReporter(environ, *sys.exc_info()).get_traceback_html()[0] if App._admins and not App.debug and Mail.default_email['host']: Mail(f'Internal Server Error: {(environ or {}).get("PATH_INFO", "???")}', '\n'.join(str(e) for e in sys.exc_info()), App._admins, html=er.decode()).embed().send() start_response(self.error_status, self.error_headers[:] if not App.debug else [('Content-Type', 'text/html')], sys.exc_info()) return [er] if App.debug else [self.error_body]
def wrapped(*args, **kwargs): request = kwargs.get( 'request', args[0] if args else Request() ) # get request args otherwise use blank data (only gets correct args when doing "@route() \n @auth" otherwise "@auth \n @route()" it will not have the request argument if 'auth' not in request.COOKIE or request.COOKIE[ 'auth'].value not in set(): return '', 303, { 'Location': f'/login/?next={request.PATH_INFO}' } # should change /login/?next= to the url of login for you application return f(*args, **kwargs)
def setup_env(cls, port: int): if not cls.base_environ: cls.base_environ = Request({ 'SERVER_NAME': socket.gethostname(), 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': str(port), 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '' })
def __call__(self, env: dict, start_response: Callable): path = env['PATH_INFO'] f = self._handle_route_cache(path) env = Request(env) headers = {} cookie = set(env['COOKIE'].output().replace('\r', '').split('\n')) called = False try: if isinstance(f, bool): result = self.error_routes.get( 404, (lambda e: (b'', 404, { 'Content-Type': 'text/public' })))(env) if path[-1:] != '/' and 'result' not in locals() and not f[ 0].no_end_slash: # auto rediects to url that ends in / if no_end_slash is False result = self.error_routes.get(307, (lambda e, *a, **k: (b'', 307, { 'Location': f'{path}/' })))(env, *f[1], **f[2]) if 'result' not in locals(): r = self._handle_cors(f, headers, env) if ('*' not in f[0].methods and env['REQUEST_METHOD'].lower() not in f[0].methods ) or not r: # checks for allowed methods result = self.error_routes.get( 405, (lambda e, *a, **k: (b'', 405, { 'Content-Type': 'text/public' })))(env, *f[1], **f[2]) else: result = f[0](env, *f[1], **f[2]) called = True except Exception: result = self.error_routes[500](env, *f[1], **f[2]) r = self._handle_result(result, headers, cookie, env) status = int(r[1].split()[0]) if called and status in self.error_routes and status not in f[ 0].disable_default_errors: r = self._handle_result( self.error_routes[status](env, *f[1], **f[2]), headers, cookie, env) start_response(*r[1:]) return r[0]
def get_environ(self) -> Request: """Read headers / body and generate Request object. :returns:Request""" env = Request({'SERVER_PROTOCOL': self.request_version, 'SERVER_SOFTWARE': self.server_version, 'REQUEST_METHOD': self.command.upper(), 'BODY': b'', 'GET': {}, 'POST': {}, 'PATCH': {}, 'PUT': {}, 'OPTIONS': {}, 'DELETE': {}, 'FILES': {}, 'COOKIE': SimpleCookie(self.headers.get('COOKIE')), 'HEADERS': dict(self.headers), 'REMOTE_ADDR': self.client_address[0], 'CONTENT_TYPE': self.headers.get_content_type(), 'HOST': self.headers['HOST']}) env['HEADERS'] = {k.upper().strip(): v for k, v in env['HEADERS'].items()} path, env['QUERY_STRING'] = self.path.split('?', 1) if '?' in self.path else (self.path, '') env['PATH_INFO'] = unquote_plus(path, 'iso-8859-1') host = env['HEADERS'].get('X-REAL-IP') or env['HEADERS'].get('X-FORWARDED-FOR', '').split(',')[-1].strip() or self.address_string() if host != self.client_address[0]: env['REMOTE_HOST'] = host self.client_address = (host, self.client_address[1]) env['CONTENT_LENGTH'] = int(self.headers.get('content-length', '0')) if len(env['BODY']) != env['CONTENT_LENGTH']: env['BODY'] += self.rfile.read(env['CONTENT_LENGTH'] - len(env['BODY'])) boundary = re.findall(r'boundary=-*([\w]+)', self.headers.get('content-type', '')) # boundary is used to catch multipart form data (includes file uploads) content_type = env['CONTENT_TYPE'].lower() if boundary: self._get_boundary_enclosed(boundary, env) elif content_type == 'application/json' and env['BODY']: env[env['REQUEST_METHOD']] = json.loads(env['BODY']) elif content_type == 'application/x-www-form-urlencoded': env[env['REQUEST_METHOD']] = self._parse_qs(env['BODY'].decode()) elif content_type == 'multipart/form-data': for q in re.sub(r'-{15,}\d+', '+@~!@+', env['BODY'].decode().replace('\n', '')).split('+@~!@+'): if '=' in q: q = q.split(';')[1].strip().split('=', 1)[1].replace('"', '').split('\r\r') k, v = [unquote_plus(a) if a else a for a in q] v = v.replace('\r', '') request_method = env[env['REQUEST_METHOD']] if k in request_method: try: request_method[k].append(v) except AttributeError: request_method[k] = [request_method[k], v] else: request_method[k] = v if env['QUERY_STRING']: env['GET'] = self._parse_qs(env['QUERY_STRING']) return env
def __call__(self, env: dict, start_response: Callable): path = env['PATH_INFO'] env = Request(env) f_list = self._handle_route_cache(env) headers = {} cookie = set(env['COOKIE'].output().replace('\r', '').split('\n')) called = False try: if not f_list: result = self.error_routes.get(404, (lambda e: (b'', 404, {'Content-Type': 'text/public'})))(env) for f in f_list: if path[-1:] != '/' and 'result' not in locals() and not f[0].no_end_slash: # auto rediects to url that ends in / if no_end_slash is False result = self.error_routes.get(307, (lambda e, *a, **k: (b'', 307, {'Location': f'{path}/'})))(env, *f[1], **f[2]) else: r = self._handle_cors(f, headers, env) if ('*' not in f[0].methods and env['REQUEST_METHOD'].lower() not in f[0].methods) or not r: # checks for allowed methods result = self.error_routes.get(405, (lambda e, *a, **k: (b'', 405, {'Content-Type': 'text/public'})))(env, *f[1], **f[2]) else: result = f[0](env, *f[1], **f[2]) called = True l_result = len(result or []) if l_result <= 1 or l_result > 3 or isinstance(result, dict) or (l_result >= 2 and result[1] != 405): break except ResponseError as e: if e.code in self.error_routes and e.code not in f[0].disable_default_errors: result = self.error_routes[e.code](env, *f[1], **f[2]) else: if e.message is None: result = b'', e.code, {'Content-Type': 'text/public'} else: result = e.message, e.code, None except Exception as e: result = self.error_routes[500](env, *f[1], **f[2]) r = self._handle_result(result, headers, cookie, env) status = int(r[1].split()[0]) if called and status in self.error_routes and status not in f[0].disable_default_errors: r = self._handle_result(self.error_routes[status](env, *f[1], **f[2]), headers, cookie, env) start_response(*r[1:]) return r[0]
class WebServer(ThreadingTCPServer): request_queue_size = 500 allow_reuse_address = True application = None base_environ = Request() daemon_threads = True clients, handlers = {}, {} # websocket vars id_counter = 0 websocket_handlers = {'new': {}, 'message': {}, 'left': {}} def __init__(self, server_address, request_handler_class, bind_and_activate: bool = True): super().__init__(server_address, request_handler_class, bind_and_activate) def server_bind(self): """Override server_bind to store the server name.""" super().server_bind() self.setup_env(self.server_address[1]) @classmethod def attach_websocket_handler(cls, type: str, function: Callable[[dict, 'WebServer', Optional[str]], None], path: Optional[str] = None): if type not in {'new', 'message', 'left'}: raise ValueError try: cls.websocket_handlers[type][path].append(function) except KeyError: cls.websocket_handlers[type][path] = [function] @classmethod def setup_env(cls, port: int): if not cls.base_environ: cls.base_environ = Request({'SERVER_NAME': socket.gethostname(), 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': str(port), 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': ''}) def message_received(self, handler: 'RequestHandler', msg: str): self.handle(self.handlers[id(handler)], 'message', msg) def new_client(self, handler: 'RequestHandler', env: Request): self.id_counter += 1 client = { 'id': self.id_counter, 'handler': handler, 'request': env, 'handler_id': id(handler) } self.handlers[client['handler_id']] = self.clients[client['id']] = client self.handle(client, 'new') def client_left(self, handler: 'RequestHandler'): try: client = self.handlers[id(handler)] self.handle(client, 'left') del self.clients[client['id']] del self.handlers[client['handler_id']] except KeyError: pass for client in list(self.clients.values()): if client['handler'].connection._closed: del self.clients[client['id']] del self.handlers[client['handler_id']] for client in list(self.handlers.values()): if client['handler'].connection._closed: del self.clients[client['id']] del self.handlers[client['handler_id']] def handle(self, client: dict, type: str, msg: Optional[str] = None): for path, channels in self.websocket_handlers[type].items(): if not path or re.fullmatch(path, client['request'].PATH_INFO): for channel in channels: channel(client, self, *([msg] if msg is not None else [])) @staticmethod def send_message(client: dict, msg: Union[str, bytes]): client['handler'].send_message(msg) @staticmethod def send_json(client: dict, obj): client['handler'].send_json(obj) def send_message_all(self, msg: Union[str, bytes]): for client in self.clients.values(): client['handler'].send_message(msg) def send_json_all(self, obj): for client in self.clients.values(): client['handler'].send_json(obj) def serve(self): print(f'Server Started on {self.server_address}') try: self.serve_forever(.1) except KeyboardInterrupt: self.shutdown()