3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-10 22:19:31 +01:00

Merge pull request #21 from jlatt/safer-unicoding

normalize unicode safely
This commit is contained in:
Jeremy Latt 2014-03-13 12:20:35 -07:00
commit e9fb5979a6
12 changed files with 324 additions and 269 deletions

View File

@ -3,27 +3,22 @@ package irc
import ( import (
"log" "log"
"strconv" "strconv"
"strings"
) )
type Channel struct { type Channel struct {
flags ChannelModeSet flags ChannelModeSet
lists map[ChannelMode]*UserMaskSet lists map[ChannelMode]*UserMaskSet
key string key Text
members MemberSet members MemberSet
name string name Name
server *Server server *Server
topic string topic Text
userLimit uint64 userLimit uint64
} }
func IsChannel(target string) bool {
return ChannelNameExpr.MatchString(target)
}
// NewChannel creates a new channel from a `Server` and a `name` // NewChannel creates a new channel from a `Server` and a `name`
// string, which must be unique on the server. // string, which must be unique on the server.
func NewChannel(s *Server, name string) *Channel { func NewChannel(s *Server, name Name) *Channel {
channel := &Channel{ channel := &Channel{
flags: make(ChannelModeSet), flags: make(ChannelModeSet),
lists: map[ChannelMode]*UserMaskSet{ lists: map[ChannelMode]*UserMaskSet{
@ -32,7 +27,7 @@ func NewChannel(s *Server, name string) *Channel {
InviteMask: NewUserMaskSet(), InviteMask: NewUserMaskSet(),
}, },
members: make(MemberSet), members: make(MemberSet),
name: strings.ToLower(name), name: name,
server: s, server: s,
} }
@ -73,22 +68,22 @@ func (channel *Channel) Nicks(target *Client) []string {
nicks[i] += "+" nicks[i] += "+"
} }
} }
nicks[i] += client.Nick() nicks[i] += client.Nick().String()
i += 1 i += 1
} }
return nicks return nicks
} }
func (channel *Channel) Id() string { func (channel *Channel) Id() Name {
return channel.name return channel.name
} }
func (channel *Channel) Nick() string { func (channel *Channel) Nick() Name {
return channel.name return channel.name
} }
func (channel *Channel) String() string { func (channel *Channel) String() string {
return channel.Id() return channel.Id().String()
} }
// <mode> <mode params> // <mode> <mode params>
@ -117,7 +112,7 @@ func (channel *Channel) ModeString(client *Client) (str string) {
// args for flags with args: The order must match above to keep // args for flags with args: The order must match above to keep
// positional arguments in place. // positional arguments in place.
if showKey { if showKey {
str += " " + channel.key str += " " + channel.key.String()
} }
if showUserLimit { if showUserLimit {
str += " " + strconv.FormatUint(channel.userLimit, 10) str += " " + strconv.FormatUint(channel.userLimit, 10)
@ -131,11 +126,11 @@ func (channel *Channel) IsFull() bool {
(uint64(len(channel.members)) >= channel.userLimit) (uint64(len(channel.members)) >= channel.userLimit)
} }
func (channel *Channel) CheckKey(key string) bool { func (channel *Channel) CheckKey(key Text) bool {
return (channel.key == "") || (channel.key == key) return (channel.key == "") || (channel.key == key)
} }
func (channel *Channel) Join(client *Client, key string) { func (channel *Channel) Join(client *Client, key Text) {
if channel.members.Has(client) { if channel.members.Has(client) {
// already joined, no message? // already joined, no message?
return return
@ -179,7 +174,7 @@ func (channel *Channel) Join(client *Client, key string) {
channel.Names(client) channel.Names(client)
} }
func (channel *Channel) Part(client *Client, message string) { func (channel *Channel) Part(client *Client, message Text) {
if !channel.members.Has(client) { if !channel.members.Has(client) {
client.ErrNotOnChannel(channel) client.ErrNotOnChannel(channel)
return return
@ -207,7 +202,7 @@ func (channel *Channel) GetTopic(client *Client) {
client.RplTopic(channel) client.RplTopic(channel)
} }
func (channel *Channel) SetTopic(client *Client, topic string) { func (channel *Channel) SetTopic(client *Client, topic Text) {
if !(client.flags[Operator] || channel.members.Has(client)) { if !(client.flags[Operator] || channel.members.Has(client)) {
client.ErrNotOnChannel(channel) client.ErrNotOnChannel(channel)
return return
@ -244,7 +239,7 @@ func (channel *Channel) CanSpeak(client *Client) bool {
return true return true
} }
func (channel *Channel) PrivMsg(client *Client, message string) { func (channel *Channel) PrivMsg(client *Client, message Text) {
if !channel.CanSpeak(client) { if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel) client.ErrCannotSendToChan(channel)
return return
@ -283,7 +278,7 @@ func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode,
} }
func (channel *Channel) applyModeMember(client *Client, mode ChannelMode, func (channel *Channel) applyModeMember(client *Client, mode ChannelMode,
op ModeOp, nick string) bool { op ModeOp, nick Name) bool {
if !channel.ClientIsOperator(client) { if !channel.ClientIsOperator(client) {
client.ErrChanOPrivIsNeeded(channel) client.ErrChanOPrivIsNeeded(channel)
return false return false
@ -331,7 +326,7 @@ func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) {
} }
func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp,
mask string) bool { mask Name) bool {
list := channel.lists[mode] list := channel.lists[mode]
if list == nil { if list == nil {
// This should never happen, but better safe than panicky. // This should never happen, but better safe than panicky.
@ -362,7 +357,8 @@ func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeO
func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) bool { func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) bool {
switch change.mode { switch change.mode {
case BanMask, ExceptMask, InviteMask: case BanMask, ExceptMask, InviteMask:
return channel.applyModeMask(client, change.mode, change.op, change.arg) return channel.applyModeMask(client, change.mode, change.op,
NewName(change.arg))
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Persistent, Private: case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Persistent, Private:
return channel.applyModeFlag(client, change.mode, change.op) return channel.applyModeFlag(client, change.mode, change.op)
@ -379,11 +375,12 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
client.ErrNeedMoreParams("MODE") client.ErrNeedMoreParams("MODE")
return false return false
} }
if change.arg == channel.key { key := NewText(change.arg)
if key == channel.key {
return false return false
} }
channel.key = change.arg channel.key = key
return true return true
case Remove: case Remove:
@ -405,7 +402,8 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
return true return true
case ChannelOperator, Voice: case ChannelOperator, Voice:
return channel.applyModeMember(client, change.mode, change.op, change.arg) return channel.applyModeMember(client, change.mode, change.op,
NewName(change.arg))
default: default:
client.ErrUnknownMode(change.mode, channel) client.ErrUnknownMode(change.mode, channel)
@ -456,7 +454,7 @@ func (channel *Channel) Persist() (err error) {
return return
} }
func (channel *Channel) Notice(client *Client, message string) { func (channel *Channel) Notice(client *Client, message Text) {
if !channel.CanSpeak(client) { if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel) client.ErrCannotSendToChan(channel)
return return
@ -478,7 +476,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 Text) {
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

@ -7,14 +7,10 @@ import (
"time" "time"
) )
func IsNickname(nick string) bool {
return NicknameExpr.MatchString(nick)
}
type Client struct { type Client struct {
atime time.Time atime time.Time
authorized bool authorized bool
awayMessage string awayMessage Text
capabilities CapabilitySet capabilities CapabilitySet
capState CapState capState CapState
channels ChannelSet channels ChannelSet
@ -23,16 +19,16 @@ type Client struct {
flags map[UserMode]bool flags map[UserMode]bool
hasQuit bool hasQuit bool
hops uint hops uint
hostname string hostname Name
idleTimer *time.Timer idleTimer *time.Timer
loginTimer *time.Timer loginTimer *time.Timer
nick string nick Name
phase Phase phase Phase
quitTimer *time.Timer quitTimer *time.Timer
realname string realname Text
server *Server server *Server
socket *Socket socket *Socket
username string username Name
} }
func NewClient(server *Server, conn net.Conn) *Client { func NewClient(server *Server, conn net.Conn) *Client {
@ -186,27 +182,27 @@ func (c *Client) ModeString() (str string) {
return return
} }
func (c *Client) UserHost() string { func (c *Client) UserHost() Name {
username := "*" username := "*"
if c.HasUsername() { if c.HasUsername() {
username = c.username username = c.username.String()
} }
return fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname) return Name(fmt.Sprintf("%s!%s@%s", c.Nick(), username, c.hostname))
} }
func (c *Client) Nick() string { func (c *Client) Nick() Name {
if c.HasNick() { if c.HasNick() {
return c.nick return c.nick
} }
return "*" return Name("*")
} }
func (c *Client) Id() string { func (c *Client) Id() Name {
return c.UserHost() return c.UserHost()
} }
func (c *Client) String() string { func (c *Client) String() string {
return c.Id() return c.Id().String()
} }
func (client *Client) Friends() ClientSet { func (client *Client) Friends() ClientSet {
@ -220,12 +216,12 @@ func (client *Client) Friends() ClientSet {
return friends return friends
} }
func (client *Client) SetNickname(nickname string) { func (client *Client) SetNickname(nickname Name) {
client.nick = nickname client.nick = nickname
client.server.clients.Add(client) client.server.clients.Add(client)
} }
func (client *Client) ChangeNickname(nickname string) { func (client *Client) ChangeNickname(nickname Name) {
// Make reply before changing nick to capture original source id. // Make reply before changing nick to capture original source id.
reply := RplNick(client, nickname) reply := RplNick(client, nickname)
client.server.clients.Remove(client) client.server.clients.Remove(client)
@ -244,7 +240,7 @@ func (client *Client) Reply(reply string, args ...interface{}) {
client.socket.Write(reply) client.socket.Write(reply)
} }
func (client *Client) Quit(message string) { func (client *Client) Quit(message Text) {
if client.hasQuit { if client.hasQuit {
return return
} }

View File

@ -25,36 +25,36 @@ func HasWildcards(mask string) bool {
return wildMaskExpr.MatchString(mask) return wildMaskExpr.MatchString(mask)
} }
func ExpandUserHost(userhost string) (expanded string) { func ExpandUserHost(userhost Name) (expanded Name) {
expanded = userhost expanded = userhost
// fill in missing wildcards for nicks // fill in missing wildcards for nicks
if !strings.Contains(expanded, "!") { if !strings.Contains(expanded.String(), "!") {
expanded += "!*" expanded += "!*"
} }
if !strings.Contains(expanded, "@") { if !strings.Contains(expanded.String(), "@") {
expanded += "@*" expanded += "@*"
} }
return return
} }
func QuoteLike(userhost string) string { func QuoteLike(userhost Name) Name {
return likeQuoter.Replace(userhost) return Name(likeQuoter.Replace(userhost.String()))
} }
type ClientLookupSet struct { type ClientLookupSet struct {
byNick map[string]*Client byNick map[Name]*Client
db *ClientDB db *ClientDB
} }
func NewClientLookupSet() *ClientLookupSet { func NewClientLookupSet() *ClientLookupSet {
return &ClientLookupSet{ return &ClientLookupSet{
byNick: make(map[string]*Client), byNick: make(map[Name]*Client),
db: NewClientDB(), db: NewClientDB(),
} }
} }
func (clients *ClientLookupSet) Get(nick string) *Client { func (clients *ClientLookupSet) Get(nick Name) *Client {
return clients.byNick[strings.ToLower(nick)] return clients.byNick[nick.ToLower()]
} }
func (clients *ClientLookupSet) Add(client *Client) error { func (clients *ClientLookupSet) Add(client *Client) error {
@ -64,7 +64,7 @@ func (clients *ClientLookupSet) Add(client *Client) error {
if clients.Get(client.nick) != nil { if clients.Get(client.nick) != nil {
return ErrNicknameInUse return ErrNicknameInUse
} }
clients.byNick[strings.ToLower(client.nick)] = client clients.byNick[client.Nick().ToLower()] = client
clients.db.Add(client) clients.db.Add(client)
return nil return nil
} }
@ -76,12 +76,12 @@ func (clients *ClientLookupSet) Remove(client *Client) error {
if clients.Get(client.nick) != client { if clients.Get(client.nick) != client {
return ErrNicknameMismatch return ErrNicknameMismatch
} }
delete(clients.byNick, strings.ToLower(client.nick)) delete(clients.byNick, client.nick.ToLower())
clients.db.Remove(client) clients.db.Remove(client)
return nil return nil
} }
func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) { func (clients *ClientLookupSet) FindAll(userhost Name) (set ClientSet) {
userhost = ExpandUserHost(userhost) userhost = ExpandUserHost(userhost)
set = make(ClientSet) set = make(ClientSet)
rows, err := clients.db.db.Query( rows, err := clients.db.db.Query(
@ -94,7 +94,7 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
return return
} }
for rows.Next() { for rows.Next() {
var nickname string var nickname Name
err := rows.Scan(&nickname) err := rows.Scan(&nickname)
if err != nil { if err != nil {
if DEBUG_SERVER { if DEBUG_SERVER {
@ -114,12 +114,12 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
return return
} }
func (clients *ClientLookupSet) Find(userhost string) *Client { func (clients *ClientLookupSet) Find(userhost Name) *Client {
userhost = ExpandUserHost(userhost) userhost = ExpandUserHost(userhost)
row := clients.db.db.QueryRow( row := clients.db.db.QueryRow(
`SELECT nickname FROM client WHERE userhost LIKE ? ESCAPE '\' LIMIT 1`, `SELECT nickname FROM client WHERE userhost LIKE ? ESCAPE '\' LIMIT 1`,
QuoteLike(userhost)) QuoteLike(userhost))
var nickname string var nickname Name
err := row.Scan(&nickname) err := row.Scan(&nickname)
if err != nil { if err != nil {
if DEBUG_SERVER { if DEBUG_SERVER {
@ -184,17 +184,17 @@ func (db *ClientDB) Remove(client *Client) {
// //
type UserMaskSet struct { type UserMaskSet struct {
masks map[string]bool masks map[Name]bool
regexp *regexp.Regexp regexp *regexp.Regexp
} }
func NewUserMaskSet() *UserMaskSet { func NewUserMaskSet() *UserMaskSet {
return &UserMaskSet{ return &UserMaskSet{
masks: make(map[string]bool), masks: make(map[Name]bool),
} }
} }
func (set *UserMaskSet) Add(mask string) bool { func (set *UserMaskSet) Add(mask Name) bool {
if set.masks[mask] { if set.masks[mask] {
return false return false
} }
@ -203,7 +203,7 @@ func (set *UserMaskSet) Add(mask string) bool {
return true return true
} }
func (set *UserMaskSet) AddAll(masks []string) (added bool) { func (set *UserMaskSet) AddAll(masks []Name) (added bool) {
for _, mask := range masks { for _, mask := range masks {
if !added && !set.masks[mask] { if !added && !set.masks[mask] {
added = true added = true
@ -214,7 +214,7 @@ func (set *UserMaskSet) AddAll(masks []string) (added bool) {
return return
} }
func (set *UserMaskSet) Remove(mask string) bool { func (set *UserMaskSet) Remove(mask Name) bool {
if !set.masks[mask] { if !set.masks[mask] {
return false return false
} }
@ -223,18 +223,18 @@ func (set *UserMaskSet) Remove(mask string) bool {
return true return true
} }
func (set *UserMaskSet) Match(userhost string) bool { func (set *UserMaskSet) Match(userhost Name) bool {
if set.regexp == nil { if set.regexp == nil {
return false return false
} }
return set.regexp.MatchString(userhost) return set.regexp.MatchString(userhost.String())
} }
func (set *UserMaskSet) String() string { func (set *UserMaskSet) String() string {
masks := make([]string, len(set.masks)) masks := make([]string, len(set.masks))
index := 0 index := 0
for mask := range set.masks { for mask := range set.masks {
masks[index] = mask masks[index] = mask.String()
index += 1 index += 1
} }
return strings.Join(masks, " ") return strings.Join(masks, " ")
@ -255,7 +255,7 @@ func (set *UserMaskSet) setRegexp() {
maskExprs := make([]string, len(set.masks)) maskExprs := make([]string, len(set.masks))
index := 0 index := 0
for mask := range set.masks { for mask := range set.masks {
manyParts := strings.Split(mask, "*") manyParts := strings.Split(mask.String(), "*")
manyExprs := make([]string, len(manyParts)) manyExprs := make([]string, len(manyParts))
for mindex, manyPart := range manyParts { for mindex, manyPart := range manyParts {
oneParts := strings.Split(manyPart, "?") oneParts := strings.Split(manyPart, "?")

View File

@ -1,7 +1,6 @@
package irc package irc
import ( import (
"code.google.com/p/go.text/unicode/norm"
"errors" "errors"
"fmt" "fmt"
"regexp" "regexp"
@ -114,14 +113,14 @@ func ParseLine(line string) (command StringCode, args []string) {
_, line = splitArg(line) _, line = splitArg(line)
} }
arg, line := splitArg(line) arg, line := splitArg(line)
command = StringCode(strings.ToUpper(arg)) command = StringCode(NewName(strings.ToUpper(arg)))
for len(line) > 0 { for len(line) > 0 {
if strings.HasPrefix(line, ":") { if strings.HasPrefix(line, ":") {
args = append(args, norm.NFC.String(line[len(":"):])) args = append(args, line[len(":"):])
break break
} }
arg, line = splitArg(line) arg, line = splitArg(line)
args = append(args, norm.NFKC.String(arg)) args = append(args, arg)
} }
return return
} }
@ -147,8 +146,8 @@ func NewUnknownCommand(args []string) *UnknownCommand {
type PingCommand struct { type PingCommand struct {
BaseCommand BaseCommand
server string server Name
server2 string server2 Name
} }
func (cmd *PingCommand) String() string { func (cmd *PingCommand) String() string {
@ -160,10 +159,10 @@ func NewPingCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
msg := &PingCommand{ msg := &PingCommand{
server: args[0], server: NewName(args[0]),
} }
if len(args) > 1 { if len(args) > 1 {
msg.server2 = args[1] msg.server2 = NewName(args[1])
} }
return msg, nil return msg, nil
} }
@ -172,8 +171,8 @@ func NewPingCommand(args []string) (editableCommand, error) {
type PongCommand struct { type PongCommand struct {
BaseCommand BaseCommand
server1 string server1 Name
server2 string server2 Name
} }
func (cmd *PongCommand) String() string { func (cmd *PongCommand) String() string {
@ -185,10 +184,10 @@ func NewPongCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
message := &PongCommand{ message := &PongCommand{
server1: args[0], server1: NewName(args[0]),
} }
if len(args) > 1 { if len(args) > 1 {
message.server2 = args[1] message.server2 = NewName(args[1])
} }
return message, nil return message, nil
} }
@ -230,7 +229,7 @@ func NewPassCommand(args []string) (editableCommand, error) {
type NickCommand struct { type NickCommand struct {
BaseCommand BaseCommand
nickname string nickname Name
} }
func (m *NickCommand) String() string { func (m *NickCommand) String() string {
@ -242,21 +241,21 @@ func NewNickCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &NickCommand{ return &NickCommand{
nickname: args[0], nickname: NewName(args[0]),
}, nil }, nil
} }
type UserCommand struct { type UserCommand struct {
BaseCommand BaseCommand
username string username Name
realname string realname Text
} }
// USER <username> <hostname> <servername> <realname> // USER <username> <hostname> <servername> <realname>
type RFC1459UserCommand struct { type RFC1459UserCommand struct {
UserCommand UserCommand
hostname string hostname Name
servername string servername Name
} }
func (cmd *RFC1459UserCommand) String() string { func (cmd *RFC1459UserCommand) String() string {
@ -297,17 +296,17 @@ func NewUserCommand(args []string) (editableCommand, error) {
mode: uint8(mode), mode: uint8(mode),
unused: args[2], unused: args[2],
} }
msg.username = args[0] msg.username = NewName(args[0])
msg.realname = args[3] msg.realname = NewText(args[3])
return msg, nil return msg, nil
} }
msg := &RFC1459UserCommand{ msg := &RFC1459UserCommand{
hostname: args[1], hostname: NewName(args[1]),
servername: args[2], servername: NewName(args[2]),
} }
msg.username = args[0] msg.username = NewName(args[0])
msg.realname = args[3] msg.realname = NewText(args[3])
return msg, nil return msg, nil
} }
@ -315,7 +314,7 @@ func NewUserCommand(args []string) (editableCommand, error) {
type QuitCommand struct { type QuitCommand struct {
BaseCommand BaseCommand
message string message Text
} }
func (cmd *QuitCommand) String() string { func (cmd *QuitCommand) String() string {
@ -325,7 +324,7 @@ func (cmd *QuitCommand) String() string {
func NewQuitCommand(args []string) (editableCommand, error) { func NewQuitCommand(args []string) (editableCommand, error) {
msg := &QuitCommand{} msg := &QuitCommand{}
if len(args) > 0 { if len(args) > 0 {
msg.message = args[0] msg.message = NewText(args[0])
} }
return msg, nil return msg, nil
} }
@ -334,7 +333,7 @@ func NewQuitCommand(args []string) (editableCommand, error) {
type JoinCommand struct { type JoinCommand struct {
BaseCommand BaseCommand
channels map[string]string channels map[Name]Text
zero bool zero bool
} }
@ -344,7 +343,7 @@ func (cmd *JoinCommand) String() string {
func NewJoinCommand(args []string) (editableCommand, error) { func NewJoinCommand(args []string) (editableCommand, error) {
msg := &JoinCommand{ msg := &JoinCommand{
channels: make(map[string]string), channels: make(map[Name]Text),
} }
if len(args) == 0 { if len(args) == 0 {
@ -364,7 +363,7 @@ func NewJoinCommand(args []string) (editableCommand, error) {
} }
} }
for i, channel := range channels { for i, channel := range channels {
msg.channels[channel] = keys[i] msg.channels[NewName(channel)] = NewText(keys[i])
} }
return msg, nil return msg, nil
@ -374,13 +373,13 @@ func NewJoinCommand(args []string) (editableCommand, error) {
type PartCommand struct { type PartCommand struct {
BaseCommand BaseCommand
channels []string channels []Name
message string message Text
} }
func (cmd *PartCommand) Message() string { func (cmd *PartCommand) Message() Text {
if cmd.message == "" { if cmd.message == "" {
return cmd.Client().Nick() return cmd.Client().Nick().Text()
} }
return cmd.message return cmd.message
} }
@ -394,10 +393,10 @@ func NewPartCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
msg := &PartCommand{ msg := &PartCommand{
channels: strings.Split(args[0], ","), channels: NewNames(strings.Split(args[0], ",")),
} }
if len(args) > 1 { if len(args) > 1 {
msg.message = args[1] msg.message = NewText(args[1])
} }
return msg, nil return msg, nil
} }
@ -406,8 +405,8 @@ func NewPartCommand(args []string) (editableCommand, error) {
type PrivMsgCommand struct { type PrivMsgCommand struct {
BaseCommand BaseCommand
target string target Name
message string message Text
} }
func (cmd *PrivMsgCommand) String() string { func (cmd *PrivMsgCommand) String() string {
@ -419,8 +418,8 @@ func NewPrivMsgCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &PrivMsgCommand{ return &PrivMsgCommand{
target: args[0], target: NewName(args[0]),
message: args[1], message: NewText(args[1]),
}, nil }, nil
} }
@ -428,9 +427,9 @@ func NewPrivMsgCommand(args []string) (editableCommand, error) {
type TopicCommand struct { type TopicCommand struct {
BaseCommand BaseCommand
channel string channel Name
setTopic bool setTopic bool
topic string topic Text
} }
func (cmd *TopicCommand) String() string { func (cmd *TopicCommand) String() string {
@ -442,11 +441,11 @@ func NewTopicCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
msg := &TopicCommand{ msg := &TopicCommand{
channel: args[0], channel: NewName(args[0]),
} }
if len(args) > 1 { if len(args) > 1 {
msg.setTopic = true msg.setTopic = true
msg.topic = args[1] msg.topic = NewText(args[1])
} }
return msg, nil return msg, nil
} }
@ -482,18 +481,18 @@ func (changes ModeChanges) String() string {
type ModeCommand struct { type ModeCommand struct {
BaseCommand BaseCommand
nickname string nickname Name
changes ModeChanges changes ModeChanges
} }
// MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) ) // MODE <nickname> *( ( "+" / "-" ) *( "i" / "w" / "o" / "O" / "r" ) )
func NewUserModeCommand(args []string) (editableCommand, error) { func NewUserModeCommand(nickname Name, args []string) (editableCommand, error) {
cmd := &ModeCommand{ cmd := &ModeCommand{
nickname: args[0], nickname: nickname,
changes: make(ModeChanges, 0), changes: make(ModeChanges, 0),
} }
for _, modeChange := range args[1:] { for _, modeChange := range args {
if len(modeChange) == 0 { if len(modeChange) == 0 {
continue continue
} }
@ -559,17 +558,16 @@ func (changes ChannelModeChanges) String() (str string) {
type ChannelModeCommand struct { type ChannelModeCommand struct {
BaseCommand BaseCommand
channel string channel Name
changes ChannelModeChanges changes ChannelModeChanges
} }
// MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) // MODE <channel> *( ( "-" / "+" ) *<modes> *<modeparams> )
func NewChannelModeCommand(args []string) (editableCommand, error) { func NewChannelModeCommand(channel Name, args []string) (editableCommand, error) {
cmd := &ChannelModeCommand{ cmd := &ChannelModeCommand{
channel: args[0], channel: channel,
changes: make(ChannelModeChanges, 0), changes: make(ChannelModeChanges, 0),
} }
args = args[1:]
for len(args) > 0 { for len(args) > 0 {
if len(args[0]) == 0 { if len(args[0]) == 0 {
@ -616,17 +614,18 @@ func NewModeCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
if IsChannel(args[0]) { name := NewName(args[0])
return NewChannelModeCommand(args) if name.IsChannel() {
return NewChannelModeCommand(name, args[1:])
} else { } else {
return NewUserModeCommand(args) return NewUserModeCommand(name, args[1:])
} }
} }
type WhoisCommand struct { type WhoisCommand struct {
BaseCommand BaseCommand
target string target Name
masks []string masks []Name
} }
// WHOIS [ <target> ] <mask> *( "," <mask> ) // WHOIS [ <target> ] <mask> *( "," <mask> )
@ -646,8 +645,8 @@ func NewWhoisCommand(args []string) (editableCommand, error) {
} }
return &WhoisCommand{ return &WhoisCommand{
target: target, target: NewName(target),
masks: strings.Split(masks, ","), masks: NewNames(strings.Split(masks, ",")),
}, nil }, nil
} }
@ -657,7 +656,7 @@ func (msg *WhoisCommand) String() string {
type WhoCommand struct { type WhoCommand struct {
BaseCommand BaseCommand
mask string mask Name
operatorOnly bool operatorOnly bool
} }
@ -666,7 +665,7 @@ func NewWhoCommand(args []string) (editableCommand, error) {
cmd := &WhoCommand{} cmd := &WhoCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.mask = args[0] cmd.mask = NewName(args[0])
} }
if (len(args) > 1) && (args[1] == "o") { if (len(args) > 1) && (args[1] == "o") {
@ -682,7 +681,7 @@ func (msg *WhoCommand) String() string {
type OperCommand struct { type OperCommand struct {
PassCommand PassCommand
name string name Name
} }
func (msg *OperCommand) String() string { func (msg *OperCommand) String() string {
@ -700,7 +699,7 @@ func NewOperCommand(args []string) (editableCommand, error) {
} }
cmd := &OperCommand{ cmd := &OperCommand{
name: args[0], name: NewName(args[0]),
} }
cmd.password = []byte(args[1]) cmd.password = []byte(args[1])
return cmd, nil return cmd, nil
@ -739,12 +738,12 @@ func NewCapCommand(args []string) (editableCommand, error) {
// HAPROXY support // HAPROXY support
type ProxyCommand struct { type ProxyCommand struct {
BaseCommand BaseCommand
net string net Name
sourceIP string sourceIP Name
destIP string destIP Name
sourcePort string sourcePort Name
destPort string destPort Name
hostname string // looked up in socket thread hostname Name // looked up in socket thread
} }
func (msg *ProxyCommand) String() string { func (msg *ProxyCommand) String() string {
@ -756,18 +755,18 @@ func NewProxyCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &ProxyCommand{ return &ProxyCommand{
net: args[0], net: NewName(args[0]),
sourceIP: args[1], sourceIP: NewName(args[1]),
destIP: args[2], destIP: NewName(args[2]),
sourcePort: args[3], sourcePort: NewName(args[3]),
destPort: args[4], destPort: NewName(args[4]),
hostname: LookupHostname(args[1]), hostname: LookupHostname(NewName(args[1])),
}, nil }, nil
} }
type AwayCommand struct { type AwayCommand struct {
BaseCommand BaseCommand
text string text Text
away bool away bool
} }
@ -779,7 +778,7 @@ func NewAwayCommand(args []string) (editableCommand, error) {
cmd := &AwayCommand{} cmd := &AwayCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.text = args[0] cmd.text = NewText(args[0])
cmd.away = true cmd.away = true
} }
@ -788,7 +787,7 @@ func NewAwayCommand(args []string) (editableCommand, error) {
type IsOnCommand struct { type IsOnCommand struct {
BaseCommand BaseCommand
nicks []string nicks []Name
} }
func (msg *IsOnCommand) String() string { func (msg *IsOnCommand) String() string {
@ -801,27 +800,27 @@ func NewIsOnCommand(args []string) (editableCommand, error) {
} }
return &IsOnCommand{ return &IsOnCommand{
nicks: args, nicks: NewNames(args),
}, nil }, nil
} }
type MOTDCommand struct { type MOTDCommand struct {
BaseCommand BaseCommand
target string target Name
} }
func NewMOTDCommand(args []string) (editableCommand, error) { func NewMOTDCommand(args []string) (editableCommand, error) {
cmd := &MOTDCommand{} cmd := &MOTDCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.target = args[0] cmd.target = NewName(args[0])
} }
return cmd, nil return cmd, nil
} }
type NoticeCommand struct { type NoticeCommand struct {
BaseCommand BaseCommand
target string target Name
message string message Text
} }
func (cmd *NoticeCommand) String() string { func (cmd *NoticeCommand) String() string {
@ -833,20 +832,20 @@ func NewNoticeCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &NoticeCommand{ return &NoticeCommand{
target: args[0], target: NewName(args[0]),
message: args[1], message: NewText(args[1]),
}, nil }, nil
} }
type KickCommand struct { type KickCommand struct {
BaseCommand BaseCommand
kicks map[string]string kicks map[Name]Name
comment string comment Text
} }
func (msg *KickCommand) Comment() string { func (msg *KickCommand) Comment() Text {
if msg.comment == "" { if msg.comment == "" {
return msg.Client().Nick() return msg.Client().Nick().Text()
} }
return msg.comment return msg.comment
} }
@ -855,13 +854,13 @@ func NewKickCommand(args []string) (editableCommand, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
channels := strings.Split(args[0], ",") channels := NewNames(strings.Split(args[0], ","))
users := strings.Split(args[1], ",") users := NewNames(strings.Split(args[1], ","))
if (len(channels) != len(users)) && (len(users) != 1) { if (len(channels) != len(users)) && (len(users) != 1) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
cmd := &KickCommand{ cmd := &KickCommand{
kicks: make(map[string]string), kicks: make(map[Name]Name),
} }
for index, channel := range channels { for index, channel := range channels {
if len(users) == 1 { if len(users) == 1 {
@ -871,48 +870,48 @@ func NewKickCommand(args []string) (editableCommand, error) {
} }
} }
if len(args) > 2 { if len(args) > 2 {
cmd.comment = args[2] cmd.comment = NewText(args[2])
} }
return cmd, nil return cmd, nil
} }
type ListCommand struct { type ListCommand struct {
BaseCommand BaseCommand
channels []string channels []Name
target string target Name
} }
func NewListCommand(args []string) (editableCommand, error) { func NewListCommand(args []string) (editableCommand, error) {
cmd := &ListCommand{} cmd := &ListCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.channels = strings.Split(args[0], ",") cmd.channels = NewNames(strings.Split(args[0], ","))
} }
if len(args) > 1 { if len(args) > 1 {
cmd.target = args[1] cmd.target = NewName(args[1])
} }
return cmd, nil return cmd, nil
} }
type NamesCommand struct { type NamesCommand struct {
BaseCommand BaseCommand
channels []string channels []Name
target string target Name
} }
func NewNamesCommand(args []string) (editableCommand, error) { func NewNamesCommand(args []string) (editableCommand, error) {
cmd := &NamesCommand{} cmd := &NamesCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.channels = strings.Split(args[0], ",") cmd.channels = NewNames(strings.Split(args[0], ","))
} }
if len(args) > 1 { if len(args) > 1 {
cmd.target = args[1] cmd.target = NewName(args[1])
} }
return cmd, nil return cmd, nil
} }
type DebugCommand struct { type DebugCommand struct {
BaseCommand BaseCommand
subCommand string subCommand Name
} }
func NewDebugCommand(args []string) (editableCommand, error) { func NewDebugCommand(args []string) (editableCommand, error) {
@ -921,27 +920,27 @@ func NewDebugCommand(args []string) (editableCommand, error) {
} }
return &DebugCommand{ return &DebugCommand{
subCommand: strings.ToUpper(args[0]), subCommand: NewName(strings.ToUpper(args[0])),
}, nil }, nil
} }
type VersionCommand struct { type VersionCommand struct {
BaseCommand BaseCommand
target string target Name
} }
func NewVersionCommand(args []string) (editableCommand, error) { func NewVersionCommand(args []string) (editableCommand, error) {
cmd := &VersionCommand{} cmd := &VersionCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.target = args[0] cmd.target = NewName(args[0])
} }
return cmd, nil return cmd, nil
} }
type InviteCommand struct { type InviteCommand struct {
BaseCommand BaseCommand
nickname string nickname Name
channel string channel Name
} }
func NewInviteCommand(args []string) (editableCommand, error) { func NewInviteCommand(args []string) (editableCommand, error) {
@ -950,28 +949,28 @@ func NewInviteCommand(args []string) (editableCommand, error) {
} }
return &InviteCommand{ return &InviteCommand{
nickname: args[0], nickname: NewName(args[0]),
channel: args[1], channel: NewName(args[1]),
}, nil }, nil
} }
type TimeCommand struct { type TimeCommand struct {
BaseCommand BaseCommand
target string target Name
} }
func NewTimeCommand(args []string) (editableCommand, error) { func NewTimeCommand(args []string) (editableCommand, error) {
cmd := &TimeCommand{} cmd := &TimeCommand{}
if len(args) > 0 { if len(args) > 0 {
cmd.target = args[0] cmd.target = NewName(args[0])
} }
return cmd, nil return cmd, nil
} }
type KillCommand struct { type KillCommand struct {
BaseCommand BaseCommand
nickname string nickname Name
comment string comment Text
} }
func NewKillCommand(args []string) (editableCommand, error) { func NewKillCommand(args []string) (editableCommand, error) {
@ -979,16 +978,16 @@ func NewKillCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
return &KillCommand{ return &KillCommand{
nickname: args[0], nickname: NewName(args[0]),
comment: args[1], comment: NewText(args[1]),
}, nil }, nil
} }
type WhoWasCommand struct { type WhoWasCommand struct {
BaseCommand BaseCommand
nicknames []string nicknames []Name
count int64 count int64
target string target Name
} }
func NewWhoWasCommand(args []string) (editableCommand, error) { func NewWhoWasCommand(args []string) (editableCommand, error) {
@ -996,13 +995,13 @@ func NewWhoWasCommand(args []string) (editableCommand, error) {
return nil, NotEnoughArgsError return nil, NotEnoughArgsError
} }
cmd := &WhoWasCommand{ cmd := &WhoWasCommand{
nicknames: strings.Split(args[0], ","), nicknames: NewNames(strings.Split(args[0], ",")),
} }
if len(args) > 1 { if len(args) > 1 {
cmd.count, _ = strconv.ParseInt(args[1], 10, 64) cmd.count, _ = strconv.ParseInt(args[1], 10, 64)
} }
if len(args) > 2 { if len(args) > 2 {
cmd.target = args[2] cmd.target = NewName(args[2])
} }
return cmd, nil return cmd, nil
} }

View File

@ -37,10 +37,10 @@ type Config struct {
} }
} }
func (conf *Config) Operators() map[string][]byte { func (conf *Config) Operators() map[Name][]byte {
operators := make(map[string][]byte) operators := make(map[Name][]byte)
for name, opConf := range conf.Operator { for name, opConf := range conf.Operator {
operators[name] = opConf.PasswordBytes() operators[NewName(name)] = opConf.PasswordBytes()
} }
return operators return operators
} }

View File

@ -2,7 +2,6 @@ package irc
import ( import (
"errors" "errors"
"regexp"
"time" "time"
) )
@ -15,11 +14,6 @@ var (
// errors // errors
ErrAlreadyDestroyed = errors.New("already destroyed") ErrAlreadyDestroyed = errors.New("already destroyed")
// regexps
ChannelNameExpr = regexp.MustCompile(`^[&!#+][\pL\pN]{1,63}$`)
NicknameExpr = regexp.MustCompile(
"^[\\pL\\pN\\pP\\pS]{1,32}$")
) )
const ( const (

View File

@ -5,25 +5,25 @@ import (
"strings" "strings"
) )
func IPString(addr net.Addr) string { func IPString(addr net.Addr) Name {
addrStr := addr.String() addrStr := addr.String()
ipaddr, _, err := net.SplitHostPort(addrStr) ipaddr, _, err := net.SplitHostPort(addrStr)
if err != nil { if err != nil {
return addrStr return Name(addrStr)
} }
return ipaddr return Name(ipaddr)
} }
func AddrLookupHostname(addr net.Addr) string { func AddrLookupHostname(addr net.Addr) Name {
return LookupHostname(IPString(addr)) return LookupHostname(IPString(addr))
} }
func LookupHostname(addr string) string { func LookupHostname(addr Name) Name {
names, err := net.LookupAddr(addr) names, err := net.LookupAddr(addr.String())
if err != nil { if err != nil {
return addr return Name(addr)
} }
hostname := strings.TrimSuffix(names[0], ".") hostname := strings.TrimSuffix(names[0], ".")
return hostname return Name(hostname)
} }

View File

@ -79,23 +79,23 @@ func (target *Client) MultilineReply(names []string, code NumericCode, format st
// messaging replies // messaging replies
// //
func RplPrivMsg(source Identifier, target Identifier, message string) string { func RplPrivMsg(source Identifier, target Identifier, message Text) string {
return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message) return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message)
} }
func RplNotice(source Identifier, target Identifier, message string) string { func RplNotice(source Identifier, target Identifier, message Text) string {
return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message) return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message)
} }
func RplNick(source Identifier, newNick string) string { func RplNick(source Identifier, newNick Name) string {
return NewStringReply(source, NICK, newNick) return NewStringReply(source, NICK, newNick.String())
} }
func RplJoin(client *Client, channel *Channel) string { func RplJoin(client *Client, channel *Channel) string {
return NewStringReply(client, JOIN, channel.name) return NewStringReply(client, JOIN, channel.name.String())
} }
func RplPart(client *Client, channel *Channel, message string) string { func RplPart(client *Client, channel *Channel, message Text) string {
return NewStringReply(client, PART, "%s :%s", channel, message) return NewStringReply(client, PART, "%s :%s", channel, message)
} }
@ -117,10 +117,10 @@ func RplPing(target Identifier) string {
} }
func RplPong(client *Client) string { func RplPong(client *Client) string {
return NewStringReply(nil, PONG, client.Nick()) return NewStringReply(nil, PONG, client.Nick().String())
} }
func RplQuit(client *Client, message string) string { func RplQuit(client *Client, message Text) string {
return NewStringReply(client, QUIT, ":%s", message) return NewStringReply(client, QUIT, ":%s", message)
} }
@ -128,16 +128,16 @@ func RplError(message string) string {
return NewStringReply(nil, ERROR, ":%s", message) return NewStringReply(nil, ERROR, ":%s", message)
} }
func RplInviteMsg(inviter *Client, invitee *Client, channel string) string { func RplInviteMsg(inviter *Client, invitee *Client, channel Name) string {
return NewStringReply(inviter, INVITE, "%s :%s", invitee.Nick(), channel) return NewStringReply(inviter, INVITE, "%s :%s", invitee.Nick(), channel)
} }
func RplKick(channel *Channel, client *Client, target *Client, comment string) string { func RplKick(channel *Channel, client *Client, target *Client, comment Text) string {
return NewStringReply(client, KICK, "%s %s :%s", return NewStringReply(client, KICK, "%s %s :%s",
channel, target.Nick(), comment) channel, target.Nick(), comment)
} }
func RplKill(client *Client, target *Client, comment string) string { func RplKill(client *Client, target *Client, comment Text) string {
return NewStringReply(client, KICK, return NewStringReply(client, KICK,
"%s :%s", target.Nick(), comment) "%s :%s", target.Nick(), comment)
} }
@ -184,7 +184,7 @@ func (target *Client) RplTopic(channel *Channel) {
// <nick> <channel> // <nick> <channel>
// NB: correction in errata // NB: correction in errata
func (target *Client) RplInvitingMsg(invitee *Client, channel string) { func (target *Client) RplInvitingMsg(invitee *Client, channel Name) {
target.NumericReply(RPL_INVITING, target.NumericReply(RPL_INVITING,
"%s %s", invitee.Nick(), channel) "%s %s", invitee.Nick(), channel)
} }
@ -253,7 +253,7 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
} }
if channel != nil { if channel != nil {
channelName = channel.name channelName = channel.name.String()
if target.capabilities[MultiPrefix] { if target.capabilities[MultiPrefix] {
if channel.members[client][ChannelOperator] { if channel.members[client][ChannelOperator] {
flags += "@" flags += "@"
@ -275,12 +275,12 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
} }
// <name> :End of WHO list // <name> :End of WHO list
func (target *Client) RplEndOfWho(name string) { func (target *Client) RplEndOfWho(name Name) {
target.NumericReply(RPL_ENDOFWHO, target.NumericReply(RPL_ENDOFWHO,
"%s :End of WHO list", name) "%s :End of WHO list", name)
} }
func (target *Client) RplMaskList(mode ChannelMode, channel *Channel, mask string) { func (target *Client) RplMaskList(mode ChannelMode, channel *Channel, mask Name) {
switch mode { switch mode {
case BanMask: case BanMask:
target.RplBanList(channel, mask) target.RplBanList(channel, mask)
@ -306,7 +306,7 @@ func (target *Client) RplEndOfMaskList(mode ChannelMode, channel *Channel) {
} }
} }
func (target *Client) RplBanList(channel *Channel, mask string) { func (target *Client) RplBanList(channel *Channel, mask Name) {
target.NumericReply(RPL_BANLIST, target.NumericReply(RPL_BANLIST,
"%s %s", channel, mask) "%s %s", channel, mask)
} }
@ -316,7 +316,7 @@ func (target *Client) RplEndOfBanList(channel *Channel) {
"%s :End of channel ban list", channel) "%s :End of channel ban list", channel)
} }
func (target *Client) RplExceptList(channel *Channel, mask string) { func (target *Client) RplExceptList(channel *Channel, mask Name) {
target.NumericReply(RPL_EXCEPTLIST, target.NumericReply(RPL_EXCEPTLIST,
"%s %s", channel, mask) "%s %s", channel, mask)
} }
@ -326,7 +326,7 @@ func (target *Client) RplEndOfExceptList(channel *Channel) {
"%s :End of channel exception list", channel) "%s :End of channel exception list", channel)
} }
func (target *Client) RplInviteList(channel *Channel, mask string) { func (target *Client) RplInviteList(channel *Channel, mask Name) {
target.NumericReply(RPL_INVITELIST, target.NumericReply(RPL_INVITELIST,
"%s %s", channel, mask) "%s %s", channel, mask)
} }
@ -396,7 +396,7 @@ func (target *Client) RplVersion() {
"%s %s", SEM_VER, target.server.name) "%s %s", SEM_VER, target.server.name)
} }
func (target *Client) RplInviting(invitee *Client, channel string) { func (target *Client) RplInviting(invitee *Client, channel Name) {
target.NumericReply(RPL_INVITING, target.NumericReply(RPL_INVITING,
"%s %s", invitee.Nick(), channel) "%s %s", invitee.Nick(), channel)
} }
@ -412,7 +412,7 @@ func (target *Client) RplWhoWasUser(whoWas *WhoWas) {
whoWas.nickname, whoWas.username, whoWas.hostname, whoWas.realname) whoWas.nickname, whoWas.username, whoWas.hostname, whoWas.realname)
} }
func (target *Client) RplEndOfWhoWas(nickname string) { func (target *Client) RplEndOfWhoWas(nickname Name) {
target.NumericReply(RPL_ENDOFWHOWAS, target.NumericReply(RPL_ENDOFWHOWAS,
"%s :End of WHOWAS", nickname) "%s :End of WHOWAS", nickname)
} }
@ -426,7 +426,7 @@ func (target *Client) ErrAlreadyRegistered() {
":You may not reregister") ":You may not reregister")
} }
func (target *Client) ErrNickNameInUse(nick string) { func (target *Client) ErrNickNameInUse(nick Name) {
target.NumericReply(ERR_NICKNAMEINUSE, target.NumericReply(ERR_NICKNAMEINUSE,
"%s :Nickname is already in use", nick) "%s :Nickname is already in use", nick)
} }
@ -441,12 +441,12 @@ func (target *Client) ErrUsersDontMatch() {
":Cannot change mode for other users") ":Cannot change mode for other users")
} }
func (target *Client) ErrNeedMoreParams(command string) { func (target *Client) ErrNeedMoreParams(command StringCode) {
target.NumericReply(ERR_NEEDMOREPARAMS, target.NumericReply(ERR_NEEDMOREPARAMS,
"%s :Not enough parameters", command) "%s :Not enough parameters", command)
} }
func (target *Client) ErrNoSuchChannel(channel string) { func (target *Client) ErrNoSuchChannel(channel Name) {
target.NumericReply(ERR_NOSUCHCHANNEL, target.NumericReply(ERR_NOSUCHCHANNEL,
"%s :No such channel", channel) "%s :No such channel", channel)
} }
@ -471,7 +471,7 @@ func (target *Client) ErrBadChannelKey(channel *Channel) {
"%s :Cannot join channel (+k)", channel.name) "%s :Cannot join channel (+k)", channel.name)
} }
func (target *Client) ErrNoSuchNick(nick string) { func (target *Client) ErrNoSuchNick(nick Name) {
target.NumericReply(ERR_NOSUCHNICK, target.NumericReply(ERR_NOSUCHNICK,
"%s :No such nick/channel", nick) "%s :No such nick/channel", nick)
} }
@ -493,7 +493,7 @@ func (target *Client) ErrRestricted() {
target.NumericReply(ERR_RESTRICTED, ":Your connection is restricted!") target.NumericReply(ERR_RESTRICTED, ":Your connection is restricted!")
} }
func (target *Client) ErrNoSuchServer(server string) { func (target *Client) ErrNoSuchServer(server Name) {
target.NumericReply(ERR_NOSUCHSERVER, "%s :No such server", server) target.NumericReply(ERR_NOSUCHSERVER, "%s :No such server", server)
} }
@ -521,7 +521,7 @@ func (target *Client) ErrNoNicknameGiven() {
target.NumericReply(ERR_NONICKNAMEGIVEN, ":No nickname given") target.NumericReply(ERR_NONICKNAMEGIVEN, ":No nickname given")
} }
func (target *Client) ErrErroneusNickname(nick string) { func (target *Client) ErrErroneusNickname(nick Name) {
target.NumericReply(ERR_ERRONEUSNICKNAME, target.NumericReply(ERR_ERRONEUSNICKNAME,
"%s :Erroneous nickname", nick) "%s :Erroneous nickname", nick)
} }
@ -536,7 +536,7 @@ func (target *Client) ErrChannelIsFull(channel *Channel) {
"%s :Cannot join channel (+l)", channel) "%s :Cannot join channel (+l)", channel)
} }
func (target *Client) ErrWasNoSuchNick(nickname string) { func (target *Client) ErrWasNoSuchNick(nickname Name) {
target.NumericReply(ERR_WASNOSUCHNICK, target.NumericReply(ERR_WASNOSUCHNICK,
"%s :There was no such nickname", nickname) "%s :There was no such nickname", nickname)
} }

View File

@ -24,9 +24,9 @@ type Server struct {
db *sql.DB db *sql.DB
idle chan *Client idle chan *Client
motdFile string motdFile string
name string name Name
newConns chan net.Conn newConns chan net.Conn
operators map[string][]byte operators map[Name][]byte
password []byte password []byte
signals chan os.Signal signals chan os.Signal
whoWas *WhoWasList whoWas *WhoWasList
@ -41,7 +41,7 @@ func NewServer(config *Config) *Server {
db: OpenDB(config.Server.Database), db: OpenDB(config.Server.Database),
idle: make(chan *Client, 16), idle: make(chan *Client, 16),
motdFile: config.Server.MOTD, motdFile: config.Server.MOTD,
name: config.Server.Name, name: NewName(config.Server.Name),
newConns: make(chan net.Conn, 16), newConns: make(chan net.Conn, 16),
operators: config.Operators(), operators: config.Operators(),
signals: make(chan os.Signal, 1), signals: make(chan os.Signal, 1),
@ -68,7 +68,7 @@ func loadChannelList(channel *Channel, list string, maskMode ChannelMode) {
if list == "" { if list == "" {
return return
} }
channel.lists[maskMode].AddAll(strings.Split(list, " ")) channel.lists[maskMode].AddAll(NewNames(strings.Split(list, " ")))
} }
func (server *Server) loadChannels() { func (server *Server) loadChannels() {
@ -80,7 +80,9 @@ func (server *Server) loadChannels() {
log.Fatal("error loading channels: ", err) log.Fatal("error loading channels: ", err)
} }
for rows.Next() { for rows.Next() {
var name, flags, key, topic string var name Name
var flags string
var key, topic Text
var userLimit uint64 var userLimit uint64
var banList, exceptList, inviteList string var banList, exceptList, inviteList string
err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList, err = rows.Scan(&name, &flags, &key, &topic, &userLimit, &banList,
@ -248,15 +250,15 @@ func (server *Server) MOTD(client *Client) {
client.RplMOTDEnd() client.RplMOTDEnd()
} }
func (s *Server) Id() string { func (s *Server) Id() Name {
return s.name return s.name
} }
func (s *Server) String() string { func (s *Server) String() string {
return s.name return s.name.String()
} }
func (s *Server) Nick() string { func (s *Server) Nick() Name {
return s.Id() return s.Id()
} }
@ -339,7 +341,7 @@ func (m *NickCommand) HandleRegServer(s *Server) {
return return
} }
if !IsNickname(m.nickname) { if !m.nickname.IsNickname() {
client.ErrErroneusNickname(m.nickname) client.ErrErroneusNickname(m.nickname)
return return
} }
@ -416,7 +418,7 @@ func (msg *NickCommand) HandleServer(server *Server) {
return return
} }
if !IsNickname(msg.nickname) { if !msg.nickname.IsNickname() {
client.ErrErroneusNickname(msg.nickname) client.ErrErroneusNickname(msg.nickname)
return return
} }
@ -447,13 +449,13 @@ func (m *JoinCommand) HandleServer(s *Server) {
if m.zero { if m.zero {
for channel := range client.channels { for channel := range client.channels {
channel.Part(client, client.Nick()) channel.Part(client, client.Nick().Text())
} }
return return
} }
for name, key := range m.channels { for name, key := range m.channels {
if !IsChannel(name) { if !name.IsChannel() {
client.ErrNoSuchChannel(name) client.ErrNoSuchChannel(name)
continue continue
} }
@ -497,7 +499,7 @@ func (msg *TopicCommand) HandleServer(server *Server) {
func (msg *PrivMsgCommand) HandleServer(server *Server) { func (msg *PrivMsgCommand) HandleServer(server *Server) {
client := msg.Client() client := msg.Client()
if IsChannel(msg.target) { if msg.target.IsChannel() {
channel := server.channels.Get(msg.target) channel := server.channels.Get(msg.target)
if channel == nil { if channel == nil {
client.ErrNoSuchChannel(msg.target) client.ErrNoSuchChannel(msg.target)
@ -577,13 +579,13 @@ func (client *Client) WhoisChannelsNames() []string {
for channel := range client.channels { for channel := range client.channels {
switch { switch {
case channel.members[client][ChannelOperator]: case channel.members[client][ChannelOperator]:
chstrs[index] = "@" + channel.name chstrs[index] = "@" + channel.name.String()
case channel.members[client][Voice]: case channel.members[client][Voice]:
chstrs[index] = "+" + channel.name chstrs[index] = "+" + channel.name.String()
default: default:
chstrs[index] = channel.name chstrs[index] = channel.name.String()
} }
index += 1 index += 1
} }
@ -635,7 +637,7 @@ func (msg *WhoCommand) HandleServer(server *Server) {
for _, channel := range server.channels { for _, channel := range server.channels {
whoChannel(client, channel, friends) whoChannel(client, channel, friends)
} }
} else if IsChannel(mask) { } else if mask.IsChannel() {
// TODO implement wildcard matching // TODO implement wildcard matching
channel := server.channels.Get(mask) channel := server.channels.Get(mask)
if channel != nil { if channel != nil {
@ -685,7 +687,7 @@ func (msg *IsOnCommand) HandleServer(server *Server) {
ison := make([]string, 0) ison := make([]string, 0)
for _, nick := range msg.nicks { for _, nick := range msg.nicks {
if iclient := server.clients.Get(nick); iclient != nil { if iclient := server.clients.Get(nick); iclient != nil {
ison = append(ison, iclient.Nick()) ison = append(ison, iclient.Nick().String())
} }
} }
@ -698,7 +700,7 @@ func (msg *MOTDCommand) HandleServer(server *Server) {
func (msg *NoticeCommand) HandleServer(server *Server) { func (msg *NoticeCommand) HandleServer(server *Server) {
client := msg.Client() client := msg.Client()
if IsChannel(msg.target) { if msg.target.IsChannel() {
channel := server.channels.Get(msg.target) channel := server.channels.Get(msg.target)
if channel == nil { if channel == nil {
client.ErrNoSuchChannel(msg.target) client.ErrNoSuchChannel(msg.target)
@ -785,7 +787,7 @@ func (msg *NamesCommand) HandleServer(server *Server) {
} }
func (server *Server) Reply(target *Client, format string, args ...interface{}) { func (server *Server) Reply(target *Client, format string, args ...interface{}) {
target.Reply(RplPrivMsg(server, target, fmt.Sprintf(format, args...))) target.Reply(RplPrivMsg(server, target, NewText(fmt.Sprintf(format, args...))))
} }
func (msg *DebugCommand) HandleServer(server *Server) { func (msg *DebugCommand) HandleServer(server *Server) {
@ -880,7 +882,7 @@ func (msg *KillCommand) HandleServer(server *Server) {
} }
quitMsg := fmt.Sprintf("KILLed by %s: %s", client.Nick(), msg.comment) quitMsg := fmt.Sprintf("KILLed by %s: %s", client.Nick(), msg.comment)
target.Quit(quitMsg) target.Quit(NewText(quitMsg))
} }
func (msg *WhoWasCommand) HandleServer(server *Server) { func (msg *WhoWasCommand) HandleServer(server *Server) {

66
irc/strings.go Normal file
View File

@ -0,0 +1,66 @@
package irc
import (
"code.google.com/p/go.text/unicode/norm"
"regexp"
"strings"
)
var (
// regexps
ChannelNameExpr = regexp.MustCompile(`^[&!#+][\pL\pN]{1,63}$`)
NicknameExpr = regexp.MustCompile("^[\\pL\\pN\\pP\\pS]{1,32}$")
)
// Names are normalized and canonicalized to remove formatting marks
// and simplify usage. They are things like hostnames and usermasks.
type Name string
func NewName(str string) Name {
return Name(norm.NFKC.String(str))
}
func NewNames(strs []string) []Name {
names := make([]Name, len(strs))
for index, str := range strs {
names[index] = NewName(str)
}
return names
}
// tests
func (name Name) IsChannel() bool {
return ChannelNameExpr.MatchString(name.String())
}
func (name Name) IsNickname() bool {
return NicknameExpr.MatchString(name.String())
}
// conversions
func (name Name) String() string {
return string(name)
}
func (name Name) ToLower() Name {
return Name(strings.ToLower(name.String()))
}
// It's safe to coerce a Name to Text. Name is a strict subset of Text.
func (name Name) Text() Text {
return Text(name)
}
// Text is PRIVMSG, NOTICE, or TOPIC data. It's canonicalized UTF8
// data to simplify but keeps all formatting.
type Text string
func NewText(str string) Text {
return Text(norm.NFC.String(str))
}
func (text Text) String() string {
return string(text)
}

View File

@ -67,7 +67,7 @@ type ReplyCode interface {
String() string String() string
} }
type StringCode string type StringCode Name
func (code StringCode) String() string { func (code StringCode) String() string {
return string(code) return string(code)
@ -86,25 +86,25 @@ func (mode ChannelMode) String() string {
return string(mode) return string(mode)
} }
type ChannelNameMap map[string]*Channel type ChannelNameMap map[Name]*Channel
func (channels ChannelNameMap) Get(name string) *Channel { func (channels ChannelNameMap) Get(name Name) *Channel {
return channels[strings.ToLower(name)] return channels[name.ToLower()]
} }
func (channels ChannelNameMap) Add(channel *Channel) error { func (channels ChannelNameMap) Add(channel *Channel) error {
if channels[channel.name] != nil { if channels[channel.name.ToLower()] != nil {
return fmt.Errorf("%s: already set", channel.name) return fmt.Errorf("%s: already set", channel.name)
} }
channels[channel.name] = channel channels[channel.name.ToLower()] = channel
return nil return nil
} }
func (channels ChannelNameMap) Remove(channel *Channel) error { func (channels ChannelNameMap) Remove(channel *Channel) error {
if channel != channels[channel.name] { if channel != channels[channel.name.ToLower()] {
return fmt.Errorf("%s: mismatch", channel.name) return fmt.Errorf("%s: mismatch", channel.name)
} }
delete(channels, channel.name) delete(channels, channel.name.ToLower())
return nil return nil
} }
@ -182,8 +182,8 @@ func (channels ChannelSet) First() *Channel {
// //
type Identifier interface { type Identifier interface {
Id() string Id() Name
Nick() string Nick() Name
} }
type Replier interface { type Replier interface {

View File

@ -7,10 +7,10 @@ type WhoWasList struct {
} }
type WhoWas struct { type WhoWas struct {
nickname string nickname Name
username string username Name
hostname string hostname Name
realname string realname Text
} }
func NewWhoWasList(size uint) *WhoWasList { func NewWhoWasList(size uint) *WhoWasList {
@ -32,7 +32,7 @@ func (list *WhoWasList) Append(client *Client) {
} }
} }
func (list *WhoWasList) Find(nickname string, limit int64) []*WhoWas { func (list *WhoWasList) Find(nickname Name, limit int64) []*WhoWas {
results := make([]*WhoWas, 0) results := make([]*WhoWas, 0)
for whoWas := range list.Each() { for whoWas := range list.Each() {
if nickname != whoWas.nickname { if nickname != whoWas.nickname {