summaryrefslogtreecommitdiffstats
path: root/src/irc/core/irc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc/core/irc.c')
-rw-r--r--src/irc/core/irc.c608
1 files changed, 608 insertions, 0 deletions
diff --git a/src/irc/core/irc.c b/src/irc/core/irc.c
new file mode 100644
index 0000000..a52e919
--- /dev/null
+++ b/src/irc/core/irc.c
@@ -0,0 +1,608 @@
+/*
+ irc.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/modules.h>
+#include <irssi/src/core/net-sendbuffer.h>
+#include <irssi/src/core/network.h>
+#include <irssi/src/core/rawlog.h>
+#include <irssi/src/core/refstrings.h>
+
+#include <irssi/src/irc/core/irc-channels.h>
+#include <irssi/src/irc/core/irc-servers.h>
+#include <irssi/src/irc/core/servers-redirect.h>
+
+char *current_server_event;
+static int signal_default_event;
+static int signal_server_event;
+static int signal_server_event_tags;
+static int signal_server_incoming;
+
+#ifdef BLOCKING_SOCKETS
+# define MAX_SOCKET_READS 1
+#else
+# define MAX_SOCKET_READS 5
+#endif
+
+static void strip_params_colon(char *const);
+
+/* The core of the irc_send_cmd* functions. If `raw' is TRUE, the `cmd'
+ won't be checked at all if it's 512 bytes or not, or if it contains
+ line feeds or not. Use with extreme caution! */
+void irc_send_cmd_full(IRC_SERVER_REC *server, const char *cmd, int irc_send_when, int raw)
+{
+ GString *str;
+ int len;
+ guint pos;
+ gboolean server_supports_tag;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(cmd != NULL);
+
+ if (server->connection_lost)
+ return;
+
+ str = g_string_sized_new(MAX_IRC_USER_TAGS_LEN + 2 /* `@'+SPACE */ +
+ server->max_message_len + 2 /* CR+LF */ + 1 /* `\0' */);
+
+ if (server->cmdcount == 0)
+ irc_servers_start_cmd_timeout();
+ server->cmdcount++;
+
+ pos = g_slist_length(server->cmdqueue);
+ if (server->cmdlater > pos / 2) {
+ server->cmdlater = pos / 2;
+ pos = 0;
+ } else {
+ pos -= 2 * server->cmdlater;
+ }
+
+ if (!raw) {
+ const char *tmp = cmd;
+
+ server_supports_tag = server->cap_supported != NULL &&
+ g_hash_table_lookup_extended(server->cap_supported, CAP_MESSAGE_TAGS, NULL, NULL);
+
+ if (*cmd == '@' && server_supports_tag) {
+ const char *end;
+
+ while (*tmp != ' ' && *tmp != '\0')
+ tmp++;
+
+ end = tmp;
+
+ if (tmp - cmd > MAX_IRC_USER_TAGS_LEN) {
+ g_warning("irc_send_cmd_full(); tags too long(%ld)", tmp - cmd);
+ while (tmp - cmd > MAX_IRC_USER_TAGS_LEN && cmd != tmp - 1) tmp--;
+ while (*tmp != ',' && cmd != tmp - 1) tmp--;
+ }
+ if (cmd != tmp)
+ g_string_append_len(str, cmd, tmp - cmd);
+
+ tmp = end;
+ while (*tmp == ' ') tmp++;
+
+ if (*tmp != '\0' && str->len > 0)
+ g_string_append_c(str, ' ');
+ }
+ len = strlen(tmp);
+
+ /* check that we don't send any longer commands
+ than 510 bytes (2 bytes for CR+LF) */
+ g_string_append_len(str, tmp, len > server->max_message_len ?
+ server->max_message_len : len);
+ } else {
+ g_string_append(str, cmd);
+ }
+
+ if (!raw) {
+ /* Add CR+LF to command */
+ g_string_append(str, "\r\n");
+ }
+
+ if (irc_send_when == IRC_SEND_NOW) {
+ irc_server_send_and_redirect(server, str, server->redirect_next);
+ g_string_free(str, TRUE);
+ } else if (irc_send_when == IRC_SEND_NEXT) {
+ /* add to queue */
+ server->cmdqueue = g_slist_prepend(server->cmdqueue, server->redirect_next);
+ server->cmdqueue = g_slist_prepend(server->cmdqueue, g_string_free(str, FALSE));
+ } else if (irc_send_when == IRC_SEND_NORMAL) {
+ server->cmdqueue = g_slist_insert(server->cmdqueue, server->redirect_next, pos);
+ server->cmdqueue = g_slist_insert(server->cmdqueue, g_string_free(str, FALSE), pos);
+ } else if (irc_send_when == IRC_SEND_LATER) {
+ server->cmdqueue = g_slist_append(server->cmdqueue, g_string_free(str, FALSE));
+ server->cmdqueue = g_slist_append(server->cmdqueue, server->redirect_next);
+ server->cmdlater++;
+ } else {
+ g_warn_if_reached();
+ }
+
+ server->redirect_next = NULL;
+}
+
+/* Send command to IRC server */
+void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd)
+{
+ gint64 now;
+ int send_now;
+
+ now = g_get_real_time();
+ send_now = now >= server->wait_cmd &&
+ (server->cmdcount < server->max_cmds_at_once ||
+ server->cmd_queue_speed <= 0);
+
+ irc_send_cmd_full(server, cmd, send_now ? IRC_SEND_NOW : IRC_SEND_NORMAL, FALSE);
+}
+
+/* Send command to IRC server */
+void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...)
+{
+ va_list args;
+ char *str;
+
+ va_start(args, cmd);
+
+ str = g_strdup_vprintf(cmd, args);
+ irc_send_cmd(server, str);
+ g_free(str);
+
+ va_end(args);
+}
+
+/* Send command to server immediately bypassing all flood protections
+ and queues. */
+void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd)
+{
+ g_return_if_fail(cmd != NULL);
+
+ irc_send_cmd_full(server, cmd, IRC_SEND_NOW, FALSE);
+}
+
+/* Send command to server putting it at the beginning of the queue of
+ commands to send -- it will go out as soon as possible in accordance
+ to the flood protection settings. */
+void irc_send_cmd_first(IRC_SERVER_REC *server, const char *cmd)
+{
+ g_return_if_fail(cmd != NULL);
+
+ irc_send_cmd_full(server, cmd, IRC_SEND_NEXT, FALSE);
+}
+
+/* Send command to server putting it at the end of the queue. */
+void irc_send_cmd_later(IRC_SERVER_REC *server, const char *cmd)
+{
+ g_return_if_fail(cmd != NULL);
+
+ irc_send_cmd_full(server, cmd, IRC_SEND_LATER, FALSE);
+}
+
+static char *split_nicks(const char *cmd, char **pre, char **nicks, char **post, int arg)
+{
+ char *p;
+
+ *pre = g_strdup(cmd);
+ *post = *nicks = NULL;
+
+ if (**pre == '@') {
+ /* the message-tags "add" one space separated argument
+ in front of the non message-tagged IRC commands. So
+ the nicks are now off-set by one to the right. */
+ arg++;
+ }
+
+ for (p = *pre; *p != '\0'; p++) {
+ if (*p != ' ')
+ continue;
+
+ if (arg == 1) {
+ /* text after nicks */
+ *p++ = '\0';
+ while (*p == ' ') p++;
+ *post = p;
+ break;
+ }
+
+ /* find nicks */
+ while (p[1] == ' ') p++;
+ if (--arg == 1) {
+ *p = '\0';
+ *nicks = p+1;
+ }
+ }
+
+ return *pre;
+}
+
+void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd,
+ int nickarg, int max_nicks)
+{
+ char *str, *pre, *post, *nicks;
+ char **nicklist, **tmp;
+ GString *nickstr;
+ int count;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(cmd != NULL);
+
+ str = split_nicks(cmd, &pre, &nicks, &post, nickarg);
+ if (nicks == NULL) {
+ /* no nicks given? */
+ g_free(str);
+ return;
+ }
+
+ /* split the nicks */
+ nickstr = g_string_new(NULL);
+ nicklist = g_strsplit(nicks, ",", -1); count = 0;
+
+ tmp = nicklist;
+ for (;; tmp++) {
+ if (*tmp != NULL) {
+ g_string_append_printf(nickstr, "%s,", *tmp);
+ if (++count < max_nicks)
+ continue;
+ }
+
+ count = 0;
+ if (nickstr->len > 0)
+ g_string_truncate(nickstr, nickstr->len-1);
+
+ if (post == NULL)
+ irc_send_cmdv(server, "%s %s", pre, nickstr->str);
+ else
+ irc_send_cmdv(server, "%s %s %s", pre, nickstr->str, post);
+
+ g_string_truncate(nickstr, 0);
+
+ if (*tmp == NULL || tmp[1] == NULL)
+ break;
+ }
+ g_strfreev(nicklist);
+ g_string_free(nickstr, TRUE);
+
+ g_free(str);
+}
+
+/* Get next parameter */
+char *event_get_param(char **data)
+{
+ char *pos;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ if (**data == ':') {
+ /* last parameter */
+ pos = *data;
+ *data += strlen(*data);
+ return pos+1;
+ }
+
+ pos = *data;
+ while (**data != '\0' && **data != ' ') (*data)++;
+ if (**data == ' ') *(*data)++ = '\0';
+
+ return pos;
+}
+
+/* Get count parameters from data */
+char *event_get_params(const char *data, int count, ...)
+{
+ char **str, *tmp, *duprec, *datad;
+ gboolean rest;
+ va_list args;
+
+ g_return_val_if_fail(data != NULL, NULL);
+
+ va_start(args, count);
+ duprec = datad = g_strdup(data);
+
+ rest = count & PARAM_FLAG_GETREST;
+ count = PARAM_WITHOUT_FLAGS(count);
+
+ while (count-- > 0) {
+ str = (char **) va_arg(args, char **);
+ if (count == 0 && rest) {
+ /* Put the rest into the last parameter. */
+ strip_params_colon(datad);
+ tmp = datad;
+ } else {
+ tmp = event_get_param(&datad);
+ }
+ if (str != NULL) *str = tmp;
+ }
+ va_end(args);
+
+ return duprec;
+}
+
+/* Given a string containing <params>, strip any colon prefixing <trailing>. */
+static void strip_params_colon(char *const params)
+{
+ char *s;
+
+ if (params == NULL) {
+ return;
+ }
+
+ s = params;
+ while (*s != '\0') {
+ if (*s == ':') {
+ memmove(s, s+1, strlen(s+1)+1);
+ return;
+ }
+
+ s = strchr(s, ' ');
+ if (s == NULL) {
+ return;
+ }
+
+ while (*s == ' ') {
+ s++;
+ }
+ }
+}
+
+static void irc_server_event(IRC_SERVER_REC *server, const char *line,
+ const char *nick, const char *address)
+{
+ const char *signal;
+ char *event, *args;
+
+ g_return_if_fail(line != NULL);
+
+ /* split event / args */
+ event = g_strconcat("event ", line, NULL);
+ args = strchr(event+6, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+ while (*args == ' ') args++;
+ ascii_strdown(event);
+
+ /* check if event needs to be redirected */
+ signal = server_redirect_get_signal(server, nick, event, args);
+ if (signal == NULL)
+ signal = event;
+ else
+ rawlog_redirect(server->rawlog, signal);
+
+ /* emit it */
+ current_server_event = event+6;
+ if (!signal_emit(signal, 4, server, args, nick, address))
+ signal_emit_id(signal_default_event, 4, server, line, nick, address);
+ current_server_event = NULL;
+
+ g_free(event);
+}
+
+static void unescape_tag(char *tag)
+{
+ char *tmp;
+
+ if (tag == NULL)
+ return;
+
+ tmp = tag;
+ for (; *tmp != '\0'; tmp++, tag++) {
+ if (*tmp == '\\') {
+ tmp++;
+ if (*tmp == '\0')
+ break;
+ switch (*tmp) {
+ case ':':
+ *tag = ';';
+ break;
+ case 'n':
+ *tag = '\n';
+ break;
+ case 'r':
+ *tag = '\r';
+ break;
+ case 's':
+ *tag = ' ';
+ break;
+ default:
+ *tag = *tmp;
+ break;
+ }
+ } else {
+ *tag = *tmp;
+ }
+ }
+ *tag = '\0';
+}
+
+static gboolean i_str0_equal(const char *s1, const char *s2)
+{
+ return g_strcmp0(s1, s2) == 0;
+}
+
+GHashTable *irc_parse_message_tags(const char *tags)
+{
+ char **split, **tmp, **kv;
+ GHashTable *hash;
+
+ hash = g_hash_table_new_full(g_str_hash, (GEqualFunc) i_str0_equal,
+ (GDestroyNotify) i_refstr_release, (GDestroyNotify) g_free);
+ split = g_strsplit(tags, ";", -1);
+ for (tmp = split; *tmp != NULL; tmp++) {
+ if (*tmp[0] == '\0')
+ continue;
+ kv = g_strsplit(*tmp, "=", 2);
+ unescape_tag(kv[1]);
+ g_hash_table_replace(hash, i_refstr_intern(kv[0]),
+ g_strdup(kv[1] == NULL ? "" : kv[1]));
+ g_strfreev(kv);
+ }
+ g_strfreev(split);
+ return hash;
+}
+
+static void irc_server_event_tags(IRC_SERVER_REC *server, const char *line, const char *nick,
+ const char *address, const char *tags)
+{
+ char *timestr;
+ GHashTable *tags_hash = NULL;
+
+ if (tags != NULL && *tags != '\0') {
+ tags_hash = irc_parse_message_tags(tags);
+ if ((timestr = g_hash_table_lookup(tags_hash, "time")) != NULL) {
+ server_meta_stash(SERVER(server), "time", timestr);
+ }
+ }
+
+ if (*line != '\0')
+ signal_emit_id(signal_server_event, 4, server, line, nick, address);
+
+ if (tags_hash != NULL)
+ g_hash_table_destroy(tags_hash);
+}
+
+static char *irc_parse_prefix(char *line, char **nick, char **address, char **tags)
+{
+ char *p;
+
+ *nick = *address = *tags = NULL;
+
+ /* ["@" <tags> SPACE] :<nick> [["!" <user>] "@" <host>] SPACE */
+
+ if (*line == '@') {
+ *tags = ++line;
+ while (*line != '\0' && *line != ' ') {
+ line++;
+ }
+ if (*line == ' ') {
+ *line++ = '\0';
+ while (*line == ' ') line++;
+ }
+ }
+
+ if (*line != ':')
+ return line;
+
+ *nick = ++line; p = NULL;
+ while (*line != '\0' && *line != ' ') {
+ if (*line == '!' || *line == '@') {
+ p = line;
+ if (*line == '!')
+ break;
+ }
+ line++;
+ }
+
+ if (p != NULL) {
+ line = p;
+ *line++ = '\0';
+ *address = line;
+ while (*line != '\0' && *line != ' ')
+ line++;
+ }
+
+ if (*line == ' ') {
+ *line++ = '\0';
+ while (*line == ' ') line++;
+ }
+
+ return line;
+}
+
+/* Parse command line sent by server */
+static void irc_parse_incoming_line(IRC_SERVER_REC *server, char *line)
+{
+ char *nick, *address, *tags;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(line != NULL);
+
+ line = irc_parse_prefix(line, &nick, &address, &tags);
+ if (*line != '\0' || tags != NULL)
+ signal_emit_id(signal_server_event_tags, 5, server, line, nick, address, tags);
+
+ server_meta_clear_all(SERVER(server));
+}
+
+/* input function: handle incoming server messages */
+static void irc_parse_incoming(SERVER_REC *server)
+{
+ char *str;
+ int count;
+ int ret;
+
+ g_return_if_fail(server != NULL);
+
+ /* Some commands can send huge replies and irssi might handle them
+ too slowly, so read only a few times from the socket before
+ letting other tasks to run. */
+ count = 0;
+ ret = 0;
+ server_ref(server);
+ while (!server->disconnected &&
+ (ret = net_sendbuffer_receive_line(server->handle, &str, count < MAX_SOCKET_READS)) > 0) {
+ rawlog_input(server->rawlog, str);
+ signal_emit_id(signal_server_incoming, 2, server, str);
+
+ if (server->connection_lost)
+ server_disconnect(server);
+
+ count++;
+ }
+ if (ret == -1) {
+ /* connection lost */
+ server->connection_lost = TRUE;
+ server_disconnect(server);
+ }
+ server_unref(server);
+}
+
+static void irc_init_server(IRC_SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ if (!IS_IRC_SERVER(server))
+ return;
+
+ server->readtag = i_input_add(net_sendbuffer_handle(server->handle), I_INPUT_READ,
+ (GInputFunction) irc_parse_incoming, server);
+}
+
+void irc_irc_init(void)
+{
+ signal_add("server event", (SIGNAL_FUNC) irc_server_event);
+ signal_add("server event tags", (SIGNAL_FUNC) irc_server_event_tags);
+ signal_add("server connected", (SIGNAL_FUNC) irc_init_server);
+ signal_add("server connection switched", (SIGNAL_FUNC) irc_init_server);
+ signal_add("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line);
+
+ current_server_event = NULL;
+ signal_default_event = signal_get_uniq_id("default event");
+ signal_server_event = signal_get_uniq_id("server event");
+ signal_server_event_tags = signal_get_uniq_id("server event tags");
+ signal_server_incoming = signal_get_uniq_id("server incoming");
+}
+
+void irc_irc_deinit(void)
+{
+ signal_remove("server event", (SIGNAL_FUNC) irc_server_event);
+ signal_remove("server event tags", (SIGNAL_FUNC) irc_server_event_tags);
+ signal_remove("server connected", (SIGNAL_FUNC) irc_init_server);
+ signal_remove("server connection switched", (SIGNAL_FUNC) irc_init_server);
+ signal_remove("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line);
+}