3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-22 11:59:40 +01:00
Make snomask add/remove behavior match other ircds
This commit is contained in:
Shivaram Lingamneni 2021-02-08 11:43:13 -05:00
parent 42316bc04f
commit 91cfdb963d
8 changed files with 214 additions and 44 deletions

View File

@ -32,6 +32,7 @@ test:
cd irc/modes && go test . && go vet . cd irc/modes && go test . && go vet .
cd irc/mysql && go test . && go vet . cd irc/mysql && go test . && go vet .
cd irc/passwd && go test . && go vet . cd irc/passwd && go test . && go vet .
cd irc/sno && go test . && go vet .
cd irc/utils && go test . && go vet . cd irc/utils && go test . && go vet .
./.check-gofmt.sh ./.check-gofmt.sh

View File

@ -75,30 +75,28 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
} }
} else { } else {
// server notices are weird // server notices are weird
if !client.HasMode(modes.Operator) { if !client.HasMode(modes.Operator) || change.Op == modes.List {
continue continue
} }
var masks []sno.Mask
if change.Op == modes.Add || change.Op == modes.Remove { currentMasks := client.server.snomasks.MasksEnabled(client)
var newArg string addMasks, removeMasks, newArg := sno.EvaluateSnomaskChanges(change.Op == modes.Add, change.Arg, currentMasks)
for _, char := range change.Arg {
mask := sno.Mask(char) success := false
if sno.ValidMasks[mask] { if len(addMasks) != 0 {
masks = append(masks, mask)
newArg += string(char)
}
}
change.Arg = newArg
}
if change.Op == modes.Add {
oper := client.Oper() oper := client.Oper()
// #1176: require special operator privileges to subscribe to snomasks // #1176: require special operator privileges to subscribe to snomasks
if oper.HasRoleCapab("snomasks") || oper.HasRoleCapab("ban") { if oper.HasRoleCapab("snomasks") || oper.HasRoleCapab("ban") {
client.server.snomasks.AddMasks(client, masks...) success = true
applied = append(applied, change) client.server.snomasks.AddMasks(client, addMasks...)
} }
} else if change.Op == modes.Remove { }
client.server.snomasks.RemoveMasks(client, masks...) if len(removeMasks) != 0 {
success = true
client.server.snomasks.RemoveMasks(client, removeMasks...)
}
if success {
change.Arg = newArg
applied = append(applied, change) applied = append(applied, change)
} }
} }

View File

@ -212,12 +212,10 @@ func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
// put arg into modechange if needed // put arg into modechange if needed
switch Mode(mode) { switch Mode(mode) {
case ServerNotice: case ServerNotice:
// always require arg // arg is optional for ServerNotice (we accept bare `-s`)
if len(params) > skipArgs { if len(params) > skipArgs {
change.Arg = params[skipArgs] change.Arg = params[skipArgs]
skipArgs++ skipArgs++
} else {
continue
} }
} }

View File

@ -15,6 +15,38 @@ func assertEqual(supplied, expected interface{}, t *testing.T) {
} }
} }
func TestParseUserModeChanges(t *testing.T) {
emptyUnknown := make(map[rune]bool)
changes, unknown := ParseUserModeChanges("+i")
assertEqual(unknown, emptyUnknown, t)
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}}, t)
// no-op change to sno
changes, unknown = ParseUserModeChanges("+is")
assertEqual(unknown, emptyUnknown, t)
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}, ModeChange{Op: Add, Mode: ServerNotice}}, t)
// add snomasks
changes, unknown = ParseUserModeChanges("+is", "ac")
assertEqual(unknown, emptyUnknown, t)
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: Invisible}, ModeChange{Op: Add, Mode: ServerNotice, Arg: "ac"}}, t)
// remove snomasks
changes, unknown = ParseUserModeChanges("+s", "-cx")
assertEqual(unknown, emptyUnknown, t)
assertEqual(changes, ModeChanges{ModeChange{Op: Add, Mode: ServerNotice, Arg: "-cx"}}, t)
// remove all snomasks (arg is parsed but has no meaning)
changes, unknown = ParseUserModeChanges("-is", "ac")
assertEqual(unknown, emptyUnknown, t)
assertEqual(changes, ModeChanges{ModeChange{Op: Remove, Mode: Invisible}, ModeChange{Op: Remove, Mode: ServerNotice, Arg: "ac"}}, t)
// remove all snomasks
changes, unknown = ParseUserModeChanges("-is")
assertEqual(unknown, emptyUnknown, t)
assertEqual(changes, ModeChanges{ModeChange{Op: Remove, Mode: Invisible}, ModeChange{Op: Remove, Mode: ServerNotice}}, t)
}
func TestIssue874(t *testing.T) { func TestIssue874(t *testing.T) {
emptyUnknown := make(map[rune]bool) emptyUnknown := make(map[rune]bool)
modes, unknown := ParseChannelModeChanges("+k") modes, unknown := ParseChannelModeChanges("+k")

View File

@ -7,6 +7,8 @@ package sno
// Mask is a type of server notice mask. // Mask is a type of server notice mask.
type Mask rune type Mask rune
type Masks []Mask
// Notice mask types // Notice mask types
const ( const (
LocalAnnouncements Mask = 'a' LocalAnnouncements Mask = 'a'
@ -18,8 +20,8 @@ const (
LocalQuits Mask = 'q' LocalQuits Mask = 'q'
Stats Mask = 't' Stats Mask = 't'
LocalAccounts Mask = 'u' LocalAccounts Mask = 'u'
LocalXline Mask = 'x'
LocalVhosts Mask = 'v' LocalVhosts Mask = 'v'
LocalXline Mask = 'x'
) )
var ( var (
@ -39,17 +41,17 @@ var (
} }
// ValidMasks contains the snomasks that we support. // ValidMasks contains the snomasks that we support.
ValidMasks = map[Mask]bool{ ValidMasks = []Mask{
LocalAnnouncements: true, LocalAnnouncements,
LocalConnects: true, LocalConnects,
LocalChannels: true, LocalChannels,
LocalKills: true, LocalKills,
LocalNicks: true, LocalNicks,
LocalOpers: true, LocalOpers,
LocalQuits: true, LocalQuits,
Stats: true, Stats,
LocalAccounts: true, LocalAccounts,
LocalXline: true, LocalVhosts,
LocalVhosts: true, LocalXline,
} }
) )

87
irc/sno/utils.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package sno
import (
"strings"
)
func IsValidMask(r rune) bool {
for _, m := range ValidMasks {
if m == Mask(r) {
return true
}
}
return false
}
func (masks Masks) String() string {
var buf strings.Builder
buf.Grow(len(masks))
for _, m := range masks {
buf.WriteRune(rune(m))
}
return buf.String()
}
func (masks Masks) Contains(mask Mask) bool {
for _, m := range masks {
if mask == m {
return true
}
}
return false
}
// Evaluate changes to snomasks made with MODE. There are several cases:
// adding snomasks with `/mode +s a` or `/mode +s +a`, removing them with `/mode +s -a`,
// adding all with `/mode +s *` or `/mode +s +*`, removing all with `/mode +s -*` or `/mode -s`
func EvaluateSnomaskChanges(add bool, arg string, currentMasks Masks) (addMasks, removeMasks Masks, newArg string) {
if add {
if len(arg) == 0 {
return
}
add := true
switch arg[0] {
case '+':
arg = arg[1:]
case '-':
add = false
arg = arg[1:]
default:
// add
}
if strings.IndexByte(arg, '*') != -1 {
if add {
for _, mask := range ValidMasks {
if !currentMasks.Contains(mask) {
addMasks = append(addMasks, mask)
}
}
} else {
removeMasks = currentMasks
}
} else {
for _, r := range arg {
if IsValidMask(r) {
m := Mask(r)
if add && !currentMasks.Contains(m) {
addMasks = append(addMasks, m)
} else if !add && currentMasks.Contains(m) {
removeMasks = append(removeMasks, m)
}
}
}
}
if len(addMasks) != 0 {
newArg = "+" + addMasks.String()
} else if len(removeMasks) != 0 {
newArg = "-" + removeMasks.String()
}
} else {
removeMasks = currentMasks
newArg = ""
}
return
}

53
irc/sno/utils_test.go Normal file
View File

@ -0,0 +1,53 @@
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package sno
import (
"fmt"
"reflect"
"testing"
)
func assertEqual(supplied, expected interface{}, t *testing.T) {
if !reflect.DeepEqual(supplied, expected) {
panic(fmt.Sprintf("expected %#v but got %#v", expected, supplied))
}
}
func TestEvaluateSnomaskChanges(t *testing.T) {
add, remove, newArg := EvaluateSnomaskChanges(true, "*", nil)
assertEqual(add, Masks{'a', 'c', 'j', 'k', 'n', 'o', 'q', 't', 'u', 'v', 'x'}, t)
assertEqual(len(remove), 0, t)
assertEqual(newArg, "+acjknoqtuvx", t)
add, remove, newArg = EvaluateSnomaskChanges(true, "*", Masks{'a', 'u'})
assertEqual(add, Masks{'c', 'j', 'k', 'n', 'o', 'q', 't', 'v', 'x'}, t)
assertEqual(len(remove), 0, t)
assertEqual(newArg, "+cjknoqtvx", t)
add, remove, newArg = EvaluateSnomaskChanges(true, "-a", Masks{'a', 'u'})
assertEqual(len(add), 0, t)
assertEqual(remove, Masks{'a'}, t)
assertEqual(newArg, "-a", t)
add, remove, newArg = EvaluateSnomaskChanges(true, "-*", Masks{'a', 'u'})
assertEqual(len(add), 0, t)
assertEqual(remove, Masks{'a', 'u'}, t)
assertEqual(newArg, "-au", t)
add, remove, newArg = EvaluateSnomaskChanges(true, "+c", Masks{'a', 'u'})
assertEqual(add, Masks{'c'}, t)
assertEqual(len(remove), 0, t)
assertEqual(newArg, "+c", t)
add, remove, newArg = EvaluateSnomaskChanges(false, "", Masks{'a', 'u'})
assertEqual(len(add), 0, t)
assertEqual(remove, Masks{'a', 'u'}, t)
assertEqual(newArg, "", t)
add, remove, newArg = EvaluateSnomaskChanges(false, "*", Masks{'a', 'u'})
assertEqual(len(add), 0, t)
assertEqual(remove, Masks{'a', 'u'}, t)
assertEqual(newArg, "", t)
}

View File

@ -24,11 +24,6 @@ func (m *SnoManager) AddMasks(client *Client, masks ...sno.Mask) {
defer m.sendListMutex.Unlock() defer m.sendListMutex.Unlock()
for _, mask := range masks { for _, mask := range masks {
// confirm mask is valid
if !sno.ValidMasks[mask] {
continue
}
currentClientList := m.sendLists[mask] currentClientList := m.sendLists[mask]
if currentClientList == nil { if currentClientList == nil {
@ -101,19 +96,23 @@ func (m *SnoManager) Send(mask sno.Mask, content string) {
} }
} }
// String returns the snomasks currently enabled. // MasksEnabled returns the snomasks currently enabled.
func (m *SnoManager) String(client *Client) string { func (m *SnoManager) MasksEnabled(client *Client) (result sno.Masks) {
m.sendListMutex.RLock() m.sendListMutex.RLock()
defer m.sendListMutex.RUnlock() defer m.sendListMutex.RUnlock()
var masks string
for mask, clients := range m.sendLists { for mask, clients := range m.sendLists {
for c := range clients { for c := range clients {
if c == client { if c == client {
masks += string(mask) result = append(result, mask)
break break
} }
} }
} }
return masks return
}
func (m *SnoManager) String(client *Client) string {
masks := m.MasksEnabled(client)
return masks.String()
} }