kike: refactor CAP processing
This commit is contained in:
parent
bf01fb7aa3
commit
1d53b87016
225
kike.c
225
kike.c
|
@ -319,7 +319,7 @@ struct client
|
|||
bool half_closed; ///< Closing link: conn. is half-closed
|
||||
|
||||
unsigned long cap_version; ///< CAP protocol version
|
||||
unsigned caps; ///< Enabled capabilities
|
||||
unsigned caps_enabled; ///< Enabled capabilities
|
||||
|
||||
bool ssl_rx_want_tx; ///< SSL_read() wants to write
|
||||
bool ssl_tx_want_rx; ///< SSL_write() wants to read
|
||||
|
@ -545,6 +545,7 @@ struct server_context
|
|||
struct str_map users; ///< Maps nicknames to clients
|
||||
struct str_map channels; ///< Maps channel names to data
|
||||
struct str_map handlers; ///< Message handlers
|
||||
struct str_map cap_handlers; ///< CAP message handlers
|
||||
|
||||
struct poller poller; ///< Manages polled description
|
||||
struct poller_timer quit_timer; ///< Quit timeout timer
|
||||
|
@ -582,6 +583,8 @@ server_context_init (struct server_context *self)
|
|||
self->channels.free = (void (*) (void *)) channel_delete;
|
||||
str_map_init (&self->handlers);
|
||||
self->handlers.key_xfrm = irc_strxfrm;
|
||||
str_map_init (&self->cap_handlers);
|
||||
self->cap_handlers.key_xfrm = irc_strxfrm;
|
||||
|
||||
poller_init (&self->poller);
|
||||
poller_timer_init (&self->quit_timer, &self->poller);
|
||||
|
@ -631,6 +634,7 @@ server_context_free (struct server_context *self)
|
|||
str_map_free (&self->users);
|
||||
str_map_free (&self->channels);
|
||||
str_map_free (&self->handlers);
|
||||
str_map_free (&self->cap_handlers);
|
||||
poller_free (&self->poller);
|
||||
|
||||
str_vector_free (&self->motd);
|
||||
|
@ -1121,97 +1125,157 @@ irc_try_finish_registration (struct client *c)
|
|||
ctx->server_name, c->nickname, c->ssl_cert_fingerprint);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
struct irc_cap_args
|
||||
{
|
||||
const char *subcommand; ///< The subcommand being processed
|
||||
const char *full_params; ///< Whole parameter string
|
||||
struct str_vector params; ///< Split parameters
|
||||
const char *target; ///< Target parameter for replies
|
||||
};
|
||||
|
||||
static void
|
||||
irc_handle_cap_ls (struct client *c, struct irc_cap_args *a)
|
||||
{
|
||||
if (a->params.len == 1
|
||||
&& !xstrtoul (&c->cap_version, a->params.vector[0], 10))
|
||||
irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
|
||||
a->subcommand, "Ignoring invalid protocol version number");
|
||||
|
||||
c->cap_negotiating = true;
|
||||
client_send (c, "CAP %s LS :multi-prefix", a->target);
|
||||
}
|
||||
|
||||
static void
|
||||
irc_handle_cap_list (struct client *c, struct irc_cap_args *a)
|
||||
{
|
||||
struct str_vector caps;
|
||||
str_vector_init (&caps);
|
||||
|
||||
if (c->caps_enabled & IRC_CAP_MULTI_PREFIX)
|
||||
str_vector_add (&caps, "multi-prefix");
|
||||
|
||||
char *caps_str = join_str_vector (&caps, ' ');
|
||||
str_vector_free (&caps);
|
||||
client_send (c, "CAP %s LIST :%s", a->target, caps_str);
|
||||
free (caps_str);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
irc_decode_capability (const char *name)
|
||||
{
|
||||
if (!strcmp (name, "multi-prefix"))
|
||||
return IRC_CAP_MULTI_PREFIX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
irc_handle_cap_req (struct client *c, struct irc_cap_args *a)
|
||||
{
|
||||
c->cap_negotiating = true;
|
||||
|
||||
unsigned new_caps = c->caps_enabled;
|
||||
bool success = true;
|
||||
for (size_t i = 0; i < a->params.len; i++)
|
||||
{
|
||||
bool removing = false;
|
||||
const char *name = a->params.vector[i];
|
||||
if (*name == '-')
|
||||
{
|
||||
removing = true;
|
||||
name++;
|
||||
}
|
||||
|
||||
unsigned cap;
|
||||
if (!(cap = irc_decode_capability (name)))
|
||||
success = false;
|
||||
else if (removing)
|
||||
new_caps &= ~cap;
|
||||
else
|
||||
new_caps |= cap;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
c->caps_enabled = new_caps;
|
||||
client_send (c, "CAP %s ACK :%s", a->target, a->full_params);
|
||||
}
|
||||
else
|
||||
client_send (c, "CAP %s NAK :%s", a->target, a->full_params);
|
||||
}
|
||||
|
||||
static void
|
||||
irc_handle_cap_ack (struct client *c, struct irc_cap_args *a)
|
||||
{
|
||||
if (a->params.len)
|
||||
irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
|
||||
a->subcommand, "No acknowledgable capabilities supported");
|
||||
}
|
||||
|
||||
static void
|
||||
irc_handle_cap_end (struct client *c, struct irc_cap_args *a)
|
||||
{
|
||||
(void) a;
|
||||
|
||||
c->cap_negotiating = false;
|
||||
irc_try_finish_registration (c);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
struct irc_cap_command
|
||||
{
|
||||
const char *name;
|
||||
void (*handler) (struct client *, struct irc_cap_args *);
|
||||
};
|
||||
|
||||
static void
|
||||
irc_register_cap_handlers (struct server_context *ctx)
|
||||
{
|
||||
static const struct irc_cap_command cap_handlers[] =
|
||||
{
|
||||
{ "LS", irc_handle_cap_ls },
|
||||
{ "LIST", irc_handle_cap_list },
|
||||
{ "REQ", irc_handle_cap_req },
|
||||
{ "ACK", irc_handle_cap_ack },
|
||||
{ "END", irc_handle_cap_end },
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < N_ELEMENTS (cap_handlers); i++)
|
||||
{
|
||||
const struct irc_cap_command *cmd = &cap_handlers[i];
|
||||
str_map_set (&ctx->cap_handlers, cmd->name, (void *) cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
irc_handle_cap (const struct irc_message *msg, struct client *c)
|
||||
{
|
||||
if (msg->params.len < 1)
|
||||
RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
|
||||
|
||||
struct str_vector v;
|
||||
str_vector_init (&v);
|
||||
struct irc_cap_args args;
|
||||
args.target = c->nickname ? c->nickname : "*";
|
||||
args.subcommand = msg->params.vector[0];
|
||||
args.full_params = "";
|
||||
str_vector_init (&args.params);
|
||||
|
||||
const char *subcommand = msg->params.vector[0];
|
||||
const char *params = "";
|
||||
if (msg->params.len > 1)
|
||||
{
|
||||
params = msg->params.vector[1];
|
||||
split_str_ignore_empty (params, ' ', &v);
|
||||
args.full_params = msg->params.vector[1];
|
||||
split_str_ignore_empty (args.full_params, ' ', &args.params);
|
||||
}
|
||||
|
||||
const char *target = c->nickname ? c->nickname : "*";
|
||||
if (!irc_strcmp (subcommand, "LS"))
|
||||
{
|
||||
if (v.len == 1 && !xstrtoul (&c->cap_version, v.vector[0], 10))
|
||||
irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
|
||||
subcommand, "Ignoring invalid protocol version number");
|
||||
|
||||
c->cap_negotiating = true;
|
||||
client_send (c, "CAP %s LS :multi-prefix", target);
|
||||
}
|
||||
else if (!irc_strcmp (subcommand, "LIST"))
|
||||
{
|
||||
struct str_vector caps;
|
||||
str_vector_init (&caps);
|
||||
|
||||
if (c->caps & IRC_CAP_MULTI_PREFIX)
|
||||
str_vector_add (&caps, "multi-prefix");
|
||||
|
||||
char *caps_str = join_str_vector (&caps, ' ');
|
||||
str_vector_free (&caps);
|
||||
client_send (c, "CAP %s LIST :%s", target, caps_str);
|
||||
free (caps_str);
|
||||
}
|
||||
else if (!irc_strcmp (subcommand, "REQ"))
|
||||
{
|
||||
c->cap_negotiating = true;
|
||||
|
||||
unsigned new_caps = c->caps;
|
||||
bool success = true;
|
||||
for (size_t i = 0; i < v.len; i++)
|
||||
{
|
||||
bool neg = false;
|
||||
const char *name = v.vector[i];
|
||||
if (*name == '-')
|
||||
{
|
||||
neg = true;
|
||||
name++;
|
||||
}
|
||||
unsigned cap = 0;
|
||||
if (!strcmp (name, "multi-prefix"))
|
||||
cap = IRC_CAP_MULTI_PREFIX;
|
||||
else
|
||||
success = false;
|
||||
|
||||
if (neg)
|
||||
new_caps &= ~cap;
|
||||
else
|
||||
new_caps |= cap;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
c->caps = new_caps;
|
||||
client_send (c, "CAP %s NAK :%s", target, params);
|
||||
}
|
||||
else
|
||||
client_send (c, "CAP %s ACK :%s", target, params);
|
||||
}
|
||||
else if (!irc_strcmp (subcommand, "ACK"))
|
||||
{
|
||||
if (v.len)
|
||||
irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
|
||||
subcommand, "No acknowledgable capabilities supported");
|
||||
}
|
||||
else if (!irc_strcmp (subcommand, "END"))
|
||||
{
|
||||
c->cap_negotiating = false;
|
||||
irc_try_finish_registration (c);
|
||||
}
|
||||
else
|
||||
struct irc_cap_command *cmd =
|
||||
str_map_find (&c->ctx->cap_handlers, args.subcommand);
|
||||
if (!cmd)
|
||||
irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
|
||||
subcommand, "Invalid CAP subcommand");
|
||||
args.subcommand, "Invalid CAP subcommand");
|
||||
else
|
||||
cmd->handler (c, &args);
|
||||
|
||||
str_vector_free (&v);
|
||||
str_vector_free (&args.params);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1994,7 +2058,7 @@ irc_append_prefixes (struct client *c, struct channel_user *user,
|
|||
|
||||
if (prefixes.len)
|
||||
{
|
||||
if (c->caps & IRC_CAP_MULTI_PREFIX)
|
||||
if (c->caps_enabled & IRC_CAP_MULTI_PREFIX)
|
||||
str_append (output, prefixes.str);
|
||||
else
|
||||
str_append_c (output, prefixes.str[0]);
|
||||
|
@ -3508,6 +3572,7 @@ main (int argc, char *argv[])
|
|||
struct server_context ctx;
|
||||
server_context_init (&ctx);
|
||||
irc_register_handlers (&ctx);
|
||||
irc_register_cap_handlers (&ctx);
|
||||
|
||||
struct error *e = NULL;
|
||||
if (!read_config_file (&ctx.config, &e))
|
||||
|
|
Loading…
Reference in New Issue