From 166dc84a0da35b7f4c6675992ad9648b606f524b Mon Sep 17 00:00:00 2001 From: Jonas_Jones Date: Mon, 9 Jun 2025 00:56:49 +0200 Subject: [PATCH] added lookup logic in overleaf data directory --- serve.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/serve.py b/serve.py index 0e13a7b..a94709a 100644 --- a/serve.py +++ b/serve.py @@ -2,30 +2,109 @@ import http.server import socketserver import os from urllib.parse import unquote +import io +import zipfile +from datetime import datetime -PORT = 8001 +PORT = 8000 BASE_DIR = os.path.abspath("public") +GRAB_SYMLINKS = True # Toggle symlink handling on/off +SYMLINK_DIR = os.path.abspath("symlinks") # Directory to resolve symlinks to +STATIC_SHARE_RESTRICTION = True # Toggle restriction on/off class CustomHandler(http.server.SimpleHTTPRequestHandler): def translate_path(self, path): path = unquote(path) - # /project/ should always serve overleaf.html - if path.startswith("/project/"): #and ("/" not in path[len("/project/"):]): - print(f"Serving Overleaf for project: {path}") - return os.path.join(BASE_DIR, "overleaf.html") - - # /project//file -> serve from projects//file if path.startswith("/project/"): - sub_path = path[len("/project/"):] - return os.path.join(BASE_DIR, "projects", sub_path) + parts = path[len("/project/"):].split("/", 1) + project_id = parts[0] + project_dir = os.path.join(BASE_DIR, "projects", project_id) + + # Check .staticshare restriction + if STATIC_SHARE_RESTRICTION: + staticshare_path = os.path.join(project_dir, ".staticshare") + if not os.path.isfile(staticshare_path): + return os.path.join(BASE_DIR, "404.html") + + # Handle the zip download route + if len(parts) == 2 and parts[1] == "zip": + # Signal this special path, translate_path not used here, handle in do_GET + return project_dir # Just return project_dir path + + # Serve overleaf.html if just /project/ or /project// + if len(parts) == 1 or parts[1] == "": + return os.path.join(BASE_DIR, "overleaf.html") + + # Otherwise serve file inside project folder + sub_path = parts[1] + return os.path.join(project_dir, sub_path) - # Default: serve from public/ if path in ["/", ""]: path = "/index.html" return os.path.join(BASE_DIR, path.lstrip("/")) + def do_GET(self): + path = unquote(self.path) + if path.startswith("/project/"): + parts = path[len("/project/"):].split("/", 1) + project_id = parts[0] + + project_dir = os.path.join(BASE_DIR, "projects", project_id) + + if GRAB_SYMLINKS and not os.path.exists(project_dir): + # if the project does not exist in the projects dir, check if it exists in the symlinks dir + # get all directories in the symlinks directory + if not os.path.exists(SYMLINK_DIR) or not os.path.isdir(SYMLINK_DIR): + self.send_response(404) + self.send_header("Content-type", "text/html") + self.end_headers() + with open(os.path.join(BASE_DIR, "404.html"), "rb") as f: + self.wfile.write(f.read()) + return + # find the symlink directory that starts with the project_id + symlink_dirs = [d for d in os.listdir(SYMLINK_DIR) if os.path.isdir(os.path.join(SYMLINK_DIR, d))] + for symlink_dir in symlink_dirs: + if symlink_dir.startswith(project_id): + project_dir = os.path.join(SYMLINK_DIR, symlink_dir) + break + + if STATIC_SHARE_RESTRICTION: + staticshare_path = os.path.join(project_dir, ".staticshare") + if not os.path.isfile(staticshare_path): + self.send_response(404) + self.send_header("Content-type", "text/html") + self.end_headers() + with open(os.path.join(BASE_DIR, "404.html"), "rb") as f: + self.wfile.write(f.read()) + return + + if len(parts) == 2 and parts[1] == "zip": + # Create ZIP archive in memory + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zipf: + for root, _, files in os.walk(project_dir): + for file in files: + if file.endswith((".tex", ".bib", ".pdf")): + filepath = os.path.join(root, file) + # archive name relative to project_dir + arcname = os.path.relpath(filepath, project_dir) + zipf.write(filepath, arcname) + + buffer.seek(0) + self.send_response(200) + self.send_header("Content-Type", "application/zip") + zip_filename = f"{project_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" + self.send_header("Content-Disposition", f"attachment; filename={zip_filename}") + self.send_header("Content-Length", str(len(buffer.getvalue()))) + self.end_headers() + self.wfile.write(buffer.read()) + return + + # For all other requests, fall back to default handling + super().do_GET() + def end_headers(self): self.send_header("Access-Control-Allow-Origin", "*") super().end_headers()