diff options
author | ailin-nemui <ailin-nemui@users.noreply.github.com> | 2018-02-05 22:29:35 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-05 22:29:35 +0100 |
commit | f8fbc1e1ab9b01c651c7da0652eac8631894c44c (patch) | |
tree | 4622a6b8b545c36a4d539626ad741f1e56387fcf | |
parent | 81831806132608d5111ffb24efd2a2469c62d197 (diff) | |
parent | 260733475c2b701c35024a3b4fbe900751b45341 (diff) | |
download | irssi-f8fbc1e1ab9b01c651c7da0652eac8631894c44c.zip |
Merge pull request #775 from LemonBoy/caps_kv
CAP 3.2 support
-rw-r--r-- | src/core/misc.c | 13 | ||||
-rw-r--r-- | src/core/misc.h | 3 | ||||
-rw-r--r-- | src/irc/core/irc-cap.c | 175 | ||||
-rw-r--r-- | src/irc/core/irc-servers.c | 6 | ||||
-rw-r--r-- | src/irc/core/irc-servers.h | 3 | ||||
-rw-r--r-- | src/perl/irc/Irc.xs | 15 |
6 files changed, 172 insertions, 43 deletions
diff --git a/src/core/misc.c b/src/core/misc.c index 4e9f4bbe..27741220 100644 --- a/src/core/misc.c +++ b/src/core/misc.c @@ -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) +{ + 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) { diff --git a/src/core/misc.h b/src/core/misc.h index 375744db..a46a1432 100644 --- a/src/core/misc.h +++ b/src/core/misc.h @@ -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); diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c index 5464e493..dd720841 100644 --- a/src/irc/core/irc-cap.c +++ b/src/irc/core/irc-cap.c @@ -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; } @@ -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); @@ -79,61 +79,130 @@ 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') { + 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 */ @@ -141,11 +210,11 @@ static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *add 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")) got_sasl = TRUE; cap_emit_signal(server, "ack", caps[i]); @@ -157,7 +226,7 @@ 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 @@ -165,6 +234,42 @@ static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *add 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); diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index 4eaab712..e154d17f 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -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) { + g_hash_table_destroy(server->cap_supported); + server->cap_supported = NULL; + } gslist_free_full(server->cap_queue, (GDestroyNotify) g_free); server->cap_queue = NULL; diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index 09f3f81d..1374e846 100644 --- a/src/irc/core/irc-servers.h +++ b/src/irc/core/irc-servers.h @@ -68,6 +68,7 @@ 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 */ @@ -75,7 +76,7 @@ struct _IRC_SERVER_REC { 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 */ diff --git a/src/perl/irc/Irc.xs b/src/perl/irc/Irc.xs index 41690010..bb2d27bb 100644 --- a/src/perl/irc/Irc.xs +++ b/src/perl/irc/Irc.xs @@ -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); @@ -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); av = newAV(); for (tmp = server->cap_active; tmp != NULL; tmp = tmp->next) |