mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-22 12:36:07 +00:00
fix(sub): carry a host's Final Mask into raw share links
A Host's Final Mask was merged into the JSON and Clash subscription outputs via applyHostStreamOverrides, but the raw link builders compute the fm param once from the inbound's own streamSettings.finalmask before the per-host fan-out, and the endpoint override path never read the host's mask. A Final Mask configured only on a host was silently dropped from vless/trojan/ss/vmess share links while an inbound-level mask worked everywhere. Merge the host mask into the fm param per endpoint with the same additive semantics as the JSON path (host tcp/udp masks appended to the inbound's, quicParams only when the inbound has none), for both the URL-param and the VMess object link forms. Closes #5831
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
@@ -111,3 +115,43 @@ func TestBuildEndpointVmessLinks(t *testing.T) {
|
||||
t.Fatalf("N4 mismatch.\n got: %q\nwant: %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// N5 — a host's Final Mask is appended to the inbound's own fm param (#5831).
|
||||
func TestBuildEndpointLinks_HostFinalMaskMerge(t *testing.T) {
|
||||
s := &SubService{}
|
||||
in := &model.Inbound{Remark: "ib"}
|
||||
params := map[string]string{"type": "tcp", "security": "tls", "fm": `{"tcp":[{"type":"sudoku"}]}`}
|
||||
eps := []ShareEndpoint{
|
||||
externalProxyToEndpoint(map[string]any{"forceTls": "same", "dest": "a.example.com", "port": float64(8443), "remark": "A", "isHost": true, "finalMask": `{"tcp":[{"type":"fragment"}]}`}),
|
||||
}
|
||||
got := s.buildEndpointLinks(eps, params, "tls",
|
||||
func(e ShareEndpoint) string { return fmt.Sprintf("vless://uid@%s", joinHostPort(e.Address, e.Port)) },
|
||||
func(e ShareEndpoint) string { return s.genRemark(in, "user", e.Remark, "") },
|
||||
)
|
||||
wantFm := "fm=" + url.QueryEscape(`{"tcp":[{"type":"sudoku"},{"type":"fragment"}]}`)
|
||||
if !strings.Contains(got, wantFm) {
|
||||
t.Fatalf("host finalMask not merged into the fm param.\n got: %q\nwant substring: %q", got, wantFm)
|
||||
}
|
||||
}
|
||||
|
||||
// N6 — same for the VMess object form: the host mask lands in obj["fm"].
|
||||
func TestBuildEndpointVmessLinks_HostFinalMask(t *testing.T) {
|
||||
s := &SubService{}
|
||||
in := &model.Inbound{Remark: "ib"}
|
||||
baseObj := map[string]any{"v": "2", "add": "base.example.com", "port": 443, "type": "none", "id": "uid", "scy": "auto", "net": "tcp", "tls": "tls"}
|
||||
eps := []ShareEndpoint{
|
||||
externalProxyToEndpoint(map[string]any{"forceTls": "same", "dest": "a.example.com", "port": float64(8443), "remark": "A", "isHost": true, "finalMask": `{"udp":[{"type":"salamander"}]}`}),
|
||||
}
|
||||
got := s.buildEndpointVmessLinks(eps, baseObj, in, "user", "tcp")
|
||||
raw, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(got, "vmess://"))
|
||||
if err != nil {
|
||||
t.Fatalf("decode vmess link: %v", err)
|
||||
}
|
||||
var obj map[string]any
|
||||
if err := json.Unmarshal(raw, &obj); err != nil {
|
||||
t.Fatalf("unmarshal vmess obj: %v", err)
|
||||
}
|
||||
if fm, _ := obj["fm"].(string); fm != `{"udp":[{"type":"salamander"}]}` {
|
||||
t.Fatalf("vmess fm = %q, want the host mask", obj["fm"])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user