diff --git a/admin.go b/admin.go index 669088b..9889f43 100644 --- a/admin.go +++ b/admin.go @@ -83,7 +83,9 @@ func (b *Barnard) OpenAdminMenu() { if b.adminTargetChan != nil { b.adminTargetChan.RequestPermission() } - if root := b.Client.Channels[0]; root != nil && root != b.adminTargetChan { + var root *gumble.Channel + b.Client.Do(func() { root = b.Client.Channels[0] }) + if root != nil && root != b.adminTargetChan { root.RequestPermission() } b.UiAdmin.Rebuild() @@ -964,39 +966,47 @@ func (b *Barnard) findOrCreateACLRule(subjectType, subject string) *gumble.ACLRu } } -func (b *Barnard) findUser(token string) *gumble.User { +func (b *Barnard) findUser(token string) (found *gumble.User) { if b.Client == nil { return nil } - if session, err := strconv.ParseUint(token, 10, 32); err == nil { - if user := b.Client.Users[uint32(session)]; user != nil { - return user + b.Client.Do(func() { + if session, err := strconv.ParseUint(token, 10, 32); err == nil { + found = b.Client.Users[uint32(session)] + if found != nil { + return + } } - } - for _, user := range b.Client.Users { - if strings.EqualFold(user.Name, token) { - return user + for _, user := range b.Client.Users { + if strings.EqualFold(user.Name, token) { + found = user + return + } } - } - return nil + }) + return found } -func (b *Barnard) findChannel(token string) *gumble.Channel { +func (b *Barnard) findChannel(token string) (found *gumble.Channel) { if b.Client == nil { return nil } token = strings.TrimSpace(token) - if id, err := strconv.ParseUint(token, 10, 32); err == nil { - if channel := b.Client.Channels[uint32(id)]; channel != nil { - return channel + b.Client.Do(func() { + if id, err := strconv.ParseUint(token, 10, 32); err == nil { + found = b.Client.Channels[uint32(id)] + if found != nil { + return + } } - } - for _, channel := range b.Client.Channels { - if strings.EqualFold(channel.Name, token) { - return channel + for _, channel := range b.Client.Channels { + if strings.EqualFold(channel.Name, token) { + found = channel + return + } } - } - return nil + }) + return found } func (b *Barnard) findRegisteredUser(token string) *gumble.RegisteredUser { @@ -1019,7 +1029,9 @@ func (b *Barnard) adminCanRoot(permission gumble.Permission) bool { if b.Client == nil { return true } - return b.adminCanChannel(b.Client.Channels[0], permission) + var root *gumble.Channel + b.Client.Do(func() { root = b.Client.Channels[0] }) + return b.adminCanChannel(root, permission) } func (b *Barnard) adminCanChannel(channel *gumble.Channel, permission gumble.Permission) bool { diff --git a/admin_test.go b/admin_test.go index 59d0c80..1415b10 100644 --- a/admin_test.go +++ b/admin_test.go @@ -7,6 +7,20 @@ import ( "git.stormux.org/storm/barnard/uiterm" ) +// Regression: admin lookup helpers read Client.Users and Client.Channels while +// TCP handlers could mutate those maps. +func TestAdminLookupUsesClientSnapshot(t *testing.T) { + user := &gumble.User{Session: 7, Name: "Guest"} + channel := &gumble.Channel{ID: 4, Name: "Room"} + b := &Barnard{Client: &gumble.Client{Users: gumble.Users{7: user}, Channels: gumble.Channels{4: channel}}} + if b.findUser("guest") != user || b.findUser("7") != user { + t.Fatal("user lookup failed") + } + if b.findChannel("room") != channel || b.findChannel("4") != channel { + t.Fatal("channel lookup failed") + } +} + func TestParseToggleState(t *testing.T) { tests := []struct { name string