mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-18 02:26:11 +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
|
uuid := client.ID
|
||||||
port := inbound.Port
|
port := inbound.Port
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork, _ := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
params["type"] = streamNetwork
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
@@ -840,7 +840,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
password := encodeUserinfo(client.Password)
|
password := encodeUserinfo(client.Password)
|
||||||
port := inbound.Port
|
port := inbound.Port
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork, _ := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
params["type"] = streamNetwork
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
@@ -913,9 +913,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
}
|
}
|
||||||
|
|
||||||
settings := s.linkSettings(inbound)
|
settings := s.linkSettings(inbound)
|
||||||
inboundPassword := settings["password"].(string)
|
inboundPassword, _ := settings["password"].(string)
|
||||||
method := settings["method"].(string)
|
method, _ := settings["method"].(string)
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork, _ := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
params["type"] = streamNetwork
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
@@ -1206,9 +1206,11 @@ func applyShareNetworkParams(stream map[string]any, streamNetwork string, params
|
|||||||
header, _ := tcp["header"].(map[string]any)
|
header, _ := tcp["header"].(map[string]any)
|
||||||
typeStr, _ := header["type"].(string)
|
typeStr, _ := header["type"].(string)
|
||||||
if typeStr == "http" {
|
if typeStr == "http" {
|
||||||
request := header["request"].(map[string]any)
|
request, _ := header["request"].(map[string]any)
|
||||||
requestPath, _ := request["path"].([]any)
|
requestPath, _ := request["path"].([]any)
|
||||||
params["path"] = requestPath[0].(string)
|
if len(requestPath) > 0 {
|
||||||
|
params["path"], _ = requestPath[0].(string)
|
||||||
|
}
|
||||||
host := ""
|
host := ""
|
||||||
if response, ok := header["response"].(map[string]any); ok {
|
if response, ok := header["response"].(map[string]any); ok {
|
||||||
if respHeaders, ok := response["headers"].(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)
|
applyPathAndHostParams(ws, params)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
params["serviceName"], _ = grpc["serviceName"].(string)
|
||||||
params["authority"], _ = grpc["authority"].(string)
|
params["authority"], _ = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if mm, _ := grpc["multiMode"].(bool); mm {
|
||||||
params["mode"] = "multi"
|
params["mode"] = "multi"
|
||||||
}
|
}
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
@@ -1262,9 +1264,11 @@ func applyVmessNetworkParams(stream map[string]any, network string, obj map[stri
|
|||||||
typeStr, _ := header["type"].(string)
|
typeStr, _ := header["type"].(string)
|
||||||
obj["type"] = typeStr
|
obj["type"] = typeStr
|
||||||
if typeStr == "http" {
|
if typeStr == "http" {
|
||||||
request := header["request"].(map[string]any)
|
request, _ := header["request"].(map[string]any)
|
||||||
requestPath, _ := request["path"].([]any)
|
requestPath, _ := request["path"].([]any)
|
||||||
obj["path"] = requestPath[0].(string)
|
if len(requestPath) > 0 {
|
||||||
|
obj["path"], _ = requestPath[0].(string)
|
||||||
|
}
|
||||||
host := ""
|
host := ""
|
||||||
if response, ok := header["response"].(map[string]any); ok {
|
if response, ok := header["response"].(map[string]any); ok {
|
||||||
if respHeaders, ok := response["headers"].(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)
|
applyPathAndHostObj(ws, obj)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||||
obj["path"] = grpc["serviceName"].(string)
|
obj["path"], _ = grpc["serviceName"].(string)
|
||||||
obj["authority"] = grpc["authority"].(string)
|
obj["authority"], _ = grpc["authority"].(string)
|
||||||
if grpc["multiMode"].(bool) {
|
if mm, _ := grpc["multiMode"].(bool); mm {
|
||||||
obj["type"] = "multi"
|
obj["type"] = "multi"
|
||||||
}
|
}
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
@@ -1451,15 +1455,17 @@ func applyShareRealityParams(stream map[string]any, params map[string]string, cl
|
|||||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||||
if realitySetting != nil {
|
if realitySetting != nil {
|
||||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||||
sNames, _ := sniValue.([]any)
|
if sNames, _ := sniValue.([]any); len(sNames) > 0 {
|
||||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
params["sni"], _ = sNames[random.Num(len(sNames))].(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]any)
|
if shortIds, _ := sidValue.([]any); len(shortIds) > 0 {
|
||||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
params["sid"], _ = shortIds[random.Num(len(shortIds))].(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||||
@@ -2422,12 +2428,13 @@ func searchHost(headers any) string {
|
|||||||
case []any:
|
case []any:
|
||||||
hosts, _ := v.([]any)
|
hosts, _ := v.([]any)
|
||||||
if len(hosts) > 0 {
|
if len(hosts) > 0 {
|
||||||
return hosts[0].(string)
|
h, _ := hosts[0].(string)
|
||||||
} else {
|
return h
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
return ""
|
||||||
case any:
|
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