mirror of
				https://github.com/ergochat/ergo.git
				synced 2025-11-04 07:47:25 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			166 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) 2012-2014 Jeremy Latt
 | 
						|
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
 | 
						|
// released under the MIT license
 | 
						|
 | 
						|
package irc
 | 
						|
 | 
						|
import (
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/DanielOaks/girc-go/ircmsg"
 | 
						|
)
 | 
						|
 | 
						|
// Capability represents an optional feature that a client may request from the server.
 | 
						|
type Capability string
 | 
						|
 | 
						|
const (
 | 
						|
	AccountNotify   Capability = "account-notify"
 | 
						|
	AccountTag      Capability = "account-tag"
 | 
						|
	AwayNotify      Capability = "away-notify"
 | 
						|
	CapNotify       Capability = "cap-notify"
 | 
						|
	ChgHost         Capability = "chghost"
 | 
						|
	EchoMessage     Capability = "echo-message"
 | 
						|
	ExtendedJoin    Capability = "extended-join"
 | 
						|
	InviteNotify    Capability = "invite-notify"
 | 
						|
	MaxLine         Capability = "draft/maxline"
 | 
						|
	MessageIDs      Capability = "draft/message-ids"
 | 
						|
	MessageTags     Capability = "draft/message-tags-0.2"
 | 
						|
	MultiPrefix     Capability = "multi-prefix"
 | 
						|
	Rename          Capability = "draft/rename"
 | 
						|
	SASL            Capability = "sasl"
 | 
						|
	ServerTime      Capability = "server-time"
 | 
						|
	STS             Capability = "draft/sts"
 | 
						|
	UserhostInNames Capability = "userhost-in-names"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// SupportedCapabilities are the caps we advertise.
 | 
						|
	SupportedCapabilities = CapabilitySet{
 | 
						|
		AccountTag:    true,
 | 
						|
		AccountNotify: true,
 | 
						|
		AwayNotify:    true,
 | 
						|
		CapNotify:     true,
 | 
						|
		ChgHost:       true,
 | 
						|
		EchoMessage:   true,
 | 
						|
		ExtendedJoin:  true,
 | 
						|
		InviteNotify:  true,
 | 
						|
		MessageIDs:    true,
 | 
						|
		// MaxLine is set during server startup
 | 
						|
		MessageTags: true,
 | 
						|
		MultiPrefix: true,
 | 
						|
		Rename:      true,
 | 
						|
		// SASL is set during server startup
 | 
						|
		ServerTime: true,
 | 
						|
		// STS is set during server startup
 | 
						|
		UserhostInNames: true,
 | 
						|
	}
 | 
						|
	// CapValues are the actual values we advertise to v3.2 clients.
 | 
						|
	CapValues = map[Capability]string{
 | 
						|
		SASL: "PLAIN,EXTERNAL",
 | 
						|
	}
 | 
						|
)
 | 
						|
 | 
						|
func (capability Capability) String() string {
 | 
						|
	return string(capability)
 | 
						|
}
 | 
						|
 | 
						|
// CapState shows whether we're negotiating caps, finished, etc for connection registration.
 | 
						|
type CapState uint
 | 
						|
 | 
						|
const (
 | 
						|
	// CapNone means CAP hasn't been negotiated at all.
 | 
						|
	CapNone CapState = iota
 | 
						|
	// CapNegotiating means CAP is being negotiated and registration should be paused.
 | 
						|
	CapNegotiating CapState = iota
 | 
						|
	// CapNegotiated means CAP negotiation has been successfully ended and reg should complete.
 | 
						|
	CapNegotiated CapState = iota
 | 
						|
)
 | 
						|
 | 
						|
// CapVersion is used to select which max version of CAP the client supports.
 | 
						|
type CapVersion uint
 | 
						|
 | 
						|
const (
 | 
						|
	// Cap301 refers to the base CAP spec.
 | 
						|
	Cap301 CapVersion = 301
 | 
						|
	// Cap302 refers to the IRCv3.2 CAP spec.
 | 
						|
	Cap302 CapVersion = 302
 | 
						|
)
 | 
						|
 | 
						|
// CapabilitySet is used to track supported, enabled, and existing caps.
 | 
						|
type CapabilitySet map[Capability]bool
 | 
						|
 | 
						|
func (set CapabilitySet) String(version CapVersion) string {
 | 
						|
	strs := make([]string, len(set))
 | 
						|
	index := 0
 | 
						|
	for capability := range set {
 | 
						|
		capString := string(capability)
 | 
						|
		if version == Cap302 {
 | 
						|
			val, exists := CapValues[capability]
 | 
						|
			if exists {
 | 
						|
				capString += "=" + val
 | 
						|
			}
 | 
						|
		}
 | 
						|
		strs[index] = capString
 | 
						|
		index++
 | 
						|
	}
 | 
						|
	return strings.Join(strs, " ")
 | 
						|
}
 | 
						|
 | 
						|
// CAP <subcmd> [<caps>]
 | 
						|
func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
 | 
						|
	subCommand := strings.ToUpper(msg.Params[0])
 | 
						|
	capabilities := make(CapabilitySet)
 | 
						|
	var capString string
 | 
						|
 | 
						|
	if len(msg.Params) > 1 {
 | 
						|
		capString = msg.Params[1]
 | 
						|
		strs := strings.Split(capString, " ")
 | 
						|
		for _, str := range strs {
 | 
						|
			if len(str) > 0 {
 | 
						|
				capabilities[Capability(str)] = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch subCommand {
 | 
						|
	case "LS":
 | 
						|
		if !client.registered {
 | 
						|
			client.capState = CapNegotiating
 | 
						|
		}
 | 
						|
		if len(msg.Params) > 1 && msg.Params[1] == "302" {
 | 
						|
			client.capVersion = 302
 | 
						|
		}
 | 
						|
		// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
 | 
						|
		// the server.name source... otherwise it doesn't respond to the CAP message with
 | 
						|
		// anything and just hangs on connection.
 | 
						|
		//TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate.
 | 
						|
		client.Send(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String(client.capVersion))
 | 
						|
 | 
						|
	case "LIST":
 | 
						|
		client.Send(nil, server.name, "CAP", client.nick, subCommand, client.capabilities.String(Cap301)) // values not sent on LIST so force 3.1
 | 
						|
 | 
						|
	case "REQ":
 | 
						|
		// make sure all capabilities actually exist
 | 
						|
		for capability := range capabilities {
 | 
						|
			if !SupportedCapabilities[capability] {
 | 
						|
				client.Send(nil, server.name, "CAP", client.nick, "NAK", capString)
 | 
						|
				return false
 | 
						|
			}
 | 
						|
		}
 | 
						|
		for capability := range capabilities {
 | 
						|
			client.capabilities[capability] = true
 | 
						|
		}
 | 
						|
		client.Send(nil, server.name, "CAP", client.nick, "ACK", capString)
 | 
						|
 | 
						|
	case "END":
 | 
						|
		if !client.registered {
 | 
						|
			client.capState = CapNegotiated
 | 
						|
			server.tryRegister(client)
 | 
						|
		}
 | 
						|
 | 
						|
	default:
 | 
						|
		client.Send(nil, server.name, ERR_INVALIDCAPCMD, client.nick, subCommand, "Invalid CAP subcommand")
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 |