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
CAP 3.2 support #775
Changes from all commits
98836f8
d21706e
57827ca
8c87766
f4d811d
cfc8c9f
432368b
f683e81
74409aa
cd107de
9160dda
c00132a
6c45ab0
f3a5355
4b9fcbc
fed791e
474ee8e
b0b40be
2607334
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,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') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change is just increasing the crashiness lol. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]); | ||
|
@@ -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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indentation is off here. you can also try our new |
||
g_hash_table_destroy(server->cap_supported); | ||
server->cap_supported = NULL; | ||
} | ||
|
||
gslist_free_full(server->cap_queue, (GDestroyNotify) g_free); | ||
server->cap_queue = NULL; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no that would not work.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (but I guess we can break the API) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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