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:
MHSanaei
2026-07-15 02:36:15 +02:00
parent e4d4ab3a63
commit abab7cd000
2 changed files with 72 additions and 22 deletions
+29 -22
View File
@@ -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
}
}
}
+43
View File
@@ -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))
}
})
}
}