|
|
@@ -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"])
|
|
|
+ }
|
|
|
+}
|