65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Serve the browser game without exposing tools, secrets, or raw assets."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import posixpath
|
|
from functools import partial
|
|
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
from pathlib import Path
|
|
from urllib.parse import unquote, urlsplit
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
PUBLIC_FILES = {"/", "/index.html"}
|
|
PUBLIC_PREFIXES = ("/assets/", "/css/", "/js/", "/mp3-music/", "/test/")
|
|
PRIVATE_PREFIXES = ("/assets/raw", "/tools")
|
|
|
|
|
|
class GameRequestHandler(SimpleHTTPRequestHandler):
|
|
def is_public(self) -> bool:
|
|
raw_path = unquote(urlsplit(self.path).path)
|
|
if "\\" in raw_path:
|
|
return False
|
|
path = posixpath.normpath(raw_path)
|
|
segments = [segment for segment in path.split("/") if segment]
|
|
if any(segment.startswith(".") for segment in segments):
|
|
return False
|
|
if path in PUBLIC_FILES:
|
|
return True
|
|
if path.startswith(PRIVATE_PREFIXES):
|
|
return False
|
|
return path.startswith(PUBLIC_PREFIXES)
|
|
|
|
def list_directory(self, path: str):
|
|
self.send_error(404)
|
|
return None
|
|
|
|
def do_GET(self) -> None:
|
|
if not self.is_public():
|
|
self.send_error(404)
|
|
return
|
|
super().do_GET()
|
|
|
|
def do_HEAD(self) -> None:
|
|
if not self.is_public():
|
|
self.send_error(404)
|
|
return
|
|
super().do_HEAD()
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--bind", default="127.0.0.1")
|
|
parser.add_argument("--port", type=int, default=8123)
|
|
args = parser.parse_args()
|
|
handler = partial(GameRequestHandler, directory=str(ROOT))
|
|
with ThreadingHTTPServer((args.bind, args.port), handler) as server:
|
|
print(f"MeatSlicer running at http://{args.bind}:{args.port}")
|
|
server.serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|