--- a/libpurple/protocols/irc/irc.c Sat Apr 23 05:05:15 2022 -0500 +++ b/libpurple/protocols/irc/irc.c Sat Apr 23 05:05:54 2022 -0500 @@ -29,6 +29,7 @@ #include "blist.h" #include "conversation.h" #include "debug.h" +#include "glibcompat.h" #include "notify.h" #include "prpl.h" #include "plugin.h" @@ -95,9 +96,21 @@ ret = write(irc->fd, buf, len); } + irc->send_time = time(NULL); + return ret; } +void irc_priority_send(struct irc_conn *irc, const char *buf) +{ + if(irc->sent_partial) { + g_queue_insert_after(irc->send_queue, irc->send_queue->head, + g_strdup(buf)); + } else { + do_send(irc, buf, strlen(buf)); + } +} + static int irc_send_raw(PurpleConnection *gc, const char *buf, int len) { struct irc_conn *irc = (struct irc_conn*)gc->proto_data; @@ -108,99 +121,125 @@ return len; } -static void -irc_send_cb(gpointer data, gint source, PurpleInputCondition cond) -{ - struct irc_conn *irc = data; - int ret, writelen; +static gboolean +irc_send_handler_cb(gpointer data) { + struct irc_conn *irc = (struct irc_conn *)data; + gint available = 0; + gint interval = 0; + + interval = purple_account_get_int(irc->account, "ratelimit-interval", + IRC_DEFAULT_COMMAND_INTERVAL); - writelen = purple_circ_buffer_get_max_read(irc->outbuf); - - if (writelen == 0) { - purple_input_remove(irc->writeh); - irc->writeh = 0; - return; + /* Check if we're enabled. */ + if(interval < 1) { + available = G_MAXINT; + } else { + gint burst = purple_account_get_int(irc->account, "ratelimit-burst", + IRC_DEFAULT_COMMAND_MAX_BURST); + available = (time(NULL) - irc->send_time) / interval; + if(available > burst) { + available = burst; + } } - ret = do_send(irc, irc->outbuf->outptr, writelen); + while(available > 0) { + gchar *msg = NULL; + gpointer raw = NULL; + gint length = 0, ret = 0; + + /* No message in the queue should be NULL, so a NULL value means the + * queue is empty. + */ + raw = g_queue_pop_head(irc->send_queue); + if(raw == NULL) { + break; + } + + msg = (gchar *)raw; + length = strlen(msg); + + ret = do_send(irc, msg, length); + if(ret <= 0 && errno != EAGAIN) { + PurpleConnection *gc = purple_account_get_connection(irc->account); + gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"), + g_strerror(errno)); + + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + tmp); + g_free(tmp); + + g_free(msg); + + irc->send_handler = 0; + + return G_SOURCE_REMOVE; + } else if(ret < length) { + gchar *partial = NULL; - if (ret < 0 && errno == EAGAIN) - return; - else if (ret <= 0) { - PurpleConnection *gc = purple_account_get_connection(irc->account); - gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"), - g_strerror(errno)); - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); - g_free(tmp); - return; + /* The preceeding conditional allows EAGAIN to fall through to + * here so that we can retransmit it. There shouldn't even be a + * case where ret < 0 and != EAGAIN, which is why we have the + * assert. + */ + if(ret < 0) { + if(ret == EAGAIN) { + ret = 0; + } else { + g_assert_not_reached(); + } + } + + /* We need to move past the characters we already wrote and requeue + * the rest of the string. We know ret is less than length, so the + * starting address of msg plus ret can never get outside of the + * string, and likewise, length minus ret will always be < length + * because ret is less than length and if it was somehow negative, + * it has been reset to zero. + */ + partial = g_strndup(msg + ret, length - ret); + + /* requeue the item to the start of the queue */ + g_queue_push_head(irc->send_queue, partial); + irc->sent_partial = TRUE; + } else { + /* We successfully sent a message so decrement the counter. */ + available -= 1; + irc->sent_partial = FALSE; + } + + /* Message was processed successfully or a partial message was + * allocated and requeued so we can free the one we popped off. + */ + g_free(msg); } - purple_circ_buffer_mark_read(irc->outbuf, ret); - -#if 0 - /* We *could* try to write more if we wrote it all */ - if (ret == write_len) { - irc_send_cb(data, source, cond); - } -#endif + return G_SOURCE_CONTINUE; } -int irc_send(struct irc_conn *irc, const char *buf) +void irc_send(struct irc_conn *irc, const char *buf) { return irc_send_len(irc, buf, strlen(buf)); } -int irc_send_len(struct irc_conn *irc, const char *buf, int buflen) -{ - int ret; - char *tosend = g_strdup(buf); +void +irc_send_len(struct irc_conn *irc, const char *buf, int buflen) { + char *tosend = g_strdup(buf); purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend); - if (tosend == NULL) - return 0; - - if (!purple_strequal(tosend, buf)) { - buflen = strlen(tosend); + if(tosend == NULL) { + return; } - if (purple_debug_is_verbose()) { + if(purple_debug_is_verbose()) { char *clean = purple_utf8_salvage(tosend); clean = g_strstrip(clean); purple_debug_misc("irc", "<< %s\n", clean); g_free(clean); } - /* If we're not buffering writes, try to send immediately */ - if (!irc->writeh) - ret = do_send(irc, tosend, buflen); - else { - ret = -1; - errno = EAGAIN; - } - - /* purple_debug(PURPLE_DEBUG_MISC, "irc", "sent%s: %s", - irc->gsc ? " (ssl)" : "", tosend); */ - if (ret <= 0 && errno != EAGAIN) { - PurpleConnection *gc = purple_account_get_connection(irc->account); - gchar *tmp = g_strdup_printf(_("Lost connection with server: %s"), - g_strerror(errno)); - purple_connection_error_reason (gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); - g_free(tmp); - } else if (ret < buflen) { - if (ret < 0) - ret = 0; - if (!irc->writeh) - irc->writeh = purple_input_add( - irc->gsc ? irc->gsc->fd : irc->fd, - PURPLE_INPUT_WRITE, irc_send_cb, irc); - purple_circ_buffer_append(irc->outbuf, tosend + ret, - buflen - ret); - } - g_free(tosend); - return ret; + g_queue_push_tail(irc->send_queue, tosend); } /* XXX I don't like messing directly with these buddies */ @@ -357,7 +396,9 @@ gc->proto_data = irc = g_new0(struct irc_conn, 1); irc->fd = -1; irc->account = account; - irc->outbuf = purple_circ_buffer_new(512); + + irc->send_queue = g_queue_new(); + irc->sent_partial = FALSE; userparts = g_strsplit(username, "@", 2); purple_connection_set_display_name(gc, userparts[0]); @@ -406,6 +447,7 @@ const char *nickname, *identname, *realname; struct irc_conn *irc = gc->proto_data; const char *pass = purple_connection_get_password(gc); + gint interval, burst; #ifdef HAVE_CYRUS_SASL const gboolean use_sasl = purple_account_get_bool(irc->account, "sasl", FALSE); #endif @@ -417,7 +459,7 @@ else /* intended to fall through */ #endif buf = irc_format(irc, "v:", "PASS", pass); - if (irc_send(irc, buf) < 0) { + if (do_send(irc, buf, strlen(buf)) < 0) { g_free(buf); return FALSE; } @@ -450,7 +492,7 @@ strlen(realname) ? realname : nickname); g_free(tmp); g_free(server); - if (irc_send(irc, buf) < 0) { + if (do_send(irc, buf, strlen(buf)) < 0) { g_free(buf); return FALSE; } @@ -459,7 +501,7 @@ buf = irc_format(irc, "vn", "NICK", nickname); irc->reqnick = g_strdup(nickname); irc->nickused = FALSE; - if (irc_send(irc, buf) < 0) { + if (do_send(irc, buf, strlen(buf)) < 0) { g_free(buf); return FALSE; } @@ -467,6 +509,15 @@ irc->recv_time = time(NULL); + /* Give ourselves one full burst for startup commands. */ + interval = purple_account_get_int(irc->account, "ratelimit-interval", + IRC_DEFAULT_COMMAND_INTERVAL); + burst = purple_account_get_int(irc->account, "ratelimit-burst", + IRC_DEFAULT_COMMAND_MAX_BURST); + + irc->send_time = time(NULL) - (interval * burst); + irc->send_handler = g_timeout_add_seconds(1, irc_send_handler_cb, irc); + return TRUE; } @@ -541,10 +592,10 @@ g_string_free(irc->motd, TRUE); g_free(irc->server); - if (irc->writeh) - purple_input_remove(irc->writeh); - - purple_circ_buffer_destroy(irc->outbuf); + g_queue_free_full(irc->send_queue, g_free); + if(irc->send_handler != 0) { + g_source_remove(irc->send_handler); + } g_free(irc->mode_chars); g_free(irc->reqnick); @@ -1108,6 +1159,16 @@ prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); #endif + option = purple_account_option_int_new(_("Seconds between sending messages"), + "ratelimit-interval", + IRC_DEFAULT_COMMAND_INTERVAL); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + option = purple_account_option_int_new(_("Maximum messages to send at once"), + "ratelimit-burst", + IRC_DEFAULT_COMMAND_MAX_BURST); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + _irc_plugin = plugin; purple_prefs_remove("/plugins/prpl/irc/quitmsg");