kike: refactor CAP processing

This commit is contained in:
Přemysl Eric Janouch 2015-06-12 21:27:17 +02:00
parent bf01fb7aa3
commit 1d53b87016
1 changed files with 145 additions and 80 deletions

225
kike.c
View File

@ -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))