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