flesh out channel modes

- deprecate 's' mode
- add user limit
- don't leak key in mode messages to non-members
- begin refactoring Mode()
This commit is contained in:
Jeremy Latt 2014-02-22 12:49:33 -08:00
parent 21337cda7f
commit c7298c55b9
3 changed files with 175 additions and 102 deletions

View File

@ -1,13 +1,18 @@
package irc package irc
import (
"strconv"
)
type Channel struct { type Channel struct {
banList []UserMask banList []UserMask
flags ChannelModeSet flags ChannelModeSet
key string key string
members MemberSet members MemberSet
name string name string
server *Server server *Server
topic string topic string
userLimit uint64
} }
func IsChannel(target string) bool { func IsChannel(target string) bool {
@ -71,11 +76,20 @@ func (channel *Channel) String() string {
} }
// <mode> <mode params> // <mode> <mode params>
func (channel *Channel) ModeString() (str string) { func (channel *Channel) ModeString(client *Client) (str string) {
if channel.key != "" { isMember := client.flags[Operator] || channel.members.Has(client)
showKey := isMember && (channel.key != "")
showUserLimit := channel.userLimit > 0
// flags with args
if showKey {
str += Key.String() str += Key.String()
} }
if showUserLimit {
str += UserLimit.String()
}
// flags
for mode := range channel.flags { for mode := range channel.flags {
str += mode.String() str += mode.String()
} }
@ -84,21 +98,40 @@ func (channel *Channel) ModeString() (str string) {
str = "+" + str str = "+" + str
} }
if channel.key != "" { // args for flags with args: The order must match above to keep
// positional arguments in place.
if showKey {
str += " " + channel.key str += " " + channel.key
} }
if showUserLimit {
str += " " + strconv.FormatUint(channel.userLimit, 10)
}
return return
} }
func (channel *Channel) IsFull() bool {
return (channel.userLimit > 0) &&
(uint64(len(channel.members)) >= channel.userLimit)
}
func (channel *Channel) CheckKey(key string) bool {
return (channel.key == "") || (channel.key == key)
}
func (channel *Channel) Join(client *Client, key string) { func (channel *Channel) Join(client *Client, key string) {
if (channel.key != "") && (channel.key != key) { if channel.members.Has(client) {
client.ErrBadChannelKey(channel) // already joined, no message?
return return
} }
if channel.members[client] != nil { if channel.IsFull() {
// already joined, no message? client.ErrChannelIsFull(channel)
return
}
if !channel.CheckKey(key) {
client.ErrBadChannelKey(channel)
return return
} }
@ -150,7 +183,7 @@ func (channel *Channel) GetTopic(client *Client) {
} }
func (channel *Channel) SetTopic(client *Client, topic string) { func (channel *Channel) SetTopic(client *Client, topic string) {
if !channel.members.Has(client) { if !(client.flags[Operator] || channel.members.Has(client)) {
client.ErrNotOnChannel(channel) client.ErrNotOnChannel(channel)
return return
} }
@ -168,8 +201,22 @@ func (channel *Channel) SetTopic(client *Client, topic string) {
} }
} }
func (channel *Channel) PrivMsg(client *Client, message string) { func (channel *Channel) CanSpeak(client *Client) bool {
if client.flags[Operator] {
return true
}
if channel.flags[NoOutside] && !channel.members.Has(client) { if channel.flags[NoOutside] && !channel.members.Has(client) {
return false
}
if channel.flags[Moderated] && !(channel.members.HasMode(client, Voice) ||
channel.members.HasMode(client, ChannelOperator)) {
return false
}
return true
}
func (channel *Channel) PrivMsg(client *Client, message string) {
if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel) client.ErrCannotSendToChan(channel)
return return
} }
@ -181,6 +228,105 @@ func (channel *Channel) PrivMsg(client *Client, message string) {
} }
} }
func (channel *Channel) applyMode(client *Client, change ChannelModeChange) bool {
switch change.mode {
case BanMask:
// TODO add/remove
for _, banMask := range channel.banList {
client.RplBanList(channel, banMask)
}
client.RplEndOfBanList(channel)
case Moderated, NoOutside, OpOnlyTopic, Private:
if !channel.ClientIsOperator(client) {
client.ErrChanOPrivIsNeeded(channel)
return false
}
switch change.op {
case Add:
channel.flags[change.mode] = true
return true
case Remove:
delete(channel.flags, change.mode)
return true
}
case Key:
if !channel.ClientIsOperator(client) {
client.ErrChanOPrivIsNeeded(channel)
return false
}
switch change.op {
case Add:
if change.arg == "" {
client.ErrNeedMoreParams("MODE")
return false
}
channel.key = change.arg
return true
case Remove:
channel.key = ""
return true
}
case UserLimit:
limit, err := strconv.ParseUint(change.arg, 10, 64)
if err != nil {
client.ErrNeedMoreParams("MODE")
return false
}
if limit == 0 {
return false
}
channel.userLimit = limit
return true
case ChannelOperator, Voice:
if !channel.ClientIsOperator(client) {
client.ErrChanOPrivIsNeeded(channel)
return false
}
if change.arg == "" {
client.ErrNeedMoreParams("MODE")
return false
}
target := channel.server.clients.Get(change.arg)
if target == nil {
client.ErrNoSuchNick(change.arg)
return false
}
if !channel.members.Has(target) {
client.ErrUserNotInChannel(channel, target)
return false
}
switch change.op {
case Add:
channel.members[target][change.mode] = true
return true
case Remove:
channel.members[target][change.mode] = false
return true
}
default:
client.ErrUnknownMode(change.mode, channel)
return false
}
return false
}
func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) { func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) {
if len(changes) == 0 { if len(changes) == 0 {
client.RplChannelModeIs(channel) client.RplChannelModeIs(channel)
@ -188,100 +334,22 @@ func (channel *Channel) Mode(client *Client, changes ChannelModeChanges) {
} }
applied := make(ChannelModeChanges, 0) applied := make(ChannelModeChanges, 0)
for _, change := range changes { for _, change := range changes {
switch change.mode { if channel.applyMode(client, change) {
case BanMask: applied = append(applied, change)
// TODO add/remove
for _, banMask := range channel.banList {
client.RplBanList(channel, banMask)
}
client.RplEndOfBanList(channel)
case NoOutside, Private, Secret, OpOnlyTopic:
if !channel.ClientIsOperator(client) {
client.ErrChanOPrivIsNeeded(channel)
continue
}
switch change.op {
case Add:
channel.flags[change.mode] = true
applied = append(applied, change)
case Remove:
delete(channel.flags, change.mode)
applied = append(applied, change)
}
case Key:
if !channel.ClientIsOperator(client) {
client.ErrChanOPrivIsNeeded(channel)
continue
}
switch change.op {
case Add:
if change.arg == "" {
client.ErrNeedMoreParams("MODE")
continue
}
channel.key = change.arg
applied = append(applied, change)
case Remove:
channel.key = ""
applied = append(applied, change)
}
case ChannelOperator, Voice:
if !channel.ClientIsOperator(client) {
client.ErrChanOPrivIsNeeded(channel)
continue
}
if change.arg == "" {
client.ErrNeedMoreParams("MODE")
continue
}
target := channel.server.clients.Get(change.arg)
if target == nil {
client.ErrNoSuchNick(change.arg)
continue
}
if !channel.members.Has(target) {
client.ErrUserNotInChannel(channel, target)
continue
}
switch change.op {
case Add:
channel.members[target][change.mode] = true
applied = append(applied, change)
case Remove:
channel.members[target][change.mode] = false
applied = append(applied, change)
}
default:
client.ErrUnknownMode(change.mode, channel)
} }
} }
if len(applied) > 0 { if len(applied) > 0 {
reply := RplChannelMode(client, channel, applied)
for member := range channel.members { for member := range channel.members {
member.Reply(RplChannelMode(client, channel, applied)) member.Reply(reply)
} }
} }
} }
func (channel *Channel) Notice(client *Client, message string) { func (channel *Channel) Notice(client *Client, message string) {
if channel.flags[NoOutside] && !channel.members.Has(client) { if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel) client.ErrCannotSendToChan(channel)
return return
} }
@ -299,7 +367,7 @@ func (channel *Channel) Quit(client *Client) {
} }
func (channel *Channel) Kick(client *Client, target *Client, comment string) { func (channel *Channel) Kick(client *Client, target *Client, comment string) {
if !client.flags[Operator] && !channel.members.Has(client) { if !(client.flags[Operator] || channel.members.Has(client)) {
client.ErrNotOnChannel(channel) client.ErrNotOnChannel(channel)
return return
} }

View File

@ -222,7 +222,7 @@ const (
Private ChannelMode = 'p' // flag Private ChannelMode = 'p' // flag
Quiet ChannelMode = 'q' // flag Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag Secret ChannelMode = 's' // flag, deprecated
UserLimit ChannelMode = 'l' // flag arg UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg Voice ChannelMode = 'v' // arg
) )

View File

@ -215,7 +215,7 @@ func (target *Client) RplEndOfWhois() {
func (target *Client) RplChannelModeIs(channel *Channel) { func (target *Client) RplChannelModeIs(channel *Channel) {
target.NumericReply(RPL_CHANNELMODEIS, target.NumericReply(RPL_CHANNELMODEIS,
"%s %s", channel, channel.ModeString()) "%s %s", channel, channel.ModeString(target))
} }
// <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ] // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
@ -421,3 +421,8 @@ func (target *Client) ErrUnknownMode(mode ChannelMode, channel *Channel) {
target.NumericReply(ERR_UNKNOWNMODE, target.NumericReply(ERR_UNKNOWNMODE,
"%s :is unknown mode char to me for %s", mode, channel) "%s :is unknown mode char to me for %s", mode, channel)
} }
func (target *Client) ErrChannelIsFull(channel *Channel) {
target.NumericReply(ERR_CHANNELISFULL,
"%s :Cannot join channel (+l)", channel)
}