# -*- coding: utf-8 -*- r"""中转网站加"下载"区 —— 把成品直接挂在管理页上。 用户要求:"把下载链接 release 放到那个中转网站上" 做法: 1. 中转加一个下载目录(relay/downloads/) 2. GET /download/<文件名> 直接给文件(带正确的 Content-Type 和文件名) 3. GET /v1/downloads 列出有哪些文件(名字 + 大小) 4. 管理页加一张"下载"卡片,列出文件并给可点的链接 5. 部署脚本把 dist/ 里的成品传上去 安全: 只允许下载 downloads/ 目录里的**白名单后缀**(.apk/.zip/.txt), 并且文件名做严格校验(不许有 .. / \ 之类),防止目录穿越。 """ from __future__ import annotations import ast import sys from pathlib import Path RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay" r"\relay.py") # ---------------- 1. 下载目录常量 ---------------- ANCHOR_DIR = '''ADMIN_SESSIONS: dict = {}''' NEW_DIR = '''# 成品下载目录(管理页上给人下载 APK / 整包用)。 # 只放白名单后缀的文件,文件名做严格校验防目录穿越。 DOWNLOADS_DIR = Path(__file__).resolve().parent / "downloads" DOWNLOAD_SUFFIXES = (".apk", ".zip", ".txt", ".exe", ".idsig") def _safe_download_name(name: str) -> str: """校验下载文件名。不合法返回空串。 防目录穿越:只允许字母数字、点、横线、下划线、中文, 且必须是白名单后缀。 """ name = (name or "").strip() if not name or len(name) > 120: return "" if "/" in name or "\\\\" in name or ".." in name: return "" if not name.lower().endswith(DOWNLOAD_SUFFIXES): return "" for ch in name: if ch.isalnum() or ch in "._-()() ": continue return "" return name def _list_downloads() -> list: """列出可下载的文件(按修改时间倒序)。""" items = [] try: if not DOWNLOADS_DIR.is_dir(): return items for entry in DOWNLOADS_DIR.iterdir(): if not entry.is_file(): continue if not _safe_download_name(entry.name): continue stat = entry.stat() items.append({ "name": entry.name, "size": stat.st_size, "mtime": stat.st_mtime, }) except OSError: return items items.sort(key=lambda item: item["mtime"], reverse=True) return items ''' + ANCHOR_DIR # ---------------- 2. 下载路由 ---------------- ANCHOR_ROUTE = ''' if path in ("/v1/health", "/health"):''' NEW_ROUTE = ''' # ---- 成品下载(挂在管理页上给人下 APK / 整包)---- if path == "/v1/downloads": self._json(200, {"ok": True, "files": _list_downloads()}) return if path.startswith("/download/"): wanted = urllib.parse.unquote(path[len("/download/"):]) safe = _safe_download_name(wanted) if not safe: self._json(400, {"ok": False, "error": "文件名不合法"}) return target = DOWNLOADS_DIR / safe if not target.is_file(): self._json(404, {"ok": False, "error": "没有这个文件"}) return try: blob = target.read_bytes() except OSError as exc: self._json(500, {"ok": False, "error": f"读取失败: {exc}"}) return if safe.lower().endswith(".apk"): content_type = "application/vnd.android.package-archive" elif safe.lower().endswith(".zip"): content_type = "application/zip" elif safe.lower().endswith(".txt"): content_type = "text/plain; charset=utf-8" else: content_type = "application/octet-stream" self.send_response(200) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(blob))) # 文件名可能含中文,用 RFC 5987 编码 quoted = urllib.parse.quote(safe) self.send_header( "Content-Disposition", f"attachment; filename*=UTF-8''{quoted}") self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(blob) log(f"下载:{safe}({len(blob)} 字节)") return ''' + ANCHOR_ROUTE # ---------------- 3. 管理页卡片 ---------------- ANCHOR_CARD = '''