def handleQuery(self, message, protocol, address): _super = super() def _handleQuery(mpa): message, protocol, address = mpa return _super.handleQuery(message, protocol, address) name = message.queries[0].name.name.decode("UTF-8") return apply_middlewares(self.middlewares.get_descriptors(name), _handleQuery)((message, protocol, address))
def pick_certificate(self, connection): def _pick_certificate(connection): return self.tls_ctx # Apply middlewares server_name_indication = (connection.get_servername() or b'').decode("UTF-8") ctx = apply_middlewares(self.middlewares.get_descriptors(server_name_indication), _pick_certificate)(connection) if ctx is not None: connection.set_context(self.tls_ctx)
def pick_certificate(self, connection): def _pick_certificate(server_name_indication, connection): return self.tls_ctx # Apply middlewares server_name_indication = (connection.get_servername() or b'').decode("UTF-8") middlewares = self.routes.get_descriptors( server_name_indication, rfilter=lambda x: x.get("protocol") == "ssl_middleware") ctx = utils.apply_middlewares(middlewares, _pick_certificate)( server_name_indication, connection) if ctx is not None: connection.set_context(ctx) else: connection.set_context(self.tls_ctx)
def handleQuery(self, message, protocol, address): def _handleQuery(route_descriptor, qname, lookup_cls, qtype, message, protocol, address): return self._lookup(route_descriptor, qname, lookup_cls, qtype, 1000) query = message.queries[0] # Standardise the query name qname = query.name.name if not isinstance(qname, str): qname = qname.decode('idna') qname = qname.lower() # At this point we only know of dns.IN lookup classes lookup_cls = dns.IN # Access route_descriptors directly to perform complex filtering route_descriptor = {} for _route_descriptor, _ in self.routes.get_descriptors(qname, rfilter=lambda x: x.get("protocol") == "dns"): _action_des = _route_descriptor.get("action") if _action_des is None: continue if lookup_cls == _action_des.get("class", dns.IN): # Convert the route_descriptor type to an integer rd_type = _action_des.get("type", query.type) if not isinstance(rd_type, int): rd_type = dns.REV_TYPES.get(rd_type, 0) # If the lookup type matches the route_descriptor type if query.type == rd_type: route_descriptor = _route_descriptor break middlewares = self.routes.get_descriptors(qname, rfilter=lambda x: x.get("protocol") == "dns_middleware") response = utils.apply_middlewares(middlewares, _handleQuery)(route_descriptor, qname, lookup_cls, query.type, message, protocol, address) self.gotResolverResponse(response, protocol, message, address)
def getChild(self, name, request): # Rebuild the request parts for route matching scheme = "https" if request.isSecure() else "http" host = request.getRequestHostname().decode("UTF-8") or "-" port = request.getHost().port path = request.path.decode("UTF-8") args = (request.uri.decode("UTF-8").split("?", 1) + [""])[1] # Match on any of scheme://host(:port)/path?args, /path?args, /path in that order request_path = path request_parts = [] request_parts.append("{scheme}://{host}{port}{path}{args}".format( scheme=scheme, host=host, port=(":%d" % port) if port != {"http": 80, "https": 443}[scheme] else "", path=request_path, args= "?" + args if args else "" )) if args: request_parts.append(request_path + "?" + args) request_parts.append(request_path) def _getChild(route_descriptor, route_match, request): response = SimpleResource(404, body=b'Not Found') if route_descriptor is not None: action_des = route_descriptor.get("action") if action_des is not None: response_handler = action_des.get("handler", "serve") if response_handler == "serve": # Replace regex groups in the route path resource_path = action_des["path"] for i, group in enumerate(re.search(route_descriptor["route"], route_match).groups()): if group is not None: resource_path = resource_path.replace("${}".format(i + 1), group) # Security: Ensure the absolute resource_path is within the `self.filesroot` directory! # Prepend the resource_path with the self.filesroot and canonicalize resource_path = os.path.abspath(os.path.join(self.filesroot, resource_path.lstrip("/"))) if resource_path.startswith(self.filesroot): # If the resource_path does not exist, or is a directory, search for an index.py in each url path directory if not os.path.isfile(resource_path): search_path = "" search_dirs = [""] + resource_path.replace(self.filesroot, "").strip("/").split("/") for search_dir in search_dirs: search_path = os.path.join(search_path, search_dir) if os.path.isfile(os.path.join(self.filesroot, search_path, "index.py")): resource_path = os.path.join(self.filesroot, search_path, "index.py") break if os.path.isfile(resource_path): # Execute python scripts if os.path.splitext(resource_path)[1].lower() == ".py": # Fixup the request path request.postpath.insert(0, request.prepath.pop(0)) response = get_script_response(resource_path, request) else: data = b'' with open(resource_path, "rb") as f: data = f.read() response = SimpleResource(200, body=data) elif response_handler == "script": # Fixup the request path request.postpath.insert(0, request.prepath.pop(0)) # Relocate to `base` if "base" in action_des: base = action_des["base"] request.prepath = base.strip("/").encode().split(b'/') if request_path.startswith(base): request.postpath = request_path.split(base, 1)[1].strip("/").encode().split(b'/') # Apply rewrite if "rewrite" in action_des: match = re.search(action_des["rewrite"], request_path) if match: try: rewrite = match.group(1) except IndexError: rewrite = match.group(0) finally: request.postpath = rewrite.encode().split(b'/') response = get_script_response(action_des["path"], request) elif response_handler == "raw": code = action_des.get("code", 200) body = action_des.get("body", "").encode("UTF-8") response = SimpleResource(code, body=body) elif response_handler == "forward": url = action_des["destination"] if action_des.get("recreate_url", True): fscheme, fnetloc, _, _, _, _ = urllib.parse.urlparse(url) url = urllib.parse.urlunparse((fscheme, fnetloc, request.uri.decode("UTF-8"), "", "", "")) response = ForwardResource(url, headers=action_des.get("request_headers", {})) headers = action_des.get("headers", {}) # Take a copy of the `replace` array as we will modify it afterwards replace = copy.deepcopy(action_des.get("replace", [])) if len(headers) or len(replace): if len(replace): # Prepare the replacements for replace_descriptor in replace: replacement = replace_descriptor["replacement"] replacement = replacement.replace("{hostname}", host) replacement = replacement.replace("{port}", str(port)) replacement = replacement.replace("{path}", path) replacement = replacement.replace("{scheme}", scheme) replace_descriptor["pattern"] = replace_descriptor["pattern"].encode() replace_descriptor["replacement"] = replacement.encode() # Patch request.write to replace headers after rendering and perform replacements request.write = lambda data: _write(request, headers, replace, data) return response route_descriptor, route_match = self.routes.get_descriptor(*request_parts, rfilter=lambda x: x.get("protocol") == "http") middlewares = self.routes.get_descriptors(*request_parts, rfilter=lambda x: x.get("protocol") == "http_middleware") return utils.apply_middlewares(middlewares, _getChild)(route_descriptor, route_match, request)
def getChild(self, name, request): # Rebuild the request parts for route matching scheme = "https" if request.isSecure() else "http" host = request.getRequestHostname().decode("UTF-8") or "-" port = request.getHost().port path = request.path.decode("UTF-8") args = (request.uri.decode("UTF-8").split("?", 1) + [""])[1] request_path = path request_parts = [request_path] if args: request_parts.append(request_path + "?" + args) request_parts.append("{}://{}{}{}".format( scheme, host, (":%d" % port) if port != {"http": 80, "https": 443}[scheme] else "", request_parts[-1] )) def _getChild(request): headers = {} resource_path = request_path route_descriptor, route_match = self.routes.get_descriptor(*request_parts) if route_descriptor is not None: headers = route_descriptor.get("headers", {}) # Forward case if "forward" in route_descriptor: # Recreate the URL if route_descriptor.get("recreate_url", True): fscheme, fnetloc, _, _, _, _ = urllib.parse.urlparse(route_descriptor["forward"]) url = urllib.parse.urlunparse((fscheme, fnetloc, request.uri.decode("UTF-8"), "", "", "")) else: url = route_descriptor["forward"] replace = route_descriptor.get("replace", []) return ForwardResource(url, headers=headers, replace=replace) # Code case elif "code" in route_descriptor: code = route_descriptor.get("code", 200) body = route_descriptor.get("body", "") return SimpleResource(request_path, code, headers=headers, body=body) # Path case elif "path" in route_descriptor: resource_path = route_descriptor["path"] # Replace regex groups in the route path for i, group in enumerate(re.search(route_descriptor["route"], route_match).groups()): if group is not None: resource_path = resource_path.replace("${}".format(i + 1), group) # Security: Ensure the absolute resource_path is within the wwwroot! # Prepend the resource_path with the wwwroot and canonicalize resource_path = os.path.abspath(os.path.join(self.filesroot, resource_path.lstrip("/"))) if resource_path.startswith(self.filesroot): if os.path.exists(resource_path): # Security: Don't show the soruce of python files! if os.path.splitext(resource_path)[1].lower() == ".py": try: res = exec_cached_script(resource_path) return resource.IResource(res["get_resource"](request)) except Exception: logger.exception("Unahandled exception in exec'd file '{}'".format(resource_path)) else: with open(resource_path, "r") as f: data = f.read() return SimpleResource(request_path, 200, headers=headers, body=data) # Default handling, 404 here return super().getChild(name, request) return apply_middlewares(self.middlewares.get_descriptors(*request_parts), _getChild)(request)