-
Notifications
You must be signed in to change notification settings - Fork 0
/
capability.go
182 lines (162 loc) · 6.27 KB
/
capability.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// 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/goshuirc/irc-go/ircmsg"
)
// Capability represents an optional feature that a client may request from the server.
type Capability string
const (
// AccountNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/account-notify-3.1.html
AccountNotify Capability = "account-notify"
// AccountTag is this IRCv3 capability: http://ircv3.net/specs/extensions/account-tag-3.2.html
AccountTag Capability = "account-tag"
// AwayNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/away-notify-3.1.html
AwayNotify Capability = "away-notify"
// CapNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/cap-notify-3.2.html
CapNotify Capability = "cap-notify"
// ChgHost is this IRCv3 capability: http://ircv3.net/specs/extensions/chghost-3.2.html
ChgHost Capability = "chghost"
// EchoMessage is this IRCv3 capability: http://ircv3.net/specs/extensions/echo-message-3.2.html
EchoMessage Capability = "echo-message"
// ExtendedJoin is this IRCv3 capability: http://ircv3.net/specs/extensions/extended-join-3.1.html
ExtendedJoin Capability = "extended-join"
// InviteNotify is this IRCv3 capability: http://ircv3.net/specs/extensions/invite-notify-3.2.html
InviteNotify Capability = "invite-notify"
// MaxLine is this proposed capability: https://github.com/DanielOaks/ircv3-specifications/blob/master+line-lengths/extensions/line-lengths.md
MaxLine Capability = "draft/maxline"
// MessageIDs is this draft IRCv3 capability: http://ircv3.net/specs/extensions/message-ids.html
MessageIDs Capability = "draft/message-ids"
// MessageTags is this draft IRCv3 capability: http://ircv3.net/specs/core/message-tags-3.3.html
MessageTags Capability = "draft/message-tags-0.2"
// MultiPrefix is this IRCv3 capability: http://ircv3.net/specs/extensions/multi-prefix-3.1.html
MultiPrefix Capability = "multi-prefix"
// Rename is this proposed capability: https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
Rename Capability = "draft/rename"
// SASL is this IRCv3 capability: http://ircv3.net/specs/extensions/sasl-3.2.html
SASL Capability = "sasl"
// ServerTime is this IRCv3 capability: http://ircv3.net/specs/extensions/server-time-3.2.html
ServerTime Capability = "server-time"
// STS is this draft IRCv3 capability: http://ircv3.net/specs/core/sts-3.3.html
STS Capability = "draft/sts"
// UserhostInNames is this IRCv3 capability: http://ircv3.net/specs/extensions/userhost-in-names-3.2.html
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
}