Răsfoiți Sursa

fix(link): parse the snake_case and extra-blob xhttp fields when importing a share link

The panel's share-link emitters (Go and TS) carry advanced xhttp knobs as a
snake_case x_padding_bytes plus an extra=<json> payload, but the Go parser's
xhttp branch read only top-level camelCase params, so importing an xhttp link
via the outbound-subscription feature dropped xPaddingBytes, scMaxEachPostBytes
and the rest, silently reverting them to the stream defaults and producing a
non-working outbound. Mirror the TS parser: read the snake_case alias, merge the
extra JSON blob, then let explicit camelCase params win.
MHSanaei 1 zi în urmă
părinte
comite
e8cf7242a0

+ 16 - 1
internal/util/link/outbound.go

@@ -622,7 +622,22 @@ func applyTransport(stream map[string]any, p url.Values) {
 		if m := p.Get("mode"); m != "" {
 			xh["mode"] = m
 		}
-		// A few advanced xhttp fields that are commonly carried
+		// The panel's own emitters carry the advanced xhttp knobs as a
+		// snake_case x_padding_bytes plus an extra=<json> blob, not as top-level
+		// camelCase params. Mirror the TS parser's precedence (snake_case alias
+		// -> extra JSON -> explicit camelCase) so a re-imported share link keeps
+		// them instead of silently reverting to the stream defaults.
+		if v := p.Get("x_padding_bytes"); v != "" {
+			xh["xPaddingBytes"] = v
+		}
+		if extra := p.Get("extra"); extra != "" {
+			var parsed map[string]any
+			if err := json.Unmarshal([]byte(extra), &parsed); err == nil {
+				for k, v := range parsed {
+					xh[k] = v
+				}
+			}
+		}
 		for _, k := range []string{"xPaddingBytes", "scMaxEachPostBytes", "scMinPostsIntervalMs", "uplinkChunkSize"} {
 			if v := p.Get(k); v != "" {
 				xh[k] = v

+ 21 - 0
internal/util/link/outbound_helpers_test.go

@@ -156,6 +156,27 @@ func TestParse_WSAndGRPCTransport(t *testing.T) {
 	}
 }
 
+func TestParse_XhttpExtraAndSnakeCaseFields(t *testing.T) {
+	q := url.Values{}
+	q.Set("type", "xhttp")
+	q.Set("encryption", "none")
+	q.Set("security", "none")
+	q.Set("mode", "auto")
+	q.Set("x_padding_bytes", "1-50")
+	q.Set("extra", `{"mode":"auto","xPaddingBytes":"1-50","scMaxEachPostBytes":"1000000"}`)
+	res, err := ParseLink("vless://[email protected]:443?" + q.Encode() + "#r")
+	if err != nil {
+		t.Fatalf("parse: %v", err)
+	}
+	xh := streamSub(t, res, "xhttpSettings")
+	if xh["xPaddingBytes"] != "1-50" {
+		t.Errorf("xPaddingBytes = %v, want 1-50 (dropped from the snake_case/extra payload the emitter writes)", xh["xPaddingBytes"])
+	}
+	if xh["scMaxEachPostBytes"] != "1000000" {
+		t.Errorf("scMaxEachPostBytes = %v, want 1000000 (dropped from the extra blob)", xh["scMaxEachPostBytes"])
+	}
+}
+
 func TestParse_TCPHTTPHeader(t *testing.T) {
 	res, err := ParseLink("vless://[email protected]:443?type=tcp&headerType=http&host=ex.com&path=%2F")
 	if err != nil {