Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAP 3.2 support #775

Merged
merged 19 commits into from Feb 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/core/misc.c
Expand Up @@ -218,6 +218,19 @@ GSList *gslist_remove_string (GSList *list, const char *str)
return list;
}

GSList *gslist_delete_string (GSList *list, const char *str, GDestroyNotify free_func)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove the remove_string method, which is apparently flawed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol this comment came back

{
GSList *l;

l = g_slist_find_custom(list, str, (GCompareFunc) g_strcmp0);
if (l != NULL) {
free_func(l->data);
return g_slist_delete_link(list, l);
}

return list;
}

/* `list' contains pointer to structure with a char* to string. */
char *gslistptr_to_string(GSList *list, int offset, const char *delimiter)
{
Expand Down
3 changes: 2 additions & 1 deletion src/core/misc.h
Expand Up @@ -21,7 +21,8 @@ GSList *gslist_find_string(GSList *list, const char *key);
GSList *gslist_find_icase_string(GSList *list, const char *key);
GList *glist_find_string(GList *list, const char *key);
GList *glist_find_icase_string(GList *list, const char *key);
GSList *gslist_remove_string (GSList *list, const char *str);
GSList *gslist_remove_string (GSList *list, const char *str) G_GNUC_DEPRECATED;
GSList *gslist_delete_string (GSList *list, const char *str, GDestroyNotify free_func);

void gslist_free_full (GSList *list, GDestroyNotify free_func);

Expand Down
175 changes: 140 additions & 35 deletions src/irc/core/irc-cap.c
Expand Up @@ -36,7 +36,7 @@ int cap_toggle (IRC_SERVER_REC *server, char *cap, int enable)
return TRUE;
}
else if (!enable && gslist_find_string(server->cap_queue, cap)) {
server->cap_queue = gslist_remove_string(server->cap_queue, cap);
server->cap_queue = gslist_delete_string(server->cap_queue, cap, g_free);
return TRUE;
}

Expand All @@ -45,7 +45,7 @@ int cap_toggle (IRC_SERVER_REC *server, char *cap, int enable)

if (enable && !gslist_find_string(server->cap_active, cap)) {
/* Make sure the required cap is supported by the server */
if (!gslist_find_string(server->cap_supported, cap))
if (!g_hash_table_lookup_extended(server->cap_supported, cap, NULL, NULL))
return FALSE;

irc_send_cmdv(server, "CAP REQ %s", cap);
Expand Down Expand Up @@ -79,73 +79,142 @@ static void cap_emit_signal (IRC_SERVER_REC *server, char *cmd, char *args)
g_free(signal_name);
}

static gboolean parse_cap_name(char *name, char **key, char **val)
{
const char *eq;

g_return_val_if_fail(name != NULL, FALSE);
g_return_val_if_fail(name[0] != '\0', FALSE);

eq = strchr(name, '=');
/* KEY only value */
if (eq == NULL) {
*key = g_strdup(name);
*val = NULL;
/* Some values are in a KEY=VALUE form, parse them */
} else {
*key = g_strndup(name, (gsize)(eq - name));
*val = g_strdup(eq + 1);
}

return TRUE;
}

static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *address)
{
GSList *tmp;
GString *cmd;
char *params, *evt, *list, **caps;
int i, caps_length, disable, avail_caps;
char *params, *evt, *list, *star, **caps;
int i, caps_length, disable, avail_caps, multiline;

params = event_get_params(args, 3, NULL, &evt, &list);
params = event_get_params(args, 4, NULL, &evt, &star, &list);
if (params == NULL)
return;

/* Multiline responses have an additional parameter and we have to do
* this stupid dance to parse them */
if (!g_ascii_strcasecmp(evt, "LS") && !strcmp(star, "*")) {
multiline = TRUE;
}
/* This branch covers the '*' parameter isn't present, adjust the
* parameter pointer to compensate for this */
else if (list[0] == '\0') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, not sure if this one can be null instead of an empty string.

multiline = FALSE;
list = star;
}
/* Malformed request, terminate the negotiation */
else {
cap_finish_negotiation(server);
g_warn_if_reached();
return;
}

/* The table is created only when needed */
if (server->cap_supported == NULL) {
server->cap_supported = g_hash_table_new_full(g_str_hash,
g_str_equal,
g_free, g_free);
}

/* Strip the trailing whitespaces before splitting the string, some servers send responses with
* superfluous whitespaces that g_strsplit the interprets as tokens */
caps = g_strsplit(g_strchomp(list), " ", -1);
caps_length = g_strv_length(caps);

if (!g_strcmp0(evt, "LS")) {
if (!g_ascii_strcasecmp(evt, "LS")) {
if (!server->cap_in_multiline) {
/* Throw away everything and start from scratch */
g_hash_table_remove_all(server->cap_supported);
}

server->cap_in_multiline = multiline;

/* Create a list of the supported caps */
for (i = 0; i < caps_length; i++)
server->cap_supported = g_slist_prepend(server->cap_supported, g_strdup(caps[i]));
for (i = 0; i < caps_length; i++) {
char *key, *val;

/* Request the required caps, if any */
if (server->cap_queue == NULL) {
cap_finish_negotiation(server);
if (!parse_cap_name(caps[i], &key, &val)) {
g_warning("Invalid CAP %s key/value pair", evt);
continue;
}

if (!g_hash_table_insert(server->cap_supported, key, val)) {
/* The specification doesn't say anything about
* duplicated values, let's just warn the user */
g_warning("The server sent the %s capability twice", key);
}
}
else {
cmd = g_string_new("CAP REQ :");

avail_caps = 0;
/* A multiline response is always terminated by a normal one,
* wait until we receive that one to require any CAP */
if (multiline == FALSE) {
/* No CAP has been requested */
if (server->cap_queue == NULL) {
cap_finish_negotiation(server);
}
else {
cmd = g_string_new("CAP REQ :");

avail_caps = 0;

/* Check whether the cap is supported by the server */
for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) {
if (gslist_find_string(server->cap_supported, tmp->data)) {
if (avail_caps > 0)
g_string_append_c(cmd, ' ');
g_string_append(cmd, tmp->data);
/* Check whether the cap is supported by the server */
for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) {
if (g_hash_table_lookup_extended(server->cap_supported, tmp->data, NULL, NULL)) {
if (avail_caps > 0)
g_string_append_c(cmd, ' ');
g_string_append(cmd, tmp->data);

avail_caps++;
avail_caps++;
}
}
}

/* Clear the queue here */
gslist_free_full(server->cap_queue, (GDestroyNotify) g_free);
server->cap_queue = NULL;
/* Clear the queue here */
gslist_free_full(server->cap_queue, (GDestroyNotify) g_free);
server->cap_queue = NULL;

/* If the server doesn't support any cap we requested close the negotiation here */
if (avail_caps > 0)
irc_send_cmd_now(server, cmd->str);
else
cap_finish_negotiation(server);
/* If the server doesn't support any cap we requested close the negotiation here */
if (avail_caps > 0)
irc_send_cmd_now(server, cmd->str);
else
cap_finish_negotiation(server);

g_string_free(cmd, TRUE);
g_string_free(cmd, TRUE);
}
}
}
else if (!g_strcmp0(evt, "ACK")) {
else if (!g_ascii_strcasecmp(evt, "ACK")) {
int got_sasl = FALSE;

/* Emit a signal for every ack'd cap */
for (i = 0; i < caps_length; i++) {
disable = (*caps[i] == '-');

if (disable)
server->cap_active = gslist_remove_string(server->cap_active, caps[i] + 1);
server->cap_active = gslist_delete_string(server->cap_active, caps[i] + 1, g_free);
else
server->cap_active = g_slist_prepend(server->cap_active, g_strdup(caps[i]));

if (!g_strcmp0(caps[i], "sasl"))
if (!strcmp(caps[i], "sasl"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is just increasing the crashiness lol.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite as the right hand side is a constant string and the left hand side comes from a null-terminated vector of strings, I used the plain strcmp because of that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just seemed pointless to bother changing it, but sure, it's harmless.

got_sasl = TRUE;

cap_emit_signal(server, "ack", caps[i]);
Expand All @@ -157,14 +226,50 @@ static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *add
if (got_sasl == FALSE)
cap_finish_negotiation(server);
}
else if (!g_strcmp0(evt, "NAK")) {
else if (!g_ascii_strcasecmp(evt, "NAK")) {
g_warning("The server answered with a NAK to our CAP request, this should not happen");

/* A NAK'd request means that a required cap can't be enabled or disabled, don't update the
* list of active caps and notify the listeners. */
for (i = 0; i < caps_length; i++)
cap_emit_signal(server, "nak", caps[i]);
}
else if (!g_ascii_strcasecmp(evt, "NEW")) {
for (i = 0; i < caps_length; i++) {
char *key, *val;

if (!parse_cap_name(caps[i], &key, &val)) {
g_warning("Invalid CAP %s key/value pair", evt);
continue;
}

g_hash_table_insert(server->cap_supported, key, val);
cap_emit_signal(server, "new", key);
}
}
else if (!g_ascii_strcasecmp(evt, "DEL")) {
for (i = 0; i < caps_length; i++) {
char *key, *val;

if (!parse_cap_name(caps[i], &key, &val)) {
g_warning("Invalid CAP %s key/value pair", evt);
continue;
}

g_hash_table_remove(server->cap_supported, key);
cap_emit_signal(server, "delete", key);
/* The server removed this CAP, remove it from the list
* of the active ones if we had requested it */
server->cap_active = gslist_delete_string(server->cap_active, key, g_free);
/* We don't transfer the ownership of those two
* variables this time, just free them when we're done. */
g_free(key);
g_free(val);
}
}
else {
g_warning("Unhandled CAP subcommand %s", evt);
}

g_strfreev(caps);
g_free(params);
Expand Down
6 changes: 4 additions & 2 deletions src/irc/core/irc-servers.c
Expand Up @@ -443,8 +443,10 @@ static void sig_disconnected(IRC_SERVER_REC *server)
gslist_free_full(server->cap_active, (GDestroyNotify) g_free);
server->cap_active = NULL;

gslist_free_full(server->cap_supported, (GDestroyNotify) g_free);
server->cap_supported = NULL;
if (server->cap_supported) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation is off here. you can also try our new git-clang-format

g_hash_table_destroy(server->cap_supported);
server->cap_supported = NULL;
}

gslist_free_full(server->cap_queue, (GDestroyNotify) g_free);
server->cap_queue = NULL;
Expand Down
3 changes: 2 additions & 1 deletion src/irc/core/irc-servers.h
Expand Up @@ -68,14 +68,15 @@ struct _IRC_SERVER_REC {
unsigned int motd_got:1; /* We've received MOTD */
unsigned int isupport_sent:1; /* Server has sent us an isupport reply */
unsigned int cap_complete:1; /* We've done the initial CAP negotiation */
unsigned int cap_in_multiline:1; /* We're waiting for the multiline response to end */
unsigned int sasl_success:1; /* Did we authenticate successfully ? */

int max_kicks_in_cmd; /* max. number of people to kick with one /KICK command */
int max_modes_in_cmd; /* max. number of mode changes in one /MODE command */
int max_whois_in_cmd; /* max. number of nicks in one /WHOIS command */
int max_msgs_in_cmd; /* max. number of targets in one /MSG */

GSList *cap_supported; /* A list of caps supported by the server */
GHashTable *cap_supported; /* A list of caps supported by the server */
GSList *cap_active; /* A list of caps active for this session */
GSList *cap_queue; /* A list of caps to request on connection */

Expand Down
15 changes: 11 additions & 4 deletions src/perl/irc/Irc.xs
Expand Up @@ -12,7 +12,10 @@ static void perl_irc_connect_fill_hash(HV *hv, IRC_SERVER_CONNECT_REC *conn)
static void perl_irc_server_fill_hash(HV *hv, IRC_SERVER_REC *server)
{
AV *av;
HV *hv_;
GSList *tmp;
GHashTableIter iter;
gpointer key_, val_;

perl_irc_connect_fill_hash(hv, server->connrec);
perl_server_fill_hash(hv, (SERVER_REC *) server);
Expand All @@ -34,10 +37,14 @@ static void perl_irc_server_fill_hash(HV *hv, IRC_SERVER_REC *server)
(void) hv_store(hv, "cap_complete", 12, newSViv(server->cap_complete), 0);
(void) hv_store(hv, "sasl_success", 12, newSViv(server->sasl_success), 0);

av = newAV();
for (tmp = server->cap_supported; tmp != NULL; tmp = tmp->next)
av_push(av, new_pv(tmp->data));
(void) hv_store(hv, "cap_supported", 13, newRV_noinc((SV*)av), 0);
hv_ = newHV();
g_hash_table_iter_init(&iter, server->cap_supported);
while (g_hash_table_iter_next(&iter, &key_, &val_)) {
char *key = (char *)key_;
char *val = (char *)val_;
hv_store(hv_, key, strlen(key), new_pv(val), 0);
}
(void) hv_store(hv, "cap_supported", 13, newRV_noinc((SV*)hv_), 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a perl api change, right? Does perl handle this decently enough, falling back to keys only if it is accessed like an array instead of a hash? @ailin-nemui

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no that would not work..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(but I guess we can break the API)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My (wild) guess is that we have no users of this API heh
I'd avoid such a breaking change in a non-point release though, got a better idea that's backward compatible?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, I can't think of any way other than adding another key, say cap_supported_hash or another method, since we initially put it it all in an array. but that seems silly imo


av = newAV();
for (tmp = server->cap_active; tmp != NULL; tmp = tmp->next)
Expand Down