mark unicode normalization with type

This commit is contained in:
Jeremy Latt 2014-03-09 13:45:36 -07:00
parent 97886dd00f
commit 96a108f8da
12 changed files with 324 additions and 269 deletions

@ -3,27 +3,22 @@ package irc
import (
"log"
"strconv"
"strings"
)
type Channel struct {
flags ChannelModeSet
lists map[ChannelMode]*UserMaskSet
key string
key Text
members MemberSet
name string
name Name
server *Server
topic string
topic Text
userLimit uint64
}
func IsChannel(target string) bool {
return ChannelNameExpr.MatchString(target)
}
// NewChannel creates a new channel from a `Server` and a `name`
// string, which must be unique on the server.
func NewChannel(s *Server, name string) *Channel {
func NewChannel(s *Server, name Name) *Channel {
channel := &Channel{
flags: make(ChannelModeSet),
lists: map[ChannelMode]*UserMaskSet{
@ -32,7 +27,7 @@ func NewChannel(s *Server, name string) *Channel {
InviteMask: NewUserMaskSet(),
},
members: make(MemberSet),
name: strings.ToLower(name),
name: name,
server: s,
}
@ -73,22 +68,22 @@ func (channel *Channel) Nicks(target *Client) []string {
nicks[i] += "+"
}
}
nicks[i] += client.Nick()
nicks[i] += client.Nick().String()
i += 1
}
return nicks
}
func (channel *Channel) Id() string {
func (channel *Channel) Id() Name {
return channel.name
}
func (channel *Channel) Nick() string {
func (channel *Channel) Nick() Name {
return channel.name
}
func (channel *Channel) String() string {
return channel.Id()
return channel.Id().String()
}
// <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
// positional arguments in place.
if showKey {
str += " " + channel.key
str += " " + channel.key.String()
}
if showUserLimit {
str += " " + strconv.FormatUint(channel.userLimit, 10)
@ -131,11 +126,11 @@ func (channel *Channel) IsFull() bool {
(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)
}
func (channel *Channel) Join(client *Client, key string) {
func (channel *Channel) Join(client *Client, key Text) {
if channel.members.Has(client) {
// already joined, no message?
return
@ -179,7 +174,7 @@ func (channel *Channel) Join(client *Client, key string) {
channel.Names(client)
}
func (channel *Channel) Part(client *Client, message string) {
func (channel *Channel) Part(client *Client, message Text) {
if !channel.members.Has(client) {
client.ErrNotOnChannel(channel)
return
@ -207,7 +202,7 @@ func (channel *Channel) GetTopic(client *Client) {
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)) {
client.ErrNotOnChannel(channel)
return
@ -244,7 +239,7 @@ func (channel *Channel) CanSpeak(client *Client) bool {
return true
}
func (channel *Channel) PrivMsg(client *Client, message string) {
func (channel *Channel) PrivMsg(client *Client, message Text) {
if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel)
return
@ -283,7 +278,7 @@ func (channel *Channel) applyModeFlag(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) {
client.ErrChanOPrivIsNeeded(channel)
return false
@ -331,7 +326,7 @@ func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) {
}
func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp,
mask string) bool {
mask Name) bool {
list := channel.lists[mode]
if list == nil {
// 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 {
switch change.mode {
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:
return channel.applyModeFlag(client, change.mode, change.op)
@ -379,11 +375,12 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
client.ErrNeedMoreParams("MODE")
return false
}
if change.arg == channel.key {
key := NewText(change.arg)
if key == channel.key {
return false
}
channel.key = change.arg
channel.key = key
return true
case Remove:
@ -405,7 +402,8 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
return true
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:
client.ErrUnknownMode(change.mode, channel)
@ -456,7 +454,7 @@ func (channel *Channel) Persist() (err error) {
return
}
func (channel *Channel) Notice(client *Client, message string) {
func (channel *Channel) Notice(client *Client, message Text) {
if !channel.CanSpeak(client) {
client.ErrCannotSendToChan(channel)
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)) {
client.ErrNotOnChannel(channel)
return

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

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

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

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

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

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

@ -79,23 +79,23 @@ func (target *Client) MultilineReply(names []string, code NumericCode, format st
// 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)
}
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)
}
func RplNick(source Identifier, newNick string) string {
return NewStringReply(source, NICK, newNick)
func RplNick(source Identifier, newNick Name) string {
return NewStringReply(source, NICK, newNick.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)
}
@ -117,10 +117,10 @@ func RplPing(target Identifier) 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)
}
@ -128,16 +128,16 @@ func RplError(message string) string {
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)
}
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",
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,
"%s :%s", target.Nick(), comment)
}
@ -184,7 +184,7 @@ func (target *Client) RplTopic(channel *Channel) {
// <nick> <channel>
// NB: correction in errata
func (target *Client) RplInvitingMsg(invitee *Client, channel string) {
func (target *Client) RplInvitingMsg(invitee *Client, channel Name) {
target.NumericReply(RPL_INVITING,
"%s %s", invitee.Nick(), channel)
}
@ -253,7 +253,7 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
}
if channel != nil {
channelName = channel.name
channelName = channel.name.String()
if target.capabilities[MultiPrefix] {
if channel.members[client][ChannelOperator] {
flags += "@"
@ -275,12 +275,12 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
}
// <name> :End of WHO list
func (target *Client) RplEndOfWho(name string) {
func (target *Client) RplEndOfWho(name Name) {
target.NumericReply(RPL_ENDOFWHO,
"%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 {
case BanMask:
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,
"%s %s", channel, mask)
}
@ -316,7 +316,7 @@ func (target *Client) RplEndOfBanList(channel *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,
"%s %s", channel, mask)
}
@ -326,7 +326,7 @@ func (target *Client) RplEndOfExceptList(channel *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,
"%s %s", channel, mask)
}
@ -396,7 +396,7 @@ func (target *Client) RplVersion() {
"%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,
"%s %s", invitee.Nick(), channel)
}
@ -412,7 +412,7 @@ func (target *Client) RplWhoWasUser(whoWas *WhoWas) {
whoWas.nickname, whoWas.username, whoWas.hostname, whoWas.realname)
}
func (target *Client) RplEndOfWhoWas(nickname string) {
func (target *Client) RplEndOfWhoWas(nickname Name) {
target.NumericReply(RPL_ENDOFWHOWAS,
"%s :End of WHOWAS", nickname)
}
@ -426,7 +426,7 @@ func (target *Client) ErrAlreadyRegistered() {
":You may not reregister")
}
func (target *Client) ErrNickNameInUse(nick string) {
func (target *Client) ErrNickNameInUse(nick Name) {
target.NumericReply(ERR_NICKNAMEINUSE,
"%s :Nickname is already in use", nick)
}
@ -441,12 +441,12 @@ func (target *Client) ErrUsersDontMatch() {
":Cannot change mode for other users")
}
func (target *Client) ErrNeedMoreParams(command string) {
func (target *Client) ErrNeedMoreParams(command StringCode) {
target.NumericReply(ERR_NEEDMOREPARAMS,
"%s :Not enough parameters", command)
}
func (target *Client) ErrNoSuchChannel(channel string) {
func (target *Client) ErrNoSuchChannel(channel Name) {
target.NumericReply(ERR_NOSUCHCHANNEL,
"%s :No such channel", channel)
}
@ -471,7 +471,7 @@ func (target *Client) ErrBadChannelKey(channel *Channel) {
"%s :Cannot join channel (+k)", channel.name)
}
func (target *Client) ErrNoSuchNick(nick string) {
func (target *Client) ErrNoSuchNick(nick Name) {
target.NumericReply(ERR_NOSUCHNICK,
"%s :No such nick/channel", nick)
}
@ -493,7 +493,7 @@ func (target *Client) ErrRestricted() {
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)
}
@ -521,7 +521,7 @@ func (target *Client) ErrNoNicknameGiven() {
target.NumericReply(ERR_NONICKNAMEGIVEN, ":No nickname given")
}
func (target *Client) ErrErroneusNickname(nick string) {
func (target *Client) ErrErroneusNickname(nick Name) {
target.NumericReply(ERR_ERRONEUSNICKNAME,
"%s :Erroneous nickname", nick)
}
@ -536,7 +536,7 @@ func (target *Client) ErrChannelIsFull(channel *Channel) {
"%s :Cannot join channel (+l)", channel)
}
func (target *Client) ErrWasNoSuchNick(nickname string) {
func (target *Client) ErrWasNoSuchNick(nickname Name) {
target.NumericReply(ERR_WASNOSUCHNICK,
"%s :There was no such nickname", nickname)
}

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

66
irc/strings.go Normal 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)
}

@ -67,7 +67,7 @@ type ReplyCode interface {
String() string
}
type StringCode string
type StringCode Name
func (code StringCode) String() string {
return string(code)
@ -86,25 +86,25 @@ func (mode ChannelMode) String() string {
return string(mode)
}
type ChannelNameMap map[string]*Channel
type ChannelNameMap map[Name]*Channel
func (channels ChannelNameMap) Get(name string) *Channel {
return channels[strings.ToLower(name)]
func (channels ChannelNameMap) Get(name Name) *Channel {
return channels[name.ToLower()]
}
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)
}
channels[channel.name] = channel
channels[channel.name.ToLower()] = channel
return nil
}
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)
}
delete(channels, channel.name)
delete(channels, channel.name.ToLower())
return nil
}
@ -182,8 +182,8 @@ func (channels ChannelSet) First() *Channel {
//
type Identifier interface {
Id() string
Nick() string
Id() Name
Nick() Name
}
type Replier interface {

@ -7,10 +7,10 @@ type WhoWasList struct {
}
type WhoWas struct {
nickname string
username string
hostname string
realname string
nickname Name
username Name
hostname Name
realname Text
}
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)
for whoWas := range list.Each() {
if nickname != whoWas.nickname {