瀏覽代碼

fix(xray): guard RemoveUser against an uninitialized handler client

Every XrayAPI handler method returns an error when HandlerServiceClient is nil,
except RemoveUser, which dereferenced it directly. A depletion sweep runs Init
with the port ignored and, during a restart window where the fresh process's
api port is still 0, Init fails and leaves the client nil — so RemoveUser
panicked (recovered by the traffic writer, but re-thrown every poll) instead of
returning an error. Add the same nil guard the siblings have.
MHSanaei 1 天之前
父節點
當前提交
89c27c5835
共有 2 個文件被更改,包括 16 次插入1 次删除
  1. 6 1
      internal/xray/api.go
  2. 10 0
      internal/xray/api_test.go

+ 6 - 1
internal/xray/api.go

@@ -637,6 +637,11 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]an
 
 // RemoveUser removes a user from an inbound in the Xray core by email.
 func (x *XrayAPI) RemoveUser(inboundTag, email string) error {
+	if x.HandlerServiceClient == nil {
+		return common.NewError("xray HandlerServiceClient is not initialized")
+	}
+	client := *x.HandlerServiceClient
+
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
 
@@ -646,7 +651,7 @@ func (x *XrayAPI) RemoveUser(inboundTag, email string) error {
 		Operation: serial.ToTypedMessage(op),
 	}
 
-	_, err := (*x.HandlerServiceClient).AlterInbound(ctx, req)
+	_, err := client.AlterInbound(ctx, req)
 	if err != nil {
 		return fmt.Errorf("failed to remove user: %w", err)
 	}

+ 10 - 0
internal/xray/api_test.go

@@ -5,6 +5,16 @@ import (
 	"testing"
 )
 
+// RemoveUser must return an error, not panic, when the handler client is not
+// initialized — matching every sibling API method. A depletion sweep can reach
+// it with a nil client during a restart window where Init(0) failed.
+func TestRemoveUserGuardsNilHandlerClient(t *testing.T) {
+	err := (&XrayAPI{}).RemoveUser("in-443-tcp", "[email protected]")
+	if err == nil {
+		t.Fatal("RemoveUser with an uninitialized HandlerServiceClient must return an error")
+	}
+}
+
 func TestGetRequiredUserString_Present(t *testing.T) {
 	user := map[string]any{"email": "[email protected]"}
 	got, err := getRequiredUserString(user, "email")