mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
commit
8d50500b7d
@ -335,12 +335,14 @@ func (channel *Channel) regenerateMembersCache() {
|
|||||||
|
|
||||||
// Names sends the list of users joined to the channel to the given client.
|
// Names sends the list of users joined to the channel to the given client.
|
||||||
func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
||||||
|
isJoined := channel.hasClient(client)
|
||||||
isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
|
isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
|
||||||
isUserhostInNames := rb.session.capabilities.Has(caps.UserhostInNames)
|
isUserhostInNames := rb.session.capabilities.Has(caps.UserhostInNames)
|
||||||
|
|
||||||
maxNamLen := 480 - len(client.server.name) - len(client.Nick())
|
maxNamLen := 480 - len(client.server.name) - len(client.Nick())
|
||||||
var namesLines []string
|
var namesLines []string
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
if isJoined || !channel.flags.HasMode(modes.Secret) {
|
||||||
for _, target := range channel.Members() {
|
for _, target := range channel.Members() {
|
||||||
var nick string
|
var nick string
|
||||||
if isUserhostInNames {
|
if isUserhostInNames {
|
||||||
@ -349,12 +351,15 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
|||||||
nick = target.Nick()
|
nick = target.Nick()
|
||||||
}
|
}
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
modes := channel.members[target]
|
modeSet := channel.members[target]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
if modes == nil {
|
if modeSet == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prefix := modes.Prefixes(isMultiPrefix)
|
if !isJoined && target.flags.HasMode(modes.Invisible) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prefix := modeSet.Prefixes(isMultiPrefix)
|
||||||
if buffer.Len()+len(nick)+len(prefix)+1 > maxNamLen {
|
if buffer.Len()+len(nick)+len(prefix)+1 > maxNamLen {
|
||||||
namesLines = append(namesLines, buffer.String())
|
namesLines = append(namesLines, buffer.String())
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
@ -368,6 +373,7 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
|||||||
if buffer.Len() > 0 {
|
if buffer.Len() > 0 {
|
||||||
namesLines = append(namesLines, buffer.String())
|
namesLines = append(namesLines, buffer.String())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, line := range namesLines {
|
for _, line := range namesLines {
|
||||||
if buffer.Len() > 0 {
|
if buffer.Len() > 0 {
|
||||||
@ -377,30 +383,39 @@ func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
|
|||||||
rb.Add(nil, client.server.name, RPL_ENDOFNAMES, client.nick, channel.name, client.t("End of NAMES list"))
|
rb.Add(nil, client.server.name, RPL_ENDOFNAMES, client.nick, channel.name, client.t("End of NAMES list"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func channelUserModeIsAtLeast(clientModes *modes.ModeSet, permission modes.Mode) bool {
|
// does `clientMode` give you privileges to grant/remove `targetMode` to/from people,
|
||||||
if clientModes == nil {
|
// or to kick them?
|
||||||
return false
|
func channelUserModeHasPrivsOver(clientMode modes.Mode, targetMode modes.Mode) bool {
|
||||||
}
|
switch clientMode {
|
||||||
|
case modes.ChannelFounder:
|
||||||
for _, mode := range modes.ChannelUserModes {
|
|
||||||
if clientModes.HasMode(mode) {
|
|
||||||
return true
|
return true
|
||||||
}
|
case modes.ChannelAdmin, modes.ChannelOperator:
|
||||||
|
// admins cannot kick other admins, operators *can* kick other operators
|
||||||
if mode == permission {
|
return targetMode != modes.ChannelFounder && targetMode != modes.ChannelAdmin
|
||||||
break
|
case modes.Halfop:
|
||||||
}
|
// halfops cannot kick other halfops
|
||||||
}
|
return targetMode != modes.ChannelFounder && targetMode != modes.ChannelAdmin && targetMode != modes.Halfop
|
||||||
|
default:
|
||||||
|
// voice and unprivileged cannot kick anyone
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
|
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
|
||||||
func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) bool {
|
func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) bool {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
clientModes := channel.members[client]
|
clientModes := channel.members[client]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
return channelUserModeIsAtLeast(clientModes, permission)
|
|
||||||
|
for _, mode := range modes.ChannelUserModes {
|
||||||
|
if clientModes.HasMode(mode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if mode == permission {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) string {
|
func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) string {
|
||||||
@ -420,28 +435,13 @@ func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool
|
|||||||
targetModes := channel.members[target]
|
targetModes := channel.members[target]
|
||||||
channel.stateMutex.RUnlock()
|
channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
if clientModes.HasMode(modes.ChannelFounder) {
|
return channelUserModeHasPrivsOver(clientModes.HighestChannelUserMode(), targetModes.HighestChannelUserMode())
|
||||||
// founder can kick anyone
|
|
||||||
return true
|
|
||||||
} else if clientModes.HasMode(modes.ChannelAdmin) {
|
|
||||||
// admins cannot kick other admins
|
|
||||||
return !channelUserModeIsAtLeast(targetModes, modes.ChannelAdmin)
|
|
||||||
} else if clientModes.HasMode(modes.ChannelOperator) {
|
|
||||||
// operators *can* kick other operators
|
|
||||||
return !channelUserModeIsAtLeast(targetModes, modes.ChannelAdmin)
|
|
||||||
} else if clientModes.HasMode(modes.Halfop) {
|
|
||||||
// halfops cannot kick other halfops
|
|
||||||
return !channelUserModeIsAtLeast(targetModes, modes.Halfop)
|
|
||||||
} else {
|
|
||||||
// voice and unprivileged cannot kick anyone
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) hasClient(client *Client) bool {
|
func (channel *Channel) hasClient(client *Client) bool {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
|
||||||
_, present := channel.members[client]
|
_, present := channel.members[client]
|
||||||
|
channel.stateMutex.RUnlock()
|
||||||
return present
|
return present
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,7 +902,7 @@ func msgCommandToHistType(server *Server, command string) (history.ItemType, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) SendSplitMessage(command string, minPrefix *modes.Mode, clientOnlyTags map[string]string, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
|
func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mode, clientOnlyTags map[string]string, client *Client, message utils.SplitMessage, rb *ResponseBuffer) {
|
||||||
histType, err := msgCommandToHistType(channel.server, command)
|
histType, err := msgCommandToHistType(channel.server, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -920,11 +920,11 @@ func (channel *Channel) SendSplitMessage(command string, minPrefix *modes.Mode,
|
|||||||
chname := channel.Name()
|
chname := channel.Name()
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
// for STATUSMSG
|
// STATUSMSG targets are prefixed with the supplied min-prefix, e.g., @#channel
|
||||||
var minPrefixMode modes.Mode
|
if minPrefixMode != modes.Mode(0) {
|
||||||
if minPrefix != nil {
|
chname = fmt.Sprintf("%s%s", modes.ChannelModePrefixes[minPrefixMode], chname)
|
||||||
minPrefixMode = *minPrefix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// send echo-message
|
// send echo-message
|
||||||
// TODO this should use `now` as the time for consistency
|
// TODO this should use `now` as the time for consistency
|
||||||
if rb.session.capabilities.Has(caps.EchoMessage) {
|
if rb.session.capabilities.Has(caps.EchoMessage) {
|
||||||
@ -959,7 +959,7 @@ func (channel *Channel) SendSplitMessage(command string, minPrefix *modes.Mode,
|
|||||||
if member == client {
|
if member == client {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
|
if minPrefixMode != modes.Mode(0) && !channel.ClientIsAtLeast(member, minPrefixMode) {
|
||||||
// STATUSMSG
|
// STATUSMSG
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -352,3 +352,10 @@ func (channel *Channel) DirtyBits() (dirtyBits uint) {
|
|||||||
channel.stateMutex.Unlock()
|
channel.stateMutex.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (channel *Channel) HighestUserMode(client *Client) (result modes.Mode) {
|
||||||
|
channel.stateMutex.RLock()
|
||||||
|
clientModes := channel.members[client]
|
||||||
|
channel.stateMutex.RUnlock()
|
||||||
|
return clientModes.HighestChannelUserMode()
|
||||||
|
}
|
||||||
|
17
irc/modes.go
17
irc/modes.go
@ -128,16 +128,12 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
|||||||
}
|
}
|
||||||
cfarg, _ := CasefoldName(change.Arg)
|
cfarg, _ := CasefoldName(change.Arg)
|
||||||
isSelfChange := cfarg == client.NickCasefolded()
|
isSelfChange := cfarg == client.NickCasefolded()
|
||||||
// Admins can't give other people Admin or remove it from others
|
|
||||||
if change.Mode == modes.ChannelAdmin && !isSelfChange {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if change.Op == modes.Remove && isSelfChange {
|
if change.Op == modes.Remove && isSelfChange {
|
||||||
// "There is no restriction, however, on anyone `deopping' themselves"
|
// "There is no restriction, however, on anyone `deopping' themselves"
|
||||||
// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
|
// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return channel.ClientIsAtLeast(client, change.Mode)
|
return channelUserModeHasPrivsOver(channel.HighestUserMode(client), change.Mode)
|
||||||
case modes.BanMask:
|
case modes.BanMask:
|
||||||
// #163: allow unprivileged users to list ban masks
|
// #163: allow unprivileged users to list ban masks
|
||||||
return isListOp(change) || channel.ClientIsAtLeast(client, modes.ChannelOperator)
|
return isListOp(change) || channel.ClientIsAtLeast(client, modes.ChannelOperator)
|
||||||
@ -254,13 +250,6 @@ func umodeGreaterThan(l modes.Mode, r modes.Mode) bool {
|
|||||||
|
|
||||||
// ProcessAccountToUmodeChange processes Add/Remove/List operations for channel persistent usermodes.
|
// ProcessAccountToUmodeChange processes Add/Remove/List operations for channel persistent usermodes.
|
||||||
func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes.ModeChange) (results []modes.ModeChange, err error) {
|
func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes.ModeChange) (results []modes.ModeChange, err error) {
|
||||||
hasPrivsOver := func(l modes.Mode, r modes.Mode) bool {
|
|
||||||
if l == modes.ChannelAdmin {
|
|
||||||
return umodeGreaterThan(l, r)
|
|
||||||
}
|
|
||||||
return l == r || umodeGreaterThan(l, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
changed := false
|
changed := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if changed {
|
if changed {
|
||||||
@ -284,9 +273,9 @@ func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes
|
|||||||
// operators and founders can do anything
|
// operators and founders can do anything
|
||||||
hasPrivs := isOperChange || (account != "" && account == channel.registeredFounder)
|
hasPrivs := isOperChange || (account != "" && account == channel.registeredFounder)
|
||||||
// halfop and up can list, and do add/removes at levels <= their own
|
// halfop and up can list, and do add/removes at levels <= their own
|
||||||
if change.Op == modes.List && hasPrivsOver(clientMode, modes.Halfop) {
|
if change.Op == modes.List && (clientMode == modes.Halfop || umodeGreaterThan(clientMode, modes.Halfop)) {
|
||||||
hasPrivs = true
|
hasPrivs = true
|
||||||
} else if hasPrivsOver(clientMode, modes.Halfop) && hasPrivsOver(clientMode, targetModeNow) && hasPrivsOver(clientMode, targetModeAfter) {
|
} else if channelUserModeHasPrivsOver(clientMode, targetModeNow) && channelUserModeHasPrivsOver(clientMode, targetModeAfter) {
|
||||||
hasPrivs = true
|
hasPrivs = true
|
||||||
}
|
}
|
||||||
if !hasPrivs {
|
if !hasPrivs {
|
||||||
|
@ -143,12 +143,6 @@ var (
|
|||||||
Halfop Mode = 'h' // arg
|
Halfop Mode = 'h' // arg
|
||||||
Voice Mode = 'v' // arg
|
Voice Mode = 'v' // arg
|
||||||
|
|
||||||
// ChannelPrivModes holds the list of modes that are privileged, ie founder/op/halfop, in order.
|
|
||||||
// voice is not in this list because it cannot perform channel operator actions.
|
|
||||||
ChannelPrivModes = Modes{
|
|
||||||
ChannelFounder, ChannelAdmin, ChannelOperator, Halfop,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChannelUserModes holds the list of all modes that can be applied to a user in a channel,
|
// ChannelUserModes holds the list of all modes that can be applied to a user in a channel,
|
||||||
// including Voice, in descending order of precedence
|
// including Voice, in descending order of precedence
|
||||||
ChannelUserModes = Modes{
|
ChannelUserModes = Modes{
|
||||||
@ -171,23 +165,24 @@ var (
|
|||||||
// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
|
// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
|
||||||
func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
|
func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
|
||||||
name = target
|
name = target
|
||||||
for {
|
for i := 0; i < len(name); i++ {
|
||||||
if len(name) > 0 && strings.Contains("~&@%+", string(name[0])) {
|
switch name[i] {
|
||||||
prefixes += string(name[0])
|
case '~', '&', '@', '%', '+':
|
||||||
name = name[1:]
|
prefixes = target[:i+1]
|
||||||
} else {
|
name = target[i+1:]
|
||||||
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return prefixes, name
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
|
// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
|
||||||
func GetLowestChannelModePrefix(prefixes string) (lowest *Mode) {
|
func GetLowestChannelModePrefix(prefixes string) (lowest Mode) {
|
||||||
for i, mode := range ChannelUserModes {
|
for i, mode := range ChannelUserModes {
|
||||||
if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
|
if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
|
||||||
lowest = &ChannelPrivModes[i]
|
lowest = ChannelUserModes[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -405,3 +400,15 @@ func (set *ModeSet) Prefixes(isMultiPrefix bool) (prefixes string) {
|
|||||||
|
|
||||||
return prefixes
|
return prefixes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HighestChannelUserMode returns the most privileged channel-user mode
|
||||||
|
// (e.g., ChannelFounder, Halfop, Voice) present in the ModeSet.
|
||||||
|
// If no such modes are present, or `set` is nil, returns the zero mode.
|
||||||
|
func (set *ModeSet) HighestChannelUserMode() (result Mode) {
|
||||||
|
for _, mode := range ChannelUserModes {
|
||||||
|
if set.HasMode(mode) {
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -90,3 +90,26 @@ func TestNilReceivers(t *testing.T) {
|
|||||||
t.Errorf("nil Modeset should have empty String(), got %v instead", str)
|
t.Errorf("nil Modeset should have empty String(), got %v instead", str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHighestChannelUserMode(t *testing.T) {
|
||||||
|
set := NewModeSet()
|
||||||
|
|
||||||
|
if set.HighestChannelUserMode() != Mode(0) {
|
||||||
|
t.Errorf("no channel user modes should be present yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
set.SetMode(Voice, true)
|
||||||
|
if set.HighestChannelUserMode() != Voice {
|
||||||
|
t.Errorf("should see that user is voiced")
|
||||||
|
}
|
||||||
|
|
||||||
|
set.SetMode(ChannelAdmin, true)
|
||||||
|
if set.HighestChannelUserMode() != ChannelAdmin {
|
||||||
|
t.Errorf("should see that user has channel admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
set = nil
|
||||||
|
if set.HighestChannelUserMode() != Mode(0) {
|
||||||
|
t.Errorf("nil modeset should have the zero mode as highest channel-user mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -48,3 +48,22 @@ func TestUmodeGreaterThan(t *testing.T) {
|
|||||||
t.Errorf("modes should not be greater than themselves")
|
t.Errorf("modes should not be greater than themselves")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertEqual(supplied, expected interface{}, t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(supplied, expected) {
|
||||||
|
t.Errorf("expected %v but got %v", expected, supplied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelUserModeHasPrivsOver(t *testing.T) {
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.Voice, modes.Halfop), false, t)
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.Mode(0), modes.Halfop), false, t)
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.Voice, modes.Mode(0)), false, t)
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.ChannelAdmin, modes.ChannelAdmin), false, t)
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.Halfop, modes.Halfop), false, t)
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.Voice, modes.Voice), false, t)
|
||||||
|
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.Halfop, modes.Voice), true, t)
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.ChannelFounder, modes.ChannelAdmin), true, t)
|
||||||
|
assertEqual(channelUserModeHasPrivsOver(modes.ChannelOperator, modes.ChannelOperator), true, t)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user