Index: Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/handlers.py |
diff --git a/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/handlers.py b/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/handlers.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f22ddd59d710386143f818ba80bcb3bb8c55d98a |
--- /dev/null |
+++ b/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/handlers.py |
@@ -0,0 +1,332 @@ |
+import cgi |
+import json |
+import os |
+import traceback |
+import urllib |
+import urlparse |
+ |
+from constants import content_types |
+from pipes import Pipeline, template |
+from ranges import RangeParser |
+from request import Authentication |
+from response import MultipartContent |
+from utils import HTTPException |
+ |
+__all__ = ["file_handler", "python_script_handler", |
+ "FunctionHandler", "handler", "json_handler", |
+ "as_is_handler", "ErrorHandler", "BasicAuthHandler"] |
+ |
+ |
+def guess_content_type(path): |
+ ext = os.path.splitext(path)[1].lstrip(".") |
+ if ext in content_types: |
+ return content_types[ext] |
+ |
+ return "application/octet-stream" |
+ |
+ |
+ |
+def filesystem_path(base_path, request, url_base="/"): |
+ if base_path is None: |
+ base_path = request.doc_root |
+ |
+ path = request.url_parts.path |
+ |
+ if path.startswith(url_base): |
+ path = path[len(url_base):] |
+ |
+ if ".." in path: |
+ raise HTTPException(404) |
+ |
+ new_path = os.path.join(base_path, path) |
+ |
+ # Otherwise setting path to / allows access outside the root directory |
+ if not new_path.startswith(base_path): |
+ raise HTTPException(404) |
+ |
+ return new_path |
+ |
+class DirectoryHandler(object): |
+ def __init__(self, base_path=None, url_base="/"): |
+ self.base_path = base_path |
+ self.url_base = url_base |
+ |
+ def __call__(self, request, response): |
+ if not request.url_parts.path.endswith("/"): |
+ raise HTTPException(404) |
+ |
+ path = filesystem_path(self.base_path, request, self.url_base) |
+ |
+ assert os.path.isdir(path) |
+ |
+ response.headers = [("Content-Type", "text/html")] |
+ response.content = """<!doctype html> |
+<meta name="viewport" content="width=device-width"> |
+<title>Directory listing for %(path)s</title> |
+<h1>Directory listing for %(path)s</h1> |
+<ul> |
+%(items)s |
+</li> |
+""" % {"path": cgi.escape(request.url_parts.path), |
+ "items": "\n".join(self.list_items(request, path))} |
+ |
+ def list_items(self, request, path): |
+ # TODO: this won't actually list all routes, only the |
+ # ones that correspond to a real filesystem path. It's |
+ # not possible to list every route that will match |
+ # something, but it should be possible to at least list the |
+ # statically defined ones |
+ base_path = request.url_parts.path |
+ |
+ if not base_path.endswith("/"): |
+ base_path += "/" |
+ if base_path != "/": |
+ link = urlparse.urljoin(base_path, "..") |
+ yield ("""<li class="dir"><a href="%(link)s">%(name)s</a>""" % |
+ {"link": link, "name": ".."}) |
+ for item in sorted(os.listdir(path)): |
+ link = cgi.escape(urllib.quote(item)) |
+ if os.path.isdir(os.path.join(path, item)): |
+ link += "/" |
+ class_ = "dir" |
+ else: |
+ class_ = "file" |
+ yield ("""<li class="%(class)s"><a href="%(link)s">%(name)s</a>""" % |
+ {"link": link, "name": cgi.escape(item), "class": class_}) |
+ |
+ |
+directory_handler = DirectoryHandler() |
+ |
+ |
+class FileHandler(object): |
+ def __init__(self, base_path=None, url_base="/"): |
+ self.base_path = base_path |
+ self.url_base = url_base |
+ self.directory_handler = DirectoryHandler(self.base_path) |
+ |
+ def __call__(self, request, response): |
+ path = filesystem_path(self.base_path, request, self.url_base) |
+ |
+ if os.path.isdir(path): |
+ return self.directory_handler(request, response) |
+ try: |
+ #This is probably racy with some other process trying to change the file |
+ file_size = os.stat(path).st_size |
+ response.headers.update(self.get_headers(request, path)) |
+ if "Range" in request.headers: |
+ try: |
+ byte_ranges = RangeParser()(request.headers['Range'], file_size) |
+ except HTTPException as e: |
+ if e.code == 416: |
+ response.headers.set("Content-Range", "bytes */%i" % file_size) |
+ raise |
+ else: |
+ byte_ranges = None |
+ data = self.get_data(response, path, byte_ranges) |
+ response.content = data |
+ query = urlparse.parse_qs(request.url_parts.query) |
+ |
+ pipeline = None |
+ if "pipe" in query: |
+ pipeline = Pipeline(query["pipe"][-1]) |
+ elif os.path.splitext(path)[0].endswith(".sub"): |
+ pipeline = Pipeline("sub") |
+ if pipeline is not None: |
+ response = pipeline(request, response) |
+ |
+ return response |
+ |
+ except (OSError, IOError): |
+ raise HTTPException(404) |
+ |
+ def get_headers(self, request, path): |
+ rv = self.default_headers(path) |
+ rv.extend(self.load_headers(request, os.path.join(os.path.split(path)[0], "__dir__"))) |
+ rv.extend(self.load_headers(request, path)) |
+ return rv |
+ |
+ def load_headers(self, request, path): |
+ headers_path = path + ".sub.headers" |
+ if os.path.exists(headers_path): |
+ use_sub = True |
+ else: |
+ headers_path = path + ".headers" |
+ use_sub = False |
+ |
+ try: |
+ with open(headers_path) as headers_file: |
+ data = headers_file.read() |
+ except IOError: |
+ return [] |
+ else: |
+ if use_sub: |
+ data = template(request, data) |
+ return [tuple(item.strip() for item in line.split(":", 1)) |
+ for line in data.splitlines() if line] |
+ |
+ def get_data(self, response, path, byte_ranges): |
+ with open(path, 'rb') as f: |
+ if byte_ranges is None: |
+ return f.read() |
+ else: |
+ response.status = 206 |
+ if len(byte_ranges) > 1: |
+ parts_content_type, content = self.set_response_multipart(response, |
+ byte_ranges, |
+ f) |
+ for byte_range in byte_ranges: |
+ content.append_part(self.get_range_data(f, byte_range), |
+ parts_content_type, |
+ [("Content-Range", byte_range.header_value())]) |
+ return content |
+ else: |
+ response.headers.set("Content-Range", byte_ranges[0].header_value()) |
+ return self.get_range_data(f, byte_ranges[0]) |
+ |
+ def set_response_multipart(self, response, ranges, f): |
+ parts_content_type = response.headers.get("Content-Type") |
+ if parts_content_type: |
+ parts_content_type = parts_content_type[-1] |
+ else: |
+ parts_content_type = None |
+ content = MultipartContent() |
+ response.headers.set("Content-Type", "multipart/byteranges; boundary=%s" % content.boundary) |
+ return parts_content_type, content |
+ |
+ def get_range_data(self, f, byte_range): |
+ f.seek(byte_range.lower) |
+ return f.read(byte_range.upper - byte_range.lower) |
+ |
+ def default_headers(self, path): |
+ return [("Content-Type", guess_content_type(path))] |
+ |
+ |
+file_handler = FileHandler() |
+ |
+ |
+class PythonScriptHandler(object): |
+ def __init__(self, base_path=None, url_base="/"): |
+ self.base_path = base_path |
+ self.url_base = url_base |
+ |
+ def __call__(self, request, response): |
+ path = filesystem_path(self.base_path, request, self.url_base) |
+ |
+ try: |
+ environ = {"__file__": path} |
+ execfile(path, environ, environ) |
+ if "main" in environ: |
+ handler = FunctionHandler(environ["main"]) |
+ handler(request, response) |
+ else: |
+ raise HTTPException(500, "No main function in script %s" % path) |
+ except IOError: |
+ raise HTTPException(404) |
+ |
+python_script_handler = PythonScriptHandler() |
+ |
+class FunctionHandler(object): |
+ def __init__(self, func): |
+ self.func = func |
+ |
+ def __call__(self, request, response): |
+ try: |
+ rv = self.func(request, response) |
+ except Exception: |
+ msg = traceback.format_exc() |
+ raise HTTPException(500, message=msg) |
+ if rv is not None: |
+ if isinstance(rv, tuple): |
+ if len(rv) == 3: |
+ status, headers, content = rv |
+ response.status = status |
+ elif len(rv) == 2: |
+ headers, content = rv |
+ else: |
+ raise HTTPException(500) |
+ response.headers.update(headers) |
+ else: |
+ content = rv |
+ response.content = content |
+ |
+ |
+#The generic name here is so that this can be used as a decorator |
+def handler(func): |
+ return FunctionHandler(func) |
+ |
+ |
+class JsonHandler(object): |
+ def __init__(self, func): |
+ self.func = func |
+ |
+ def __call__(self, request, response): |
+ return FunctionHandler(self.handle_request)(request, response) |
+ |
+ def handle_request(self, request, response): |
+ rv = self.func(request, response) |
+ response.headers.set("Content-Type", "application/json") |
+ enc = json.dumps |
+ if isinstance(rv, tuple): |
+ rv = list(rv) |
+ value = tuple(rv[:-1] + [enc(rv[-1])]) |
+ length = len(value[-1]) |
+ else: |
+ value = enc(rv) |
+ length = len(value) |
+ response.headers.set("Content-Length", length) |
+ return value |
+ |
+def json_handler(func): |
+ return JsonHandler(func) |
+ |
+class AsIsHandler(object): |
+ def __init__(self, base_path=None, url_base="/"): |
+ self.base_path = base_path |
+ self.url_base = url_base |
+ |
+ def __call__(self, request, response): |
+ path = filesystem_path(self.base_path, request, self.url_base) |
+ |
+ try: |
+ with open(path) as f: |
+ response.writer.write_content(f.read()) |
+ response.close_connection = True |
+ except IOError: |
+ raise HTTPException(404) |
+ |
+as_is_handler = AsIsHandler() |
+ |
+class BasicAuthHandler(object): |
+ def __init__(self, handler, user, password): |
+ """ |
+ A Basic Auth handler |
+ |
+ :Args: |
+ - handler: a secondary handler for the request after authentication is successful (example file_handler) |
+ - user: string of the valid user name or None if any / all credentials are allowed |
+ - password: string of the password required |
+ """ |
+ self.user = user |
+ self.password = password |
+ self.handler = handler |
+ |
+ def __call__(self, request, response): |
+ if "authorization" not in request.headers: |
+ response.status = 401 |
+ response.headers.set("WWW-Authenticate", "Basic") |
+ return response |
+ else: |
+ auth = Authentication(request.headers) |
+ if self.user is not None and (self.user != auth.username or self.password != auth.password): |
+ response.set_error(403, "Invalid username or password") |
+ return response |
+ return self.handler(request, response) |
+ |
+basic_auth_handler = BasicAuthHandler(file_handler, None, None) |
+ |
+class ErrorHandler(object): |
+ def __init__(self, status): |
+ self.status = status |
+ |
+ def __call__(self, request, response): |
+ response.set_error(self.status) |