mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-15 17:16:07 +00:00
fix(sub): stop the subscription from 500ing on valid-but-unusual stream settings
The raw share-link generators used unchecked type assertions and unguarded array indexing: an empty Reality shortIds/serverNames array (random.Num(0) panics), a tcp-http header with no request block or an empty request.path, a grpc block missing its keys, empty stream settings, and a non-string Host header all panicked mid-generation. Because getSubs loops every client's link with no recover, one such client 500s the entire subscription for everyone. The sibling JSON, Clash and frontend generators already guard these; make the raw generators match with comma-ok assertions and length checks.
This commit is contained in:
+29
-22
@@ -781,7 +781,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
}
|
||||
uuid := client.ID
|
||||
port := inbound.Port
|
||||
streamNetwork := stream["network"].(string)
|
||||
streamNetwork, _ := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
params["type"] = streamNetwork
|
||||
|
||||
@@ -840,7 +840,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
}
|
||||
password := encodeUserinfo(client.Password)
|
||||
port := inbound.Port
|
||||
streamNetwork := stream["network"].(string)
|
||||
streamNetwork, _ := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
params["type"] = streamNetwork
|
||||
|
||||
@@ -913,9 +913,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
}
|
||||
|
||||
settings := s.linkSettings(inbound)
|
||||
inboundPassword := settings["password"].(string)
|
||||
method := settings["method"].(string)
|
||||
streamNetwork := stream["network"].(string)
|
||||
inboundPassword, _ := settings["password"].(string)
|
||||
method, _ := settings["method"].(string)
|
||||
streamNetwork, _ := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
params["type"] = streamNetwork
|
||||
|
||||
@@ -1206,9 +1206,11 @@ func applyShareNetworkParams(stream map[string]any, streamNetwork string, params
|
||||
header, _ := tcp["header"].(map[string]any)
|
||||
typeStr, _ := header["type"].(string)
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]any)
|
||||
request, _ := header["request"].(map[string]any)
|
||||
requestPath, _ := request["path"].([]any)
|
||||
params["path"] = requestPath[0].(string)
|
||||
if len(requestPath) > 0 {
|
||||
params["path"], _ = requestPath[0].(string)
|
||||
}
|
||||
host := ""
|
||||
if response, ok := header["response"].(map[string]any); ok {
|
||||
if respHeaders, ok := response["headers"].(map[string]any); ok {
|
||||
@@ -1229,9 +1231,9 @@ func applyShareNetworkParams(stream map[string]any, streamNetwork string, params
|
||||
applyPathAndHostParams(ws, params)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
params["serviceName"], _ = grpc["serviceName"].(string)
|
||||
params["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
if mm, _ := grpc["multiMode"].(bool); mm {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
@@ -1262,9 +1264,11 @@ func applyVmessNetworkParams(stream map[string]any, network string, obj map[stri
|
||||
typeStr, _ := header["type"].(string)
|
||||
obj["type"] = typeStr
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]any)
|
||||
request, _ := header["request"].(map[string]any)
|
||||
requestPath, _ := request["path"].([]any)
|
||||
obj["path"] = requestPath[0].(string)
|
||||
if len(requestPath) > 0 {
|
||||
obj["path"], _ = requestPath[0].(string)
|
||||
}
|
||||
host := ""
|
||||
if response, ok := header["response"].(map[string]any); ok {
|
||||
if respHeaders, ok := response["headers"].(map[string]any); ok {
|
||||
@@ -1284,9 +1288,9 @@ func applyVmessNetworkParams(stream map[string]any, network string, obj map[stri
|
||||
applyPathAndHostObj(ws, obj)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||
obj["path"] = grpc["serviceName"].(string)
|
||||
obj["authority"] = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
obj["path"], _ = grpc["serviceName"].(string)
|
||||
obj["authority"], _ = grpc["authority"].(string)
|
||||
if mm, _ := grpc["multiMode"].(bool); mm {
|
||||
obj["type"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
@@ -1451,15 +1455,17 @@ func applyShareRealityParams(stream map[string]any, params map[string]string, cl
|
||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||
if realitySetting != nil {
|
||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||
sNames, _ := sniValue.([]any)
|
||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||
if sNames, _ := sniValue.([]any); len(sNames) > 0 {
|
||||
params["sni"], _ = sNames[random.Num(len(sNames))].(string)
|
||||
}
|
||||
}
|
||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||
params["pbk"], _ = pbkValue.(string)
|
||||
}
|
||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||
shortIds, _ := sidValue.([]any)
|
||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||
if shortIds, _ := sidValue.([]any); len(shortIds) > 0 {
|
||||
params["sid"], _ = shortIds[random.Num(len(shortIds))].(string)
|
||||
}
|
||||
}
|
||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||
@@ -2422,12 +2428,13 @@ func searchHost(headers any) string {
|
||||
case []any:
|
||||
hosts, _ := v.([]any)
|
||||
if len(hosts) > 0 {
|
||||
return hosts[0].(string)
|
||||
} else {
|
||||
return ""
|
||||
h, _ := hosts[0].(string)
|
||||
return h
|
||||
}
|
||||
return ""
|
||||
case any:
|
||||
return v.(string)
|
||||
h, _ := v.(string)
|
||||
return h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// A subscription is built by iterating every client's share link with no
|
||||
// recover(), so any panic in the link generators 500s the whole subscription
|
||||
// for every client. Valid-but-unusual stream settings (an empty Reality
|
||||
// shortIds/serverNames array, a tcp-http header with no request, a grpc block
|
||||
// missing its keys) must therefore produce a link, not a panic.
|
||||
func TestGetSubsToleratesUnusualStreamSettings(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
cases := []struct {
|
||||
name string
|
||||
stream string
|
||||
}{
|
||||
{"reality empty arrays", `{"network":"tcp","security":"reality","realitySettings":{"serverNames":[],"shortIds":[],"settings":{"publicKey":"pk"}}}`},
|
||||
{"tcp http missing request", `{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"http","response":{"headers":{}}}}}`},
|
||||
{"tcp http empty path", `{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"http","request":{"path":[]}}}}`},
|
||||
{"grpc missing keys", `{"network":"grpc","security":"none","grpcSettings":{}}`},
|
||||
{"empty stream settings", `{}`},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
seedSubDB(t)
|
||||
subId := fmt.Sprintf("s%d", i)
|
||||
seedSubInbound(t, subId, fmt.Sprintf("t%d", i), 46000+i, 1, tc.stream)
|
||||
|
||||
links, _, _, _, err := NewSubService("").GetSubs(subId, "req.example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("GetSubs errored: %v", err)
|
||||
}
|
||||
if len(links) != 1 {
|
||||
t.Fatalf("expected 1 share link, got %d", len(links))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user