fix bug with capabilities breaking after reconnect

This commit is contained in:
Liam Stanley 2017-01-06 01:09:18 -05:00
parent 0648fdc4f7
commit 683485f34c
4 changed files with 58 additions and 75 deletions

116
cap.go
View File

@ -9,7 +9,6 @@ import (
"fmt"
"io"
"strings"
"time"
)
var possibleCap = []string{
@ -19,89 +18,68 @@ var possibleCap = []string{
"message-tags",
}
// capHandler attempts to find out what IRCv3 capabilities the server supports.
// handleCAP attempts to find out what IRCv3 capabilities the server supports.
// This will lock further registration until we have acknowledged the
// capabilities.
func (c *Client) capHandler() {
func handleCAP(c *Client, e Event) {
// testnet.inspircd.org may potentially be used for testing.
capDone := make(chan struct{})
var caps []string
possible := c.Config.SupportedCaps
possible = append(possible, possibleCap...)
cuid := c.Callbacks.sregister(true, CAP, CallbackFunc(func(client *Client, e Event) {
// We can assume there was a failure attempting to enable a capability.
if len(e.Params) == 2 && e.Params[1] == CAP_NAK {
// Let the server know that we're done.
client.Send(&Event{Command: CAP, Params: []string{CAP_END}})
// We can assume there was a failure attempting to enable a capability.
if len(e.Params) == 2 && e.Params[1] == CAP_NAK {
// Let the server know that we're done.
c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
return
}
capDone <- struct{}{}
return
}
if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS {
client.state.mu.Lock()
client.state.supportedCap = strings.Split(e.Trailing, " ")
for i := 0; i < len(client.state.supportedCap); i++ {
for j := 0; j < len(possible); j++ {
if client.state.supportedCap[i] == possibleCap[j] {
// It's one for which we support.
caps = append(caps, client.state.supportedCap[i])
break
if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS {
c.state.mu.Lock()
// Loop through and check if it's one we support.
for _, cap := range strings.Split(e.Trailing, " ") {
for i := 0; i < len(possible); i++ {
// Check if it's one that we support.
if possibleCap[i] == cap {
// Ensure that there are no duplicates.
var isin bool
for j := 0; j < len(c.state.tmpCap); j++ {
if c.state.tmpCap[j] == cap {
isin = true
break
}
}
if !isin {
c.state.tmpCap = append(c.state.tmpCap, cap)
}
break
}
}
client.state.mu.Unlock()
}
c.state.mu.Unlock()
// Indicates if this is a multi-line LS. (2 args means it's the
// last LS)
if len(e.Params) == 2 {
// If we support no caps, just ack the CAP message and END.
if len(caps) == 0 {
client.Send(&Event{Command: CAP, Params: []string{CAP_END}})
capDone <- struct{}{}
return
}
// Let them know which ones we'd like to enable.
client.Send(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(caps, " ")})
// Indicates if this is a multi-line LS. (2 args means it's the
// last LS).
if len(e.Params) == 2 {
// If we support no caps, just ack the CAP message and END.
if len(c.state.tmpCap) == 0 {
c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
return
}
// Let them know which ones we'd like to enable.
c.Send(&Event{Command: CAP, Params: []string{CAP_REQ}, Trailing: strings.Join(c.state.tmpCap, " ")})
}
}
if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK {
client.state.mu.Lock()
client.state.enabledCap = strings.Split(e.Trailing, " ")
client.state.mu.Unlock()
if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK {
c.state.mu.Lock()
c.state.enabledCap = strings.Split(e.Trailing, " ")
c.state.mu.Unlock()
// Let the server know that we're done.
client.Send(&Event{Command: CAP, Params: []string{CAP_END}})
capDone <- struct{}{}
return
}
}))
unknCuid := c.Callbacks.sregister(true, ERR_UNKNOWNCOMMAND, CallbackFunc(func(client *Client, e Event) {
capDone <- struct{}{}
}))
// Ensure that the callbacks are removed when done.
defer c.Callbacks.Remove(cuid)
defer c.Callbacks.Remove(unknCuid)
// List the capabilities, specifically with the max protocol we support.
c.Send(&Event{Command: CAP, Params: []string{CAP_LS, "302"}})
select {
case <-capDone:
close(capDone)
case <-time.After(time.Second * 15):
// Wait 15 seconds, only because the server HAS YET to tell us that
// it's an unknown command, meaning it's likely doing things behind
// the scenes.
// Let the server know that we're done.
c.Send(&Event{Command: CAP, Params: []string{CAP_END}})
return
}
}

View File

@ -215,9 +215,12 @@ func (c *Client) Connect() error {
// Start read loop to process messages from the server.
go c.readLoop()
// Process potential IRCv3 capabilities.
if !c.Config.DisableCapTracking && !c.Config.DisableTracking {
go c.capHandler()
// List the IRCv3 capabilities, specifically with the max protocol we
// support.
if !c.Config.DisableTracking && !c.Config.DisableCapTracking {
if err := c.Send(&Event{Command: CAP, Params: []string{CAP_LS, "302"}}); err != nil {
return err
}
}
// Consider the connection a success at this point.

View File

@ -43,6 +43,7 @@ func (c *Client) registerHandlers() {
// CAP IRCv3-specific tracking and functionality.
if !c.Config.DisableTracking && !c.Config.DisableCapTracking {
c.Callbacks.register(true, CAP, CallbackFunc(handleCAP))
c.Callbacks.register(true, CAP_CHGHOST, CallbackFunc(handleCHGHOST))
c.Callbacks.register(true, CAP_AWAY, CallbackFunc(handleAWAY))
c.Callbacks.register(true, CAP_ACCOUNT, CallbackFunc(handleACCOUNT))

View File

@ -40,9 +40,10 @@ type state struct {
channels map[string]*Channel
// enabledCap are the capabilities which are enabled for this connection.
enabledCap []string
// supportedCap are the capabilties which are supporteed by the server
// during the last capability check.
supportedCap []string
// tmpCap are the capabilties which we share with the server during the
// last capability check. These will get sent once we have received the
// last capability list command from the server.
tmpCap []string
}
// User represents an IRC user and the state attached to them.