feat(sub): add legacy Clash subscription endpoint (#6338)

* feat(sub): add legacy Clash subscription endpoint

* fix(deps): update js-yaml to patched release

Raise the Swagger UI js-yaml override to 4.3.2 and refresh the lockfile to resolve GHSA-2883-xcg3-v3hh without changing Swagger UI.

* fix(sub): preserve client detection and normalize legacy cipher

Keep the original Clash/Mihomo auto-detection default so existing subscription URLs continue returning YAML. Normalize the panel-supported chacha20-poly1305 alias when generating legacy Clash profiles, and cover both regressions through HTTP endpoint tests.

* refactor(sub): drop an unreachable guard and make the alias test assert

Review of the legacy Clash subscription endpoint left three LOW findings, all
introduced by the change:

- The comment above the routing merge ran to three lines, over CLAUDE.md's
  two-line cap.
- validateClashRouteGraph on the legacy path could never fail: the legacy
  branch skips the routing merge, so it validated the literal config built a
  few lines above against itself. Dead code that reads as a guard.
- TestClashAliasesSkipConfiguredPathConflicts asserted nothing — it could only
  fail on an escaping gin panic, so a regression that registered the alias
  handler on the configured path went unnoticed. It now drives each collision
  through the router and asserts which format answers each path.

---------

Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
duqigit
2026-09-11 03:58:55 +08:00
committed by GitHub
parent 3f1e52f09e
commit 64b6e43e2b
6 changed files with 520 additions and 16 deletions
+26 -5
View File
@@ -42,17 +42,38 @@ URLs and preview both bodies here:
The **format is chosen by path**, each with its own enable toggle:
| Format | Path | Enabled by | Output |
| --------------------- | --------- | ---------------- | --------------------------------------------------- |
| **Raw links** | `subPath` | always (if on) | A list of `vless://`, `vmess://`, … links (base64-encoded when `subEncrypt` is on). |
| **JSON** | `subJsonPath` | `subJsonEnable` | Full Xray client config(s). |
| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | YAML profile. |
| Format | Path | Enabled by | Output |
| ------------------------------ | ---------------- | ---------------- | --------------------------------------------------- |
| **Raw links** | `subPath` | always (if on) | A list of `vless://`, `vmess://`, … links (base64-encoded when `subEncrypt` is on). |
| **JSON** | `subJsonPath` | `subJsonEnable` | Full Xray client config(s). |
| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | Full Mihomo-compatible YAML profile. |
| **Mihomo (explicit)** | `/mihomo/` | `subClashEnable` | Alias for the full `subClashPath` profile. |
| **Clash for Windows (legacy)** | `/clash-legacy/` | `subClashEnable` | YAML limited to proxy types, transports, and ciphers supported by the legacy Clash core. |
Only enabled inbounds using **VLESS, VMess, Trojan, Shadowsocks, or Hysteria2**
appear in a subscription, ordered by their sub-sort index. Requesting `subPath`
with an `Accept: text/html` header (or `?html=1`) returns a human-readable info
page instead of the raw body.
Use `/mihomo/<sub-id>` for Clash Verge Rev, Mihomo, and other maintained
Mihomo-based clients. Use `/clash-legacy/<sub-id>` only for the discontinued
Clash for Windows client. The legacy endpoint keeps compatible VMess, Trojan,
and Shadowsocks nodes and excludes VLESS, Hysteria2, Reality, XHTTP,
HTTPUpgrade, and Shadowsocks 2022. If no compatible node exists, it returns an
explicit `422` response instead of a YAML profile the client cannot import.
To avoid Mihomo-only syntax entering the legacy profile, this endpoint always
uses its minimal `PROXY` group and `MATCH,PROXY` rule and ignores custom Clash
routing settings.
If an administrator has already assigned `/mihomo/` or `/clash-legacy/` to a
different configurable subscription path, that existing path is preserved and
the conflicting alias is skipped with a warning at startup.
Automatic Clash format detection keeps the existing `(?i)(clash|mihomo)`
default matcher so existing subscription URLs continue returning YAML.
It does not distinguish legacy clients from Mihomo-based clients; Clash for
Windows users must use `/clash-legacy/<sub-id>` for a compatible profile.
### Base64 vs JSON
The **Base64** body is just the newline-joined share links, standard-base64
+22 -5
View File
@@ -36,14 +36,31 @@ https://<sub-host>:<sub-port>/<sub-path>/<sub-id>
**格式由路径决定**,每种格式都有各自的启用开关:
| 格式 | 路径 | 启用方式 | 输出 |
| --------------------- | --------- | ---------------- | --------------------------------------------------- |
| **原始链接** | `subPath` | 始终(若已开启) | 一组 `vless://`、`vmess://` 等链接的列表(当 `subEncrypt` 开启时进行 base64 编码)。 |
| **JSON** | `subJsonPath` | `subJsonEnable` | 完整的 Xray 客户端配置。 |
| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | YAML 配置文件。 |
| 格式 | 路径 | 启用方式 | 输出 |
| ----------------------------- | ---------------- | ---------------- | --------------------------------------------------- |
| **原始链接** | `subPath` | 始终(若已开启) | 一组 `vless://`、`vmess://` 等链接的列表(当 `subEncrypt` 开启时进行 base64 编码)。 |
| **JSON** | `subJsonPath` | `subJsonEnable` | 完整的 Xray 客户端配置。 |
| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | 完整的 Mihomo 兼容 YAML 配置。 |
| **Mihomo(明确端点)** | `/mihomo/` | `subClashEnable` | 完整 `subClashPath` 配置的别名。 |
| **Clash for Windows(旧版)** | `/clash-legacy/` | `subClashEnable` | 仅包含旧 Clash 内核支持的代理类型、传输方式和加密算法。 |
只有使用 **VLESS、VMess、Trojan、Shadowsocks 或 Hysteria2** 的已启用入站才会出现在订阅中,并按其订阅排序索引排列。使用 `Accept: text/html` 头(或 `?html=1`)请求 `subPath` 会返回一个人类可读的信息页面,而非原始内容。
Clash Verge Rev、Mihomo 及其他仍在维护的 Mihomo 客户端应使用
`/mihomo/<sub-id>`。已经停止维护的 Clash for Windows 应使用
`/clash-legacy/<sub-id>`;旧版端点只保留兼容的 VMess、Trojan 和
Shadowsocks 节点,并排除 VLESS、Hysteria2、Reality、XHTTP、HTTPUpgrade
和 Shadowsocks 2022。如果没有任何兼容节点,端点会明确返回 `422`,而不是返回一份无法导入的 YAML。
为避免 Mihomo 专用语法进入旧版配置,此端点始终使用最小的 `PROXY` 策略组与
`MATCH,PROXY` 规则,并忽略自定义 Clash 路由设置。
如果管理员已经把 `/mihomo/` 或 `/clash-legacy/` 分配给其他可配置订阅路径,
系统会保留原有路径,并在启动时记录警告、跳过发生冲突的别名。
Clash 格式自动识别保留原有的 `(?i)(clash|mihomo)` 默认匹配器,确保已有订阅 URL
继续返回 YAML。它不区分旧版客户端与 Mihomo 系客户端;Clash for Windows 用户
必须使用 `/clash-legacy/<sub-id>` 获取兼容配置。
### Base64 与 JSON
**Base64** 内容只是用换行符连接的分享链接,经标准 base64 编码(通过 `subEncrypt` 开关控制)。**JSON** 内容则将每个客户端包装为一份完整的 Xray 客户端配置 —— 一套固定的骨架(绑定到 127.0.0.1 的本地 SOCKS/HTTP 入站、DNS、路由、策略)加上一个指向该入站的 `proxy` 出站。3x-ui **对单个客户端输出单个配置对象,对多个客户端输出数组**,使用扁平的出站 `settings` 形式(`address`/`port`/`id``level: 8`),并从 `streamSettings` 中剥离 `sockopt`。