From fa4ac3100dab1899bddaa231229fdc74982e8f11 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 15 Jul 2026 02:39:40 +0200 Subject: [PATCH] fix(sub): tolerate a hysteria inbound without hysteriaSettings in the JSON subscription genHy asserted stream["hysteriaSettings"].(map[string]any) without the comma-ok form, so a hysteria inbound whose StreamSettings omit the hysteriaSettings key (a valid, representable shape the raw generator renders fine) panicked and 500ed the entire JSON subscription. Use comma-ok; the downstream reads already guard each key, so a nil map degrades gracefully. --- internal/sub/json_service.go | 2 +- internal/sub/sub_panic_test.go | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/internal/sub/json_service.go b/internal/sub/json_service.go index c4140a7ff..6385b4349 100644 --- a/internal/sub/json_service.go +++ b/internal/sub/json_service.go @@ -496,7 +496,7 @@ func (s *SubJsonService) genHy(inbound *model.Inbound, newStream map[string]any, } _ = json.Unmarshal([]byte(inbound.StreamSettings), &stream) - hyStream := stream["hysteriaSettings"].(map[string]any) + hyStream, _ := stream["hysteriaSettings"].(map[string]any) outHyStream := map[string]any{ "version": int(version), "auth": client.Auth, diff --git a/internal/sub/sub_panic_test.go b/internal/sub/sub_panic_test.go index 10e8bf3ee..e7b16fade 100644 --- a/internal/sub/sub_panic_test.go +++ b/internal/sub/sub_panic_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/gin-gonic/gin" + + "github.com/mhsanaei/3x-ui/v3/internal/database" + "github.com/mhsanaei/3x-ui/v3/internal/database/model" ) // A subscription is built by iterating every client's share link with no @@ -41,3 +44,40 @@ func TestGetSubsToleratesUnusualStreamSettings(t *testing.T) { }) } } + +// The JSON subscription generator for a Hysteria inbound whose StreamSettings +// omit the hysteriaSettings key must not panic (which would 500 the whole JSON +// subscription); the raw generator already tolerates this shape. +func TestGetJsonToleratesHysteriaWithoutHysteriaSettings(t *testing.T) { + seedSubDB(t) + db := database.GetDB() + + const subId = "hy1" + const email = "hy@e" + ib := &model.Inbound{ + UserId: 1, Tag: "hy", Enable: true, Listen: "203.0.113.5", Port: 46200, + Protocol: model.Hysteria, + Remark: "hy", + Settings: fmt.Sprintf(`{"version":2,"clients":[{"auth":"hyauth","email":%q,"subId":%q,"enable":true}]}`, email, subId), + StreamSettings: `{"security":"tls","tlsSettings":{"serverName":"hy.sni"}}`, + } + if err := db.Create(ib).Error; err != nil { + t.Fatalf("seed inbound: %v", err) + } + client := &model.ClientRecord{Email: email, SubID: subId, Enable: true} + if err := db.Create(client).Error; err != nil { + t.Fatalf("seed client: %v", err) + } + if err := db.Create(&model.ClientInbound{ClientId: client.Id, InboundId: ib.Id}).Error; err != nil { + t.Fatalf("seed client_inbound: %v", err) + } + + jsonService := NewSubJsonService("", "", "", NewSubService("")) + out, _, err := jsonService.GetJson(subId, "sub.example.com", true) + if err != nil { + t.Fatalf("GetJson: %v", err) + } + if out == "" { + t.Fatal("GetJson returned empty for a hysteria inbound without hysteriaSettings") + } +}