From 58197fd35bc119dee0eb9b6e065b628874e24f9e Mon Sep 17 00:00:00 2001 From: cbb Date: Thu, 15 Oct 2026 01:23:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20v1.5.5=20=E2=80=94=20=E3=80=8C=E6=89=93?= =?UTF-8?q?=E5=BC=80=20Gitea=E3=80=8D=E8=BF=9C=E7=A8=8B=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E6=8E=A8=E6=96=AD=E4=B8=89=E9=87=8D=E4=BF=9D=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:gitea_ui() 有个判断 bug——当请求 Host 是 127.0.0.1:8899 (CGI 内部连接面板的本地地址)时,面板把它当作"远程主机名" 去替换 gitea_host 中的 127.0.0.1,结果还是 127.0.0.1:3000。 修复: 1. 修判断 bug:只有推断出的真实主机名不是 loopback 时才替换 gitea_host 中的 127.0.0.1(本地直连保持原样,不再被错误"替换") 2. gitea_ui 新增 Referer 兜底:即使 CGI 没转发 Host/X-Forwarded-Host (fnOS CGI 环境里 $HTTP_HOST 可能为空),也能从浏览器自带的 Referer 头里推断出真实主机名 3. CGI 代理兜底转发:从 X-Forwarded-Host / HTTP_HOST / Referer 多源提取真实主机,作为 Host 和 X-Forwarded-Host 转发 4. 调用方传入 X-Forwarded-Host / Host / Referer 三个头 实测 6 种场景(远程 CGI / 远程直连 / 只有 Referer / 本地直连 / 全部为空 / 远程 IP)全部生成正确地址 manifest 1.5.4 → 1.5.5 --- GiteaHubSync/app/backend/panel.py | 34 +++++++++++++++++++++++-------- GiteaHubSync/app/ui/index.cgi | 16 ++++++++++++--- GiteaHubSync/manifest | 2 +- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/GiteaHubSync/app/backend/panel.py b/GiteaHubSync/app/backend/panel.py index f98ae1f..b4502ea 100644 --- a/GiteaHubSync/app/backend/panel.py +++ b/GiteaHubSync/app/backend/panel.py @@ -97,17 +97,24 @@ def gitea_host(): return cfg.get("gitea_host") or DEFAULT_GITEA_HOST -def gitea_ui(host_header=""): - """「打开 Gitea」按钮地址:优先环境变量;否则用设置里的地址, - 但把 127.0.0.1/localhost 替换成访问面板的主机名(浏览器打不开本机回环)""" +def gitea_ui(host_header="", referer="", xfwd_host=""): + """「打开 Gitea」按钮地址:把 127.0.0.1/localhost 替换成真实远程主机名。 + 推断顺序:X-Forwarded-Host(CGI 转发) > Host > Referer(浏览器自带)。 + 注意:仅当推断出的真实主机名不是 loopback 时才替换(本地直连保持原样)。""" if GITEA_URL_ENV: return GITEA_URL_ENV base = gitea_host() + real = (xfwd_host or host_header or "").split(":")[0].strip() + if not real and referer: + m = re.match(r'^https?://([^/:/?#]+)', referer) + if m: + real = m.group(1) + if not real or real in ("127.0.0.1", "localhost", "0.0.0.0", "::1"): + return base # 本地/无法推断,保留 gitea_host 原值 try: p = urlsplit(base) - host = (host_header or "").split(":")[0].strip() - if host and p.hostname in ("127.0.0.1", "localhost", "0.0.0.0", "::1", ""): - return f"{p.scheme}://{host}" + (f":{p.port}" if p.port else "") + if p.hostname in ("127.0.0.1", "localhost", "0.0.0.0", "::1", ""): + return f"{p.scheme}://{real}" + (f":{p.port}" if p.port else "") except Exception: pass return base @@ -1661,7 +1668,10 @@ class Handler(BaseHTTPRequestHandler): path = self.path.split("?")[0].rstrip("/") or "/" if path == "/": acc = active_account() - body = (PAGE_HOME.replace("__GITEA__", gitea_ui(self.headers.get("Host"))) + body = (PAGE_HOME.replace("__GITEA__", gitea_ui( + self.headers.get("Host"), + self.headers.get("Referer"), + self.headers.get("X-Forwarded-Host"))) .replace("__GH__", f"https://github.com/{acc['gh_user']}?tab=repositories" if acc else "") .replace("__LOGPATH__", str(LOG_FILE))) self._html(page("控制面板", body, "home")) @@ -1685,7 +1695,10 @@ class Handler(BaseHTTPRequestHandler): "account": account_public(acc), "last_real": last_real_sync()}) elif path == "/api/status": - self._json(global_status()) + self._json(global_status(gitea_ui( + self.headers.get("Host"), + self.headers.get("Referer"), + self.headers.get("X-Forwarded-Host")))) elif path == "/api/settings": self._json({"gitea_user": cfg["gitea_user"], "gitea_pass": mask_token(cfg.get("gitea_pass")), @@ -1700,7 +1713,10 @@ class Handler(BaseHTTPRequestHandler): "max_retries": max_retries(), "retry_interval": RETRY_INTERVAL, "gitea_host": gitea_host(), - "gitea_url": gitea_ui(self.headers.get("Host")), + "gitea_url": gitea_ui( + self.headers.get("Host"), + self.headers.get("Referer"), + self.headers.get("X-Forwarded-Host")), "active": active_id, "accounts": [account_public(a) for a in accounts]}) elif path == "/api/log": diff --git a/GiteaHubSync/app/ui/index.cgi b/GiteaHubSync/app/ui/index.cgi index 9220a67..2cb73f0 100644 --- a/GiteaHubSync/app/ui/index.cgi +++ b/GiteaHubSync/app/ui/index.cgi @@ -19,9 +19,19 @@ CTYPE="${CONTENT_TYPE:-application/json}" HDR="$(mktemp)"; BODY="$(mktemp)"; INBODY="" CURL_ARGS=(-s -m 120 -X "$METHOD" -H "Content-Type: $CTYPE") -# 把真实访问 Host / 协议转发给面板:面板据此把「打开 Gitea」等远程可访问链接 -# 的主机名从 127.0.0.1/localhost 替换成用户实际访问的主机名 -[ -n "$HTTP_HOST" ] && CURL_ARGS+=(-H "Host: $HTTP_HOST") +# 把真实访问 Host / 协议转发给面板,面板据此把「打开 Gitea」等远程可访问 +# 链接的主机名从 127.0.0.1/localhost 替换成用户实际访问的主机名。 +# 真实主机从 X-Forwarded-Host / HTTP_HOST / Referer 中兜底提取(fnOS CGI +# 环境里 HTTP_HOST 可能为空)。 +REAL_HOST="" +[ -n "$HTTP_X_FORWARDED_HOST" ] && REAL_HOST="$HTTP_X_FORWARDED_HOST" +[ -z "$REAL_HOST" ] && [ -n "$HTTP_HOST" ] && REAL_HOST="$HTTP_HOST" +if [ -z "$REAL_HOST" ] && [ -n "$HTTP_REFERER" ]; then + REAL_HOST=$(printf '%s' "$HTTP_REFERER" | sed -nE 's|^https?://([^/:/?#]+).*|\1|p' | head -1) +fi +if [ -n "$REAL_HOST" ]; then + CURL_ARGS+=(-H "Host: $REAL_HOST" -H "X-Forwarded-Host: $REAL_HOST") +fi if [ "${HTTPS:-off}" = "on" ]; then CURL_ARGS+=(-H "X-Forwarded-Proto: https") else diff --git a/GiteaHubSync/manifest b/GiteaHubSync/manifest index 9d86a5e..6d5d2f9 100644 --- a/GiteaHubSync/manifest +++ b/GiteaHubSync/manifest @@ -1,5 +1,5 @@ appname = GiteaHubSync -version = 1.5.4 +version = 1.5.5 display_name = Gitea→GitHub 同步助手 desc = 每次推送到飞牛 Gitea 的代码,10 秒内自动镜像到 GitHub 的 7 个私有仓库。内置控制面板:自动同步开关、立即同步、仓库状态、同步日志,一键打开 Gitea。数据全部保存在 NAS 本地。 platform = all