From 7193a27511967813774b6b17ad43a7893b2a77d5 Mon Sep 17 00:00:00 2001 From: Sam James Date: Sun, 30 Aug 2026 14:34:49 +0100 Subject: [PATCH] irc-channels: split large JOINs to avoid truncating keys Chunk JOIN lines if they're too big so that we don't end up sending broken/missing keys for channels. Doing it with two passes to grab the channels and then do the sending feels cleaner than mucking about with peeking at the next key to see how big it is. Closes: https://github.com/irssi/irssi/issues/1640 --- src/irc/core/irc-channels.c | 133 +++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/src/irc/core/irc-channels.c b/src/irc/core/irc-channels.c index 24f57f8d..3e148cbc 100644 --- a/src/irc/core/irc-channels.c +++ b/src/irc/core/irc-channels.c @@ -77,14 +77,31 @@ static char *force_channel_name(IRC_SERVER_REC *server, const char *name) return g_strdup_printf("%c%s", *chantypes, name); } +static void irc_channels_send_joins(IRC_SERVER_REC *server, + GString *outchans, GString *outkeys, + int use_keys) +{ + if (use_keys) + irc_send_cmdv(IRC_SERVER(server), "JOIN %s %s", outchans->str, outkeys->str); + else + irc_send_cmdv(IRC_SERVER(server), "JOIN %s", outchans->str); + + g_string_truncate(outchans, 0); + g_string_truncate(outkeys, 0); +} + static void irc_channels_join(IRC_SERVER_REC *server, const char *data, int automatic) { CHANNEL_SETUP_REC *schannel; IRC_CHANNEL_REC *chanrec; GString *outchans, *outkeys; + GPtrArray *channel_list, *key_list; + char* my_channel; + char* my_key; + unsigned int i; char *channels, *keys, *key, *space; - char **chanlist, **keylist, **tmp, **tmpkey, **tmpstr, *channel, *channame; + char **chanlist, **keylist, **tmp, **tmpkey, *channel, *channame; void *free_arg; int use_keys, cmdlen; @@ -102,7 +119,7 @@ static void irc_channels_join(IRC_SERVER_REC *server, const char *data, *space = '\0'; } - chanlist = g_strsplit(channels, ",", -1); + chanlist = g_strsplit(channels, ",", -1); keylist = g_strsplit(keys, ",", -1); outchans = g_string_new(NULL); @@ -110,71 +127,75 @@ static void irc_channels_join(IRC_SERVER_REC *server, const char *data, use_keys = *keys != '\0'; tmpkey = keylist; - tmp = chanlist; - for (;; tmp++) { - if (*tmp != NULL) { - channel = force_channel_name(server, *tmp); - chanrec = irc_channel_find(server, channel); - if (chanrec == NULL) { - schannel = channel_setup_find(channel, server->connrec->chatnet); + channel_list = g_ptr_array_new(); + key_list = g_ptr_array_new(); - g_string_append_printf(outchans, "%s,", channel); - if (*tmpkey != NULL && **tmpkey != '\0') - key = *tmpkey; - else if (schannel != NULL && schannel->password != NULL) { - /* get password from setup record */ - use_keys = TRUE; - key = schannel->password; - } else key = NULL; + for (tmp = chanlist; *tmp != NULL; tmp++) { + channel = force_channel_name(server, *tmp); + chanrec = irc_channel_find(server, channel); - g_string_append_printf(outkeys, "%s,", get_join_key(key)); - channame = channel + (channel[0] == '!' && - channel[1] == '!'); - chanrec = irc_channel_create(server, channame, NULL, - automatic); - if (key != NULL) chanrec->key = g_strdup(key); + if (chanrec == NULL) { + schannel = channel_setup_find(channel, server->connrec->chatnet); + + if (*tmpkey != NULL && **tmpkey != '\0') + key = *tmpkey; + else if (schannel != NULL && schannel->password != NULL) { + /* get password from setup record */ + use_keys = TRUE; + key = schannel->password; + } else { + key = NULL; } - g_free(channel); - if (*tmpkey != NULL) - tmpkey++; + channame = channel + (channel[0] == '!' && channel[1] == '!'); + g_ptr_array_add(channel_list, g_strdup(channame)); + g_ptr_array_add(key_list, g_strdup(get_join_key(key))); - tmpstr = tmp; - tmpstr++; - cmdlen = outchans->len-1; - - if (use_keys) - cmdlen += outkeys->len; - if (*tmpstr != NULL) - cmdlen += server_ischannel(SERVER(server), *tmpstr) ? strlen(*tmpstr) : - strlen(*tmpstr)+1; - if (*tmpkey != NULL) - cmdlen += strlen(*tmpkey); - - /* don't try to send too long lines - make sure it's not longer than 510 - so 510 - strlen("JOIN ") = 505 */ - if (cmdlen < server->max_message_len - 5 /* strlen("JOIN ") */) - continue; + chanrec = irc_channel_create(server, channame, NULL, + automatic); + if (key != NULL) + chanrec->key = g_strdup(key); } - if (outchans->len > 0) { - g_string_truncate(outchans, outchans->len - 1); - g_string_truncate(outkeys, outkeys->len - 1); + g_free(channel); - if (use_keys) - irc_send_cmdv(IRC_SERVER(server), "JOIN %s %s", outchans->str, outkeys->str); - else - irc_send_cmdv(IRC_SERVER(server), "JOIN %s", outchans->str); - } - cmdlen = 0; - g_string_truncate(outchans,0); - g_string_truncate(outkeys,0); - if (*tmp == NULL || tmp[1] == NULL) - break; + if (*tmpkey != NULL) + tmpkey++; } + + g_assert(channel_list->len == key_list->len); + + for (i = 0; i < channel_list->len ; i++) { + my_channel = g_ptr_array_index(channel_list, i); + my_key = g_ptr_array_index(key_list, i); + + /* How long is the pending queue? */ + cmdlen = outchans->len + outkeys->len; + cmdlen += 5; /* "JOIN " */ + cmdlen += 1; /* "," */ + + /* Need to see if we're too big when we add the pending bits too. */ + cmdlen += server_ischannel(SERVER(server), my_channel) ? strlen(my_channel) : + strlen(my_channel)+1; + cmdlen += strlen(my_key); + + if (cmdlen > server->max_message_len - 5) { + /* Send what we have so far. */ + irc_channels_send_joins(server, outchans, outkeys, use_keys); + } + + g_string_append_printf(outchans, "%s,", my_channel); + g_string_append_printf(outkeys, "%s,", my_key); + } + + /* Flush out anything left at the end too. */ + if (outchans->len != 0) + irc_channels_send_joins(server, outchans, outkeys, use_keys); + g_string_free(outchans, TRUE); g_string_free(outkeys, TRUE); + g_ptr_array_free(channel_list, TRUE); + g_ptr_array_free(key_list, TRUE); g_strfreev(chanlist); g_strfreev(keylist);