From c912726f496682a7beaeec7d00ddb501bbd2c5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Thu, 19 Nov 2015 19:09:05 +0100 Subject: [PATCH] degesch: add ability to hook IRC and user input We're going to make this available to the Lua API soon. --- degesch.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 192 insertions(+), 14 deletions(-) diff --git a/degesch.c b/degesch.c index ac54ded..ff48263 100644 --- a/degesch.c +++ b/degesch.c @@ -1382,6 +1382,42 @@ plugin_destroy (struct plugin *self) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +struct input_hook +{ + LIST_HEADER (struct input_hook) + + struct input_hook_vtable *vtable; ///< Methods + int priority; ///< The lesser the sooner +}; + +struct input_hook_vtable +{ + /// Takes over the ownership of "input", returns either NULL if input + /// was thrown away, or a possibly modified version of it + char *(*filter) (struct input_hook *self, + struct buffer *buffer, char *input); +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +struct irc_hook +{ + LIST_HEADER (struct irc_hook) + + struct irc_hook_vtable *vtable; ///< Methods + int priority; ///< The lesser the sooner +}; + +struct irc_hook_vtable +{ + /// Takes over the ownership of "message", returns either NULL if message + /// was thrown away, or a possibly modified version of it + char *(*filter) (struct irc_hook *self, + struct server *server, char *message); +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + struct app_context { bool no_colors; ///< Disable attribute printing @@ -1443,6 +1479,8 @@ struct app_context int terminal_suspended; ///< Terminal suspension level struct plugin *plugins; ///< Loaded plugins + struct input_hook *input_hooks; ///< Input hooks + struct irc_hook *irc_hooks; ///< IRC hooks } *g_ctx; @@ -4066,8 +4104,54 @@ on_irc_autojoin_timeout (void *user_data) // --- Server I/O -------------------------------------------------------------- +static char * +irc_process_hooks (struct server *s, char *input) +{ + log_server_debug (s, "#a>> \"#S\"#r", ATTR_JOIN, input); + LIST_FOR_EACH (struct irc_hook, hook, s->ctx->irc_hooks) + { + char *processed = hook->vtable->filter (hook, s, input); + if (input == processed) + continue; + + if (processed == NULL) + { + log_server_debug (s, "#a>= #s#r", ATTR_JOIN, "thrown away by hook"); + return NULL; + } + + log_server_debug (s, "#a>= \"#S\"#r", ATTR_JOIN, (input = processed)); + } + return input; +} + static void irc_process_message - (const struct irc_message *msg, const char *raw, void *user_data); + (const struct irc_message *msg, struct server *s); + +static void +irc_process_buffer_custom (struct server *s, struct str *buf) +{ + const char *start = buf->str, *end = start + buf->len; + for (const char *p = start; p + 1 < end; p++) + { + // Split the input on newlines + if (p[0] != '\r' || p[1] != '\n') + continue; + + char *processed = irc_process_hooks (s, xstrndup (start, p - start)); + start = p + 2; + if (!processed) + continue; + + struct irc_message msg; + irc_parse_message (&msg, processed); + irc_process_message (&msg, s); + irc_free_message (&msg); + + free (processed); + } + str_remove_slice (buf, 0, start - buf->str); +} static enum transport_io_result irc_try_read (struct server *s) @@ -4081,7 +4165,7 @@ irc_try_read (struct server *s) return TRANSPORT_IO_ERROR; } if (s->read_buffer.len) - irc_process_buffer (&s->read_buffer, irc_process_message, s); + irc_process_buffer_custom (s, &s->read_buffer); return result; } @@ -5969,7 +6053,7 @@ irc_try_parse_welcome_for_userhost (struct server *s, const char *m) } static bool process_input_utf8 - (struct app_context *, struct buffer *, char *, int); + (struct app_context *, struct buffer *, const char *, int); static void irc_on_registered (struct server *s, const char *nickname) @@ -5988,10 +6072,7 @@ irc_on_registered (struct server *s, const char *nickname) if (command) { log_server_debug (s, "Executing \"#s\"", command); - - char *copy = xstrdup (command); - process_input_utf8 (s->ctx, s->buffer, copy, 0); - free (copy); + process_input_utf8 (s->ctx, s->buffer, command, 0); } int64_t command_delay = get_config_integer (s->config, "command_delay"); @@ -6609,13 +6690,8 @@ irc_process_numeric (struct server *s, } static void -irc_process_message (const struct irc_message *msg, - const char *raw, void *user_data) +irc_process_message (const struct irc_message *msg, struct server *s) { - struct server *s = user_data; - - log_server_debug (s, "#a>> \"#S\"#r", ATTR_JOIN, raw); - struct irc_handler key = { .name = msg->command }; struct irc_handler *handler = bsearch (&key, g_irc_handlers, N_ELEMENTS (g_irc_handlers), sizeof key, irc_handler_cmp_by_name); @@ -7135,6 +7211,73 @@ server_rename (struct app_context *ctx, struct server *s, const char *new_name) } } +// --- Ordered linked lists ---------------------------------------------------- + +// This is a bit ugly since there's no way to force a guarantee that the list +// members are going to be the first ones in the structure, plus insertion is +// O(n), however I don't currently posses any better ordered data structure + +struct list_header +{ + LIST_HEADER (struct list_header) +}; + +static struct list_header * +list_insert_ordered (struct list_header *list, struct list_header *item, + bool (*less) (const void *, const void *)) +{ + // Corner cases: list is empty or we precede everything + if (!list || less (item, list)) + { + LIST_PREPEND (list, item); + return list; + } + + // Otherwise fast-worward to the last entry that precedes us + struct list_header *before = list; + while (before->next && less (before->next, item)) + before = before->next; + + // And link ourselves in between it and its successor + if ((item->next = before->next)) + item->next->prev = item; + before->next = item; + item->prev = before; + return list; +} + +// --- Hooks ------------------------------------------------------------------- + +static bool +input_hook_less (const void *a, const void *b) +{ + return ((const struct input_hook *) a)->priority + < ((const struct input_hook *) b)->priority; +} + +static void +input_hook_insert (struct app_context *ctx, struct input_hook *hook) +{ + ctx->input_hooks = (struct input_hook *) list_insert_ordered ( + (struct list_header *) ctx->input_hooks, + (struct list_header *) hook, input_hook_less); +} + +static bool +irc_hook_less (const void *a, const void *b) +{ + return ((const struct input_hook *) a)->priority + < ((const struct input_hook *) b)->priority; +} + +static void +irc_hook_insert (struct app_context *ctx, struct irc_hook *hook) +{ + ctx->irc_hooks = (struct irc_hook *) list_insert_ordered ( + (struct list_header *) ctx->irc_hooks, + (struct list_header *) hook, irc_hook_less); +} + // --- Lua --------------------------------------------------------------------- #ifdef HAVE_LUA @@ -8892,7 +9035,7 @@ process_alias (struct app_context *ctx, struct buffer *buffer, } static bool -process_input_utf8 (struct app_context *ctx, struct buffer *buffer, +process_input_utf8_posthook (struct app_context *ctx, struct buffer *buffer, char *input, int alias_level) { if (*input != '/' || *++input == '/') @@ -8920,6 +9063,41 @@ process_input_utf8 (struct app_context *ctx, struct buffer *buffer, return result; } +static char * +process_input_hooks (struct app_context *ctx, struct buffer *buffer, + char *input) +{ + LIST_FOR_EACH (struct input_hook, hook, ctx->input_hooks) + { + char *processed = hook->vtable->filter (hook, buffer, input); + if (input == processed) + continue; + + if (processed == NULL) + { + log_global_debug (ctx, "Input thrown away by hook"); + return NULL; + } + + log_global_debug (ctx, + "Input transformed to \"#s\"#r", (input = processed)); + } + return input; +} + +static bool +process_input_utf8 (struct app_context *ctx, struct buffer *buffer, + const char *input, int alias_level) +{ + // Note that this also gets called on expanded aliases, + // which might or might not be desirable (we can forward "alias_level") + char *processed = process_input_hooks (ctx, buffer, xstrdup (input)); + bool result = !processed + || process_input_utf8_posthook (ctx, buffer, processed, alias_level); + free (processed); + return result; +} + static void process_input (struct app_context *ctx, char *user_input) {