import os from pathlib import Path from fastapi import Request from fastapi import HTTPException PUBLIC_API_BASE_URL = os.getenv("PUBLIC_API_BASE_URL", "").strip().rstrip("/") VALID_CLIENT_TOKEN = os.getenv("CLIENT_API_TOKEN") RELEASE_MAIN_EXECUTABLE = os.getenv("RELEASE_MAIN_EXECUTABLE", "MainApp.exe").strip().replace(chr(92), "/").strip("/") TARGET_PLATFORM = os.getenv("TARGET_PLATFORM", "windows") TARGET_ARCH = os.getenv("TARGET_ARCH", "x64") PUBLISH_MAX_REQUEST_BYTES = int(os.getenv("PUBLISH_MAX_REQUEST_MB", "4096")) * 1024 * 1024 PUBLISH_MAX_FILES = int(os.getenv("PUBLISH_MAX_FILES", "20000")) PUBLISH_MAX_FIELDS = int(os.getenv("PUBLISH_MAX_FIELDS", str(PUBLISH_MAX_FILES + 100))) def public_api_base_url(request: Request) -> str: if PUBLIC_API_BASE_URL: return PUBLIC_API_BASE_URL forwarded_proto = request.headers.get("x-forwarded-proto", "").split(",")[0].strip() forwarded_host = request.headers.get("x-forwarded-host", "").split(",")[0].strip() scheme = forwarded_proto or request.url.scheme host = forwarded_host or request.headers.get("host", "") if host: return f"{scheme}://{host}".rstrip("/") return str(request.base_url).rstrip("/") def default_client_main_executable() -> str: path = RELEASE_MAIN_EXECUTABLE.strip().replace(chr(92), "/").strip("/") if path.casefold().startswith("bin/"): path = path[4:] return path or "SimCAE.exe" def default_client_config_values(request: Request) -> dict: return { "api_base_url": public_api_base_url(request), "client_token": VALID_CLIENT_TOKEN or "", "launch_token": os.getenv("CLIENT_LAUNCH_TOKEN", "SimCAE_Launch_Token_2026_ChangeMe_32Bytes"), "request_timeout_ms": os.getenv("CLIENT_REQUEST_TIMEOUT_MS", "5000"), "temp_folder": os.getenv("CLIENT_TEMP_FOLDER", "update_temp"), "install_root": os.getenv("CLIENT_INSTALL_ROOT", ".."), "main_executable": os.getenv("CLIENT_MAIN_EXECUTABLE", default_client_main_executable()), "launcher_executable": os.getenv("CLIENT_LAUNCHER_EXECUTABLE", "Launcher.exe"), "updater_executable": os.getenv("CLIENT_UPDATER_EXECUTABLE", "Updater.exe"), "bootstrap_executable": os.getenv("CLIENT_BOOTSTRAP_EXECUTABLE", "Bootstrap.exe"), "health_check_timeout_ms": os.getenv("CLIENT_HEALTH_CHECK_TIMEOUT_MS", "15000"), "platform": TARGET_PLATFORM, "arch": TARGET_ARCH, } def normalize_executable_name(value: str) -> str: normalized = str(value or "").strip().replace(chr(92), "/").strip("/") if not normalized: return "" if normalized.startswith("/") or ":" in normalized or any(part in ("", ".", "..") for part in Path(normalized).parts): raise HTTPException(status_code=400, detail="主程序文件名必须是安全的相对路径") if not normalized.casefold().endswith(".exe"): normalized += ".exe" return normalized