Update vendor lrstanley/girc and readme

This commit is contained in:
Wim 2017-12-02 23:58:02 +01:00
parent 1d414cf2fd
commit 788d3b32ac
11 changed files with 210 additions and 155 deletions

View File

@ -175,7 +175,7 @@ Matterbridge wouldn't exist without these libraries:
* echo - https://github.com/labstack/echo * echo - https://github.com/labstack/echo
* gitter - https://github.com/sromku/go-gitter * gitter - https://github.com/sromku/go-gitter
* gops - https://github.com/google/gops * gops - https://github.com/google/gops
* irc - https://github.com/thoj/go-ircevent * irc - https://github.com/lrstanley/girc
* mattermost - https://github.com/mattermost/platform * mattermost - https://github.com/mattermost/platform
* matrix - https://github.com/matrix-org/gomatrix * matrix - https://github.com/matrix-org/gomatrix
* slack - https://github.com/nlopes/slack * slack - https://github.com/nlopes/slack

View File

@ -16,64 +16,62 @@ func (c *Client) registerBuiltins() {
c.Handlers.mu.Lock() c.Handlers.mu.Lock()
// Built-in things that should always be supported. // Built-in things that should always be supported.
c.Handlers.register(true, RPL_WELCOME, HandlerFunc(func(c *Client, e Event) { c.Handlers.register(true, true, RPL_WELCOME, HandlerFunc(handleConnect))
go handleConnect(c, e) c.Handlers.register(true, false, PING, HandlerFunc(handlePING))
})) c.Handlers.register(true, false, PONG, HandlerFunc(handlePONG))
c.Handlers.register(true, PING, HandlerFunc(handlePING))
c.Handlers.register(true, PONG, HandlerFunc(handlePONG))
if !c.Config.disableTracking { if !c.Config.disableTracking {
// Joins/parts/anything that may add/remove/rename users. // Joins/parts/anything that may add/remove/rename users.
c.Handlers.register(true, JOIN, HandlerFunc(handleJOIN)) c.Handlers.register(true, false, JOIN, HandlerFunc(handleJOIN))
c.Handlers.register(true, PART, HandlerFunc(handlePART)) c.Handlers.register(true, false, PART, HandlerFunc(handlePART))
c.Handlers.register(true, KICK, HandlerFunc(handleKICK)) c.Handlers.register(true, false, KICK, HandlerFunc(handleKICK))
c.Handlers.register(true, QUIT, HandlerFunc(handleQUIT)) c.Handlers.register(true, false, QUIT, HandlerFunc(handleQUIT))
c.Handlers.register(true, NICK, HandlerFunc(handleNICK)) c.Handlers.register(true, false, NICK, HandlerFunc(handleNICK))
c.Handlers.register(true, RPL_NAMREPLY, HandlerFunc(handleNAMES)) c.Handlers.register(true, false, RPL_NAMREPLY, HandlerFunc(handleNAMES))
// Modes. // Modes.
c.Handlers.register(true, MODE, HandlerFunc(handleMODE)) c.Handlers.register(true, false, MODE, HandlerFunc(handleMODE))
c.Handlers.register(true, RPL_CHANNELMODEIS, HandlerFunc(handleMODE)) c.Handlers.register(true, false, RPL_CHANNELMODEIS, HandlerFunc(handleMODE))
// WHO/WHOX responses. // WHO/WHOX responses.
c.Handlers.register(true, RPL_WHOREPLY, HandlerFunc(handleWHO)) c.Handlers.register(true, false, RPL_WHOREPLY, HandlerFunc(handleWHO))
c.Handlers.register(true, RPL_WHOSPCRPL, HandlerFunc(handleWHO)) c.Handlers.register(true, false, RPL_WHOSPCRPL, HandlerFunc(handleWHO))
// Other misc. useful stuff. // Other misc. useful stuff.
c.Handlers.register(true, TOPIC, HandlerFunc(handleTOPIC)) c.Handlers.register(true, false, TOPIC, HandlerFunc(handleTOPIC))
c.Handlers.register(true, RPL_TOPIC, HandlerFunc(handleTOPIC)) c.Handlers.register(true, false, RPL_TOPIC, HandlerFunc(handleTOPIC))
c.Handlers.register(true, RPL_MYINFO, HandlerFunc(handleMYINFO)) c.Handlers.register(true, false, RPL_MYINFO, HandlerFunc(handleMYINFO))
c.Handlers.register(true, RPL_ISUPPORT, HandlerFunc(handleISUPPORT)) c.Handlers.register(true, false, RPL_ISUPPORT, HandlerFunc(handleISUPPORT))
c.Handlers.register(true, RPL_MOTDSTART, HandlerFunc(handleMOTD)) c.Handlers.register(true, false, RPL_MOTDSTART, HandlerFunc(handleMOTD))
c.Handlers.register(true, RPL_MOTD, HandlerFunc(handleMOTD)) c.Handlers.register(true, false, RPL_MOTD, HandlerFunc(handleMOTD))
// Keep users lastactive times up to date. // Keep users lastactive times up to date.
c.Handlers.register(true, PRIVMSG, HandlerFunc(updateLastActive)) c.Handlers.register(true, false, PRIVMSG, HandlerFunc(updateLastActive))
c.Handlers.register(true, NOTICE, HandlerFunc(updateLastActive)) c.Handlers.register(true, false, NOTICE, HandlerFunc(updateLastActive))
c.Handlers.register(true, TOPIC, HandlerFunc(updateLastActive)) c.Handlers.register(true, false, TOPIC, HandlerFunc(updateLastActive))
c.Handlers.register(true, KICK, HandlerFunc(updateLastActive)) c.Handlers.register(true, false, KICK, HandlerFunc(updateLastActive))
// CAP IRCv3-specific tracking and functionality. // CAP IRCv3-specific tracking and functionality.
c.Handlers.register(true, CAP, HandlerFunc(handleCAP)) c.Handlers.register(true, false, CAP, HandlerFunc(handleCAP))
c.Handlers.register(true, CAP_CHGHOST, HandlerFunc(handleCHGHOST)) c.Handlers.register(true, false, CAP_CHGHOST, HandlerFunc(handleCHGHOST))
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY)) c.Handlers.register(true, false, CAP_AWAY, HandlerFunc(handleAWAY))
c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT)) c.Handlers.register(true, false, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
c.Handlers.register(true, ALL_EVENTS, HandlerFunc(handleTags)) c.Handlers.register(true, false, ALL_EVENTS, HandlerFunc(handleTags))
// SASL IRCv3 support. // SASL IRCv3 support.
c.Handlers.register(true, AUTHENTICATE, HandlerFunc(handleSASL)) c.Handlers.register(true, false, AUTHENTICATE, HandlerFunc(handleSASL))
c.Handlers.register(true, RPL_SASLSUCCESS, HandlerFunc(handleSASL)) c.Handlers.register(true, false, RPL_SASLSUCCESS, HandlerFunc(handleSASL))
c.Handlers.register(true, RPL_NICKLOCKED, HandlerFunc(handleSASLError)) c.Handlers.register(true, false, RPL_NICKLOCKED, HandlerFunc(handleSASLError))
c.Handlers.register(true, ERR_SASLFAIL, HandlerFunc(handleSASLError)) c.Handlers.register(true, false, ERR_SASLFAIL, HandlerFunc(handleSASLError))
c.Handlers.register(true, ERR_SASLTOOLONG, HandlerFunc(handleSASLError)) c.Handlers.register(true, false, ERR_SASLTOOLONG, HandlerFunc(handleSASLError))
c.Handlers.register(true, ERR_SASLABORTED, HandlerFunc(handleSASLError)) c.Handlers.register(true, false, ERR_SASLABORTED, HandlerFunc(handleSASLError))
c.Handlers.register(true, RPL_SASLMECHS, HandlerFunc(handleSASLError)) c.Handlers.register(true, false, RPL_SASLMECHS, HandlerFunc(handleSASLError))
} }
// Nickname collisions. // Nickname collisions.
c.Handlers.register(true, ERR_NICKNAMEINUSE, HandlerFunc(nickCollisionHandler)) c.Handlers.register(true, false, ERR_NICKNAMEINUSE, HandlerFunc(nickCollisionHandler))
c.Handlers.register(true, ERR_NICKCOLLISION, HandlerFunc(nickCollisionHandler)) c.Handlers.register(true, false, ERR_NICKCOLLISION, HandlerFunc(nickCollisionHandler))
c.Handlers.register(true, ERR_UNAVAILRESOURCE, HandlerFunc(nickCollisionHandler)) c.Handlers.register(true, false, ERR_UNAVAILRESOURCE, HandlerFunc(nickCollisionHandler))
c.Handlers.mu.Unlock() c.Handlers.mu.Unlock()
} }
@ -389,7 +387,7 @@ func handleISUPPORT(c *Client, e Event) {
c.state.Lock() c.state.Lock()
// Skip the first parameter, as it's our nickname. // Skip the first parameter, as it's our nickname.
for i := 1; i < len(e.Params); i++ { for i := 1; i < len(e.Params); i++ {
j := strings.IndexByte(e.Params[i], 0x3D) // = j := strings.IndexByte(e.Params[i], '=')
if j < 1 || (j+1) == len(e.Params[i]) { if j < 1 || (j+1) == len(e.Params[i]) {
c.state.serverOptions[e.Params[i]] = "" c.state.serverOptions[e.Params[i]] = ""

View File

@ -375,11 +375,11 @@ func handleTags(c *Client, e Event) {
} }
const ( const (
prefixTag byte = 0x40 // @ prefixTag byte = '@'
prefixTagValue byte = 0x3D // = prefixTagValue byte = '='
prefixUserTag byte = 0x2B // + prefixUserTag byte = '+'
tagSeparator byte = 0x3B // ; tagSeparator byte = ';'
maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included. maxTagLength int = 511 // 510 + @ and " " (space), though space usually not included.
) )
// Tags represents the key-value pairs in IRCv3 message tags. The map contains // Tags represents the key-value pairs in IRCv3 message tags. The map contains
@ -618,7 +618,7 @@ func validTag(name string) bool {
for i := 0; i < len(name); i++ { for i := 0; i < len(name); i++ {
// A-Z, a-z, 0-9, -/._ // A-Z, a-z, 0-9, -/._
if (name[i] < 0x41 || name[i] > 0x5A) && (name[i] < 0x61 || name[i] > 0x7A) && (name[i] < 0x2D || name[i] > 0x39) && name[i] != 0x5F { if (name[i] < 'A' || name[i] > 'Z') && (name[i] < 'a' || name[i] > 'z') && (name[i] < '-' || name[i] > '9') && name[i] != '_' {
return false return false
} }
} }
@ -631,7 +631,7 @@ func validTag(name string) bool {
func validTagValue(value string) bool { func validTagValue(value string) bool {
for i := 0; i < len(value); i++ { for i := 0; i < len(value); i++ {
// Don't allow any invisible chars within the tag, or semicolons. // Don't allow any invisible chars within the tag, or semicolons.
if value[i] < 0x21 || value[i] > 0x7E || value[i] == 0x3B { if value[i] < '!' || value[i] > '~' || value[i] == ';' {
return false return false
} }
} }

View File

@ -464,9 +464,9 @@ func (c *Client) GetHost() string {
return c.state.host return c.state.host
} }
// Channels returns the active list of channels that the client is in. // ChannelList returns the active list of channel names that the client is in.
// Panics if tracking is disabled. // Panics if tracking is disabled.
func (c *Client) Channels() []string { func (c *Client) ChannelList() []string {
c.panicIfNotTracking() c.panicIfNotTracking()
c.state.RLock() c.state.RLock()
@ -482,9 +482,26 @@ func (c *Client) Channels() []string {
return channels return channels
} }
// Users returns the active list of users that the client is tracking across // Channels returns the active channels that the client is in. Panics if
// all files. Panics if tracking is disabled. // tracking is disabled.
func (c *Client) Users() []string { func (c *Client) Channels() []*Channel {
c.panicIfNotTracking()
c.state.RLock()
channels := make([]*Channel, len(c.state.channels))
var i int
for channel := range c.state.channels {
channels[i] = c.state.channels[channel].Copy()
i++
}
c.state.RUnlock()
return channels
}
// UserList returns the active list of nicknames that the client is tracking
// across all networks. Panics if tracking is disabled.
func (c *Client) UserList() []string {
c.panicIfNotTracking() c.panicIfNotTracking()
c.state.RLock() c.state.RLock()
@ -500,6 +517,23 @@ func (c *Client) Users() []string {
return users return users
} }
// Users returns the active users that the client is tracking across all
// networks. Panics if tracking is disabled.
func (c *Client) Users() []*User {
c.panicIfNotTracking()
c.state.RLock()
users := make([]*User, len(c.state.users))
var i int
for user := range c.state.users {
users[i] = c.state.users[user].Copy()
i++
}
c.state.RUnlock()
return users
}
// LookupChannel looks up a given channel in state. If the channel doesn't // LookupChannel looks up a given channel in state. If the channel doesn't
// exist, nil is returned. Panics if tracking is disabled. // exist, nil is returned. Panics if tracking is disabled.
func (c *Client) LookupChannel(name string) *Channel { func (c *Client) LookupChannel(name string) *Channel {

View File

@ -272,6 +272,27 @@ func (cmd *Commands) Kick(channel, user, reason string) {
cmd.c.Send(&Event{Command: KICK, Params: []string{channel, user}}) cmd.c.Send(&Event{Command: KICK, Params: []string{channel, user}})
} }
// Ban adds the +b mode on the given mask on a channel.
func (cmd *Commands) Ban(channel, mask string) {
cmd.Mode(channel, "+b", mask)
}
// Unban removes the +b mode on the given mask on a channel.
func (cmd *Commands) Unban(channel, mask string) {
cmd.Mode(channel, "-b", mask)
}
// Mode sends a mode change to the server which should be applied to target
// (usually a channel or user), along with a set of modes (generally "+m",
// "+mmmm", or "-m", where "m" is the mode you want to change). Params is only
// needed if the mode change requires a parameter (ban or invite-only exclude.)
func (cmd *Commands) Mode(target, modes string, params ...string) {
out := []string{target, modes}
out = append(out, params...)
cmd.c.Send(&Event{Command: MODE, Params: out})
}
// Invite sends a INVITE query to the server, to invite nick to channel. // Invite sends a INVITE query to the server, to invite nick to channel.
func (cmd *Commands) Invite(channel string, users ...string) { func (cmd *Commands) Invite(channel string, users ...string) {
for i := 0; i < len(users); i++ { for i := 0; i < len(users); i++ {

View File

@ -58,7 +58,7 @@ func decodeCTCP(e *Event) *CTCPEvent {
if s < 0 { if s < 0 {
for i := 0; i < len(text); i++ { for i := 0; i < len(text); i++ {
// Check for A-Z, 0-9. // Check for A-Z, 0-9.
if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) { if (text[i] < 'A' || text[i] > 'Z') && (text[i] < '0' || text[i] > '9') {
return nil return nil
} }
} }
@ -74,7 +74,7 @@ func decodeCTCP(e *Event) *CTCPEvent {
// Loop through checking the tag first. // Loop through checking the tag first.
for i := 0; i < s; i++ { for i := 0; i < s; i++ {
// Check for A-Z, 0-9. // Check for A-Z, 0-9.
if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) { if (text[i] < 'A' || text[i] > 'Z') && (text[i] < '0' || text[i] > '9') {
return nil return nil
} }
} }
@ -168,7 +168,7 @@ func (c *CTCP) parseCMD(cmd string) string {
for i := 0; i < len(cmd); i++ { for i := 0; i < len(cmd); i++ {
// Check for A-Z, 0-9. // Check for A-Z, 0-9.
if (cmd[i] < 0x41 || cmd[i] > 0x5A) && (cmd[i] < 0x30 || cmd[i] > 0x39) { if (cmd[i] < 'A' || cmd[i] > 'Z') && (cmd[i] < '0' || cmd[i] > '9') {
return "" return ""
} }
} }

View File

@ -11,8 +11,8 @@ import (
) )
const ( const (
eventSpace byte = 0x20 // Separator. eventSpace byte = ' ' // Separator.
maxLength = 510 // Maximum length is 510 (2 for line endings). maxLength = 510 // Maximum length is 510 (2 for line endings).
) )
// cutCRFunc is used to trim CR characters from prefixes/messages. // cutCRFunc is used to trim CR characters from prefixes/messages.
@ -256,7 +256,7 @@ func (e *Event) Bytes() []byte {
// Strip newlines and carriage returns. // Strip newlines and carriage returns.
for i := 0; i < len(out); i++ { for i := 0; i < len(out); i++ {
if out[i] == 0x0A || out[i] == 0x0D { if out[i] == '\n' || out[i] == '\r' {
out = append(out[:i], out[i+1:]...) out = append(out[:i], out[i+1:]...)
i-- // Decrease the index so we can pick up where we left off. i-- // Decrease the index so we can pick up where we left off.
} }
@ -432,9 +432,9 @@ func (e *Event) StripAction() string {
} }
const ( const (
messagePrefix byte = 0x3A // ":" -- prefix or last argument messagePrefix byte = ':' // Prefix or last argument.
prefixIdent byte = 0x21 // "!" -- username prefixIdent byte = '!' // Username.
prefixHost byte = 0x40 // "@" -- hostname prefixHost byte = '@' // Hostname.
) )
// Source represents the sender of an IRC event, see RFC1459 section 2.3.1. // Source represents the sender of an IRC event, see RFC1459 section 2.3.1.

View File

@ -12,8 +12,8 @@ import (
) )
const ( const (
fmtOpenChar = 0x7B // { fmtOpenChar = '{'
fmtCloseChar = 0x7D // } fmtCloseChar = '}'
) )
var fmtColors = map[string]int{ var fmtColors = map[string]int{
@ -113,7 +113,7 @@ func Fmt(text string) string {
if last > -1 { if last > -1 {
// A-Z, a-z, and "," // A-Z, a-z, and ","
if text[i] != 0x2c && (text[i] <= 0x41 || text[i] >= 0x5a) && (text[i] <= 0x61 || text[i] >= 0x7a) { if text[i] != ',' && (text[i] <= 'A' || text[i] >= 'Z') && (text[i] <= 'a' || text[i] >= 'z') {
last = -1 last = -1
continue continue
} }
@ -127,10 +127,10 @@ func Fmt(text string) string {
// See Fmt() for more information. // See Fmt() for more information.
func TrimFmt(text string) string { func TrimFmt(text string) string {
for color := range fmtColors { for color := range fmtColors {
text = strings.Replace(text, "{"+color+"}", "", -1) text = strings.Replace(text, string(fmtOpenChar)+color+string(fmtCloseChar), "", -1)
} }
for code := range fmtCodes { for code := range fmtCodes {
text = strings.Replace(text, "{"+code+"}", "", -1) text = strings.Replace(text, string(fmtOpenChar)+code+string(fmtCloseChar), "", -1)
} }
return text return text
@ -175,9 +175,10 @@ func IsValidChannel(channel string) bool {
return false return false
} }
// #, +, !<channelid>, or & // #, +, !<channelid>, ~, or &
// Including "*" in the prefix list, as this is commonly used (e.g. ZNC) // Including "*" and "~" in the prefix list, as these are commonly used
if bytes.IndexByte([]byte{0x21, 0x23, 0x26, 0x2A, 0x2B}, channel[0]) == -1 { // (e.g. ZNC.)
if bytes.IndexByte([]byte{'!', '#', '&', '*', '~', '+'}, channel[0]) == -1 {
return false return false
} }
@ -186,14 +187,14 @@ func IsValidChannel(channel string) bool {
// 1 (prefix) + 5 (id) + 1 (+, channel name) // 1 (prefix) + 5 (id) + 1 (+, channel name)
// On some networks, this may be extended with ISUPPORT capabilities, // On some networks, this may be extended with ISUPPORT capabilities,
// however this is extremely uncommon. // however this is extremely uncommon.
if channel[0] == 0x21 { if channel[0] == '!' {
if len(channel) < 7 { if len(channel) < 7 {
return false return false
} }
// check for valid ID // check for valid ID
for i := 1; i < 6; i++ { for i := 1; i < 6; i++ {
if (channel[i] < 0x30 || channel[i] > 0x39) && (channel[i] < 0x41 || channel[i] > 0x5A) { if (channel[i] < '0' || channel[i] > '9') && (channel[i] < 'A' || channel[i] > 'Z') {
return false return false
} }
} }
@ -222,18 +223,15 @@ func IsValidNick(nick string) bool {
return false return false
} }
nick = ToRFC1459(nick)
// Check the first index. Some characters aren't allowed for the first // Check the first index. Some characters aren't allowed for the first
// index of an IRC nickname. // index of an IRC nickname.
if nick[0] < 0x41 || nick[0] > 0x7D { if (nick[0] < 'A' || nick[0] > '}') && nick[0] != '?' {
// a-z, A-Z, and _\[]{}^| // a-z, A-Z, '_\[]{}^|', and '?' in the case of znc.
return false return false
} }
for i := 1; i < len(nick); i++ { for i := 1; i < len(nick); i++ {
if (nick[i] < 0x41 || nick[i] > 0x7E) && (nick[i] < 0x30 || nick[i] > 0x39) && nick[i] != 0x2D { if (nick[i] < 'A' || nick[i] > '}') && (nick[i] < '0' || nick[i] > '9') && nick[i] != '-' {
fmt.Println(nick[i], i, nick)
// a-z, A-Z, 0-9, -, and _\[]{}^| // a-z, A-Z, 0-9, -, and _\[]{}^|
return false return false
} }
@ -262,10 +260,8 @@ func IsValidUser(name string) bool {
return false return false
} }
name = ToRFC1459(name)
// "~" is prepended (commonly) if there was no ident server response. // "~" is prepended (commonly) if there was no ident server response.
if name[0] == 0x7E { if name[0] == '~' {
// Means name only contained "~". // Means name only contained "~".
if len(name) < 2 { if len(name) < 2 {
return false return false
@ -275,12 +271,12 @@ func IsValidUser(name string) bool {
} }
// Check to see if the first index is alphanumeric. // Check to see if the first index is alphanumeric.
if (name[0] < 0x41 || name[0] > 0x4A) && (name[0] < 0x61 || name[0] > 0x7A) && (name[0] < 0x30 || name[0] > 0x39) { if (name[0] < 'A' || name[0] > 'J') && (name[0] < 'a' || name[0] > 'z') && (name[0] < '0' || name[0] > '9') {
return false return false
} }
for i := 1; i < len(name); i++ { for i := 1; i < len(name); i++ {
if (name[i] < 0x41 || name[i] > 0x7D) && (name[i] < 0x30 || name[i] > 0x39) && name[i] != 0x2D && name[i] != 0x2E { if (name[i] < 'A' || name[i] > '}') && (name[i] < '0' || name[i] > '9') && name[i] != '-' && name[i] != '.' {
// a-z, A-Z, 0-9, -, and _\[]{}^| // a-z, A-Z, 0-9, -, and _\[]{}^|
return false return false
} }
@ -291,7 +287,10 @@ func IsValidUser(name string) bool {
// ToRFC1459 converts a string to the stripped down conversion within RFC // ToRFC1459 converts a string to the stripped down conversion within RFC
// 1459. This will do things like replace an "A" with an "a", "[]" with "{}", // 1459. This will do things like replace an "A" with an "a", "[]" with "{}",
// and so forth. Useful to compare two nicknames or channels. // and so forth. Useful to compare two nicknames or channels. Note that this
// should not be used to normalize nicknames or similar, as this may convert
// valid input characters to non-rfc-valid characters. As such, it's main use
// is for comparing two nicks.
func ToRFC1459(input string) string { func ToRFC1459(input string) string {
var out string var out string

View File

@ -29,11 +29,12 @@ func (c *Client) RunHandlers(event *Event) {
} }
} }
// Regular wildcard handlers. // Background handlers first.
c.Handlers.exec(ALL_EVENTS, c, event.Copy()) c.Handlers.exec(ALL_EVENTS, true, c, event.Copy())
c.Handlers.exec(event.Command, true, c, event.Copy())
// Then regular handlers. c.Handlers.exec(ALL_EVENTS, false, c, event.Copy())
c.Handlers.exec(event.Command, c, event.Copy()) c.Handlers.exec(event.Command, false, c, event.Copy())
// Check if it's a CTCP. // Check if it's a CTCP.
if ctcp := decodeCTCP(event.Copy()); ctcp != nil { if ctcp := decodeCTCP(event.Copy()); ctcp != nil {
@ -144,7 +145,7 @@ func (c *Caller) cuid(cmd string, n int) (cuid, uid string) {
// cuidToID allows easy mapping between a generated cuid and the caller // cuidToID allows easy mapping between a generated cuid and the caller
// external/internal handler maps. // external/internal handler maps.
func (c *Caller) cuidToID(input string) (cmd, uid string) { func (c *Caller) cuidToID(input string) (cmd, uid string) {
i := strings.IndexByte(input, 0x3A) i := strings.IndexByte(input, ':')
if i < 0 { if i < 0 {
return "", "" return "", ""
} }
@ -160,9 +161,9 @@ type execStack struct {
// exec executes all handlers pertaining to specified event. Internal first, // exec executes all handlers pertaining to specified event. Internal first,
// then external. // then external.
// //
// Please note that there is no specific order/priority for which the // Please note that there is no specific order/priority for which the handlers
// handler types themselves or the handlers are executed. // are executed.
func (c *Caller) exec(command string, client *Client, event *Event) { func (c *Caller) exec(command string, bg bool, client *Client, event *Event) {
// Build a stack of handlers which can be executed concurrently. // Build a stack of handlers which can be executed concurrently.
var stack []execStack var stack []execStack
@ -170,13 +171,21 @@ func (c *Caller) exec(command string, client *Client, event *Event) {
// Get internal handlers first. // Get internal handlers first.
if _, ok := c.internal[command]; ok { if _, ok := c.internal[command]; ok {
for cuid := range c.internal[command] { for cuid := range c.internal[command] {
if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) {
continue
}
stack = append(stack, execStack{c.internal[command][cuid], cuid}) stack = append(stack, execStack{c.internal[command][cuid], cuid})
} }
} }
// Aaand then external handlers. // Then external handlers.
if _, ok := c.external[command]; ok { if _, ok := c.external[command]; ok {
for cuid := range c.external[command] { for cuid := range c.external[command] {
if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) {
continue
}
stack = append(stack, execStack{c.external[command][cuid], cuid}) stack = append(stack, execStack{c.external[command][cuid], cuid})
} }
} }
@ -189,18 +198,29 @@ func (c *Caller) exec(command string, client *Client, event *Event) {
wg.Add(len(stack)) wg.Add(len(stack))
for i := 0; i < len(stack); i++ { for i := 0; i < len(stack); i++ {
go func(index int) { go func(index int) {
c.debug.Printf("executing handler %s for event %s (%d of %d)", stack[index].cuid, command, index+1, len(stack)) defer wg.Done()
c.debug.Printf("[%d/%d] exec %s => %s", index+1, len(stack), stack[index].cuid, command)
start := time.Now() start := time.Now()
// If they want to catch any panics, add to defer stack. if bg {
go func() {
if client.Config.RecoverFunc != nil {
defer recoverHandlerPanic(client, event, stack[index].cuid, 3)
}
stack[index].Execute(client, *event)
c.debug.Printf("[%d/%d] done %s == %s", index+1, len(stack), stack[index].cuid, time.Since(start))
}()
return
}
if client.Config.RecoverFunc != nil { if client.Config.RecoverFunc != nil {
defer recoverHandlerPanic(client, event, stack[index].cuid, 3) defer recoverHandlerPanic(client, event, stack[index].cuid, 3)
} }
stack[index].Execute(client, *event) stack[index].Execute(client, *event)
c.debug.Printf("[%d/%d] done %s == %s", index+1, len(stack), stack[index].cuid, time.Since(start))
c.debug.Printf("execution of %s took %s (%d of %d)", stack[index].cuid, time.Since(start), index+1, len(stack))
wg.Done()
}(i) }(i)
} }
@ -281,9 +301,9 @@ func (c *Caller) remove(cuid string) (success bool) {
// sregister is much like Caller.register(), except that it safely locks // sregister is much like Caller.register(), except that it safely locks
// the Caller mutex. // the Caller mutex.
func (c *Caller) sregister(internal bool, cmd string, handler Handler) (cuid string) { func (c *Caller) sregister(internal, bg bool, cmd string, handler Handler) (cuid string) {
c.mu.Lock() c.mu.Lock()
cuid = c.register(internal, cmd, handler) cuid = c.register(internal, bg, cmd, handler)
c.mu.Unlock() c.mu.Unlock()
return cuid return cuid
@ -291,30 +311,34 @@ func (c *Caller) sregister(internal bool, cmd string, handler Handler) (cuid str
// register will register a handler in the internal tracker. Unsafe (you // register will register a handler in the internal tracker. Unsafe (you
// must lock c.mu yourself!) // must lock c.mu yourself!)
func (c *Caller) register(internal bool, cmd string, handler Handler) (cuid string) { func (c *Caller) register(internal, bg bool, cmd string, handler Handler) (cuid string) {
var uid string var uid string
cmd = strings.ToUpper(cmd) cmd = strings.ToUpper(cmd)
cuid, uid = c.cuid(cmd, 20)
if bg {
uid += ":bg"
cuid += ":bg"
}
if internal { if internal {
if _, ok := c.internal[cmd]; !ok { if _, ok := c.internal[cmd]; !ok {
c.internal[cmd] = map[string]Handler{} c.internal[cmd] = map[string]Handler{}
} }
cuid, uid = c.cuid(cmd, 20)
c.internal[cmd][uid] = handler c.internal[cmd][uid] = handler
} else { } else {
if _, ok := c.external[cmd]; !ok { if _, ok := c.external[cmd]; !ok {
c.external[cmd] = map[string]Handler{} c.external[cmd] = map[string]Handler{}
} }
cuid, uid = c.cuid(cmd, 20)
c.external[cmd][uid] = handler c.external[cmd][uid] = handler
} }
_, file, line, _ := runtime.Caller(3) _, file, line, _ := runtime.Caller(3)
c.debug.Printf("registering handler for %q with cuid %q (internal: %t) from: %s:%d", cmd, cuid, internal, file, line) c.debug.Printf("reg %q => %s [int:%t bg:%t] %s:%d", uid, cmd, internal, bg, file, line)
return cuid return cuid
} }
@ -323,31 +347,20 @@ func (c *Caller) register(internal bool, cmd string, handler Handler) (cuid stri
// given event. cuid is the handler uid which can be used to remove the // given event. cuid is the handler uid which can be used to remove the
// handler with Caller.Remove(). // handler with Caller.Remove().
func (c *Caller) AddHandler(cmd string, handler Handler) (cuid string) { func (c *Caller) AddHandler(cmd string, handler Handler) (cuid string) {
return c.sregister(false, cmd, handler) return c.sregister(false, false, cmd, handler)
} }
// Add registers the handler function for the given event. cuid is the // Add registers the handler function for the given event. cuid is the
// handler uid which can be used to remove the handler with Caller.Remove(). // handler uid which can be used to remove the handler with Caller.Remove().
func (c *Caller) Add(cmd string, handler func(client *Client, event Event)) (cuid string) { func (c *Caller) Add(cmd string, handler func(client *Client, event Event)) (cuid string) {
return c.sregister(false, cmd, HandlerFunc(handler)) return c.sregister(false, false, cmd, HandlerFunc(handler))
} }
// AddBg registers the handler function for the given event and executes it // AddBg registers the handler function for the given event and executes it
// in a go-routine. cuid is the handler uid which can be used to remove the // in a go-routine. cuid is the handler uid which can be used to remove the
// handler with Caller.Remove(). // handler with Caller.Remove().
func (c *Caller) AddBg(cmd string, handler func(client *Client, event Event)) (cuid string) { func (c *Caller) AddBg(cmd string, handler func(client *Client, event Event)) (cuid string) {
return c.sregister(false, cmd, HandlerFunc(func(client *Client, event Event) { return c.sregister(false, true, cmd, HandlerFunc(handler))
// Setting up background-based handlers this way allows us to get
// clean call stacks for use with panic recovery.
go func() {
// If they want to catch any panics, add to defer stack.
if client.Config.RecoverFunc != nil {
defer recoverHandlerPanic(client, &event, "goroutine", 3)
}
handler(client, event)
}()
}))
} }
// AddTmp adds a "temporary" handler, which is good for one-time or few-time // AddTmp adds a "temporary" handler, which is good for one-time or few-time
@ -361,47 +374,37 @@ func (c *Caller) AddBg(cmd string, handler func(client *Client, event Event)) (c
// //
// Additionally, AddTmp has a useful option, deadline. When set to greater // Additionally, AddTmp has a useful option, deadline. When set to greater
// than 0, deadline will be the amount of time that passes before the handler // than 0, deadline will be the amount of time that passes before the handler
// is removed from the stack, regardless if the handler returns true or not. // is removed from the stack, regardless of if the handler returns true or not.
// This is useful in that it ensures that the handler is cleaned up if the // This is useful in that it ensures that the handler is cleaned up if the
// server does not respond appropriately, or takes too long to respond. // server does not respond appropriately, or takes too long to respond.
// //
// Note that handlers supplied with AddTmp are executed in a goroutine to // Note that handlers supplied with AddTmp are executed in a goroutine to
// ensure that they are not blocking other handlers. Additionally, use cuid // ensure that they are not blocking other handlers. However, if you are
// with Caller.Remove() to prematurely remove the handler from the stack, // creating a temporary handler from another handler, it should be a
// bypassing the timeout or waiting for the handler to return that it wants // background handler.
// to be removed from the stack. //
// Use cuid with Caller.Remove() to prematurely remove the handler from the
// stack, bypassing the timeout or waiting for the handler to return that it
// wants to be removed from the stack.
func (c *Caller) AddTmp(cmd string, deadline time.Duration, handler func(client *Client, event Event) bool) (cuid string, done chan struct{}) { func (c *Caller) AddTmp(cmd string, deadline time.Duration, handler func(client *Client, event Event) bool) (cuid string, done chan struct{}) {
var uid string
cuid, uid = c.cuid(cmd, 20)
done = make(chan struct{}) done = make(chan struct{})
c.mu.Lock() cuid = c.sregister(false, true, cmd, HandlerFunc(func(client *Client, event Event) {
if _, ok := c.external[cmd]; !ok { remove := handler(client, event)
c.external[cmd] = map[string]Handler{} if remove {
} if ok := c.Remove(cuid); ok {
c.external[cmd][uid] = HandlerFunc(func(client *Client, event Event) { close(done)
// Setting up background-based handlers this way allows us to get
// clean call stacks for use with panic recovery.
go func() {
// If they want to catch any panics, add to defer stack.
if client.Config.RecoverFunc != nil {
defer recoverHandlerPanic(client, &event, "tmp-goroutine", 3)
} }
}
remove := handler(client, event) }))
if remove {
if ok := c.Remove(cuid); ok {
close(done)
}
}
}()
})
c.mu.Unlock()
if deadline > 0 { if deadline > 0 {
go func() { go func() {
<-time.After(deadline) select {
case <-time.After(deadline):
case <-done:
}
if ok := c.Remove(cuid); ok { if ok := c.Remove(cuid); ok {
close(done) close(done)
} }

View File

@ -206,11 +206,11 @@ func (c *CModes) Parse(flags string, args []string) (out []CMode) {
var argCount int var argCount int
for i := 0; i < len(flags); i++ { for i := 0; i < len(flags); i++ {
if flags[i] == 0x2B { if flags[i] == '+' {
add = true add = true
continue continue
} }
if flags[i] == 0x2D { if flags[i] == '-' {
add = false add = false
continue continue
} }
@ -265,7 +265,7 @@ func IsValidChannelMode(raw string) bool {
for i := 0; i < len(raw); i++ { for i := 0; i < len(raw); i++ {
// Allowed are: ",", A-Z and a-z. // Allowed are: ",", A-Z and a-z.
if raw[i] != 0x2C && (raw[i] < 0x41 || raw[i] > 0x5A) && (raw[i] < 0x61 || raw[i] > 0x7A) { if raw[i] != ',' && (raw[i] < 'A' || raw[i] > 'Z') && (raw[i] < 'a' || raw[i] > 'z') {
return false return false
} }
} }
@ -279,7 +279,7 @@ func isValidUserPrefix(raw string) bool {
return false return false
} }
if raw[0] != 0x28 { // (. if raw[0] != '(' {
return false return false
} }
@ -288,7 +288,7 @@ func isValidUserPrefix(raw string) bool {
// Skip the first one as we know it's (. // Skip the first one as we know it's (.
for i := 1; i < len(raw); i++ { for i := 1; i < len(raw); i++ {
if raw[i] == 0x29 { // ). if raw[i] == ')' {
passedKeys = true passedKeys = true
continue continue
} }

2
vendor/manifest vendored
View File

@ -282,7 +282,7 @@
"importpath": "github.com/lrstanley/girc", "importpath": "github.com/lrstanley/girc",
"repository": "https://github.com/lrstanley/girc", "repository": "https://github.com/lrstanley/girc",
"vcs": "git", "vcs": "git",
"revision": "e698f0468e166574e122130ffd2f579aba7e2abc", "revision": "5dff93b5453c1b2ac8382c9a38881635f47bba0e",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },