Mercurial > irccd
view irccd/js-plugin.c @ 1105:96c5f34247d2
misc: remove remnant of GNUmakefile
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 20 Oct 2021 14:15:23 +0200 |
parents | 8f26ee9cc6dd |
children | ae8b91ec4e4a |
line wrap: on
line source
/* * jsapi-plugin.c -- Javascript plugins * * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <sys/stat.h> #include <assert.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <stdarg.h> #include <string.h> #include <unistd.h> #include <irccd/channel.h> #include <irccd/config.h> #include <irccd/event.h> #include <irccd/log.h> #include <irccd/plugin.h> #include <irccd/server.h> #include <irccd/util.h> #include "js-plugin.h" #include "jsapi-chrono.h" #include "jsapi-directory.h" #include "jsapi-file.h" #include "jsapi-hook.h" #include "jsapi-irccd.h" #include "jsapi-logger.h" #include "jsapi-plugin.h" #include "jsapi-rule.h" #include "jsapi-server.h" #include "jsapi-system.h" #include "jsapi-timer.h" #include "jsapi-unicode.h" #include "jsapi-util.h" struct self { struct irc_plugin plugin; duk_context *ctx; char location[PATH_MAX]; char **options; char **templates; char **paths; char *license; char *version; char *author; char *description; }; static void freelist(char **table) { if (!table) return; for (char **p = table; *p; ++p) free(*p); free(table); } static char * metadata(duk_context *ctx, const char *name) { char *ret = NULL; duk_get_global_string(ctx, "info"); if (duk_get_type(ctx, -1) == DUK_TYPE_OBJECT) { duk_get_prop_string(ctx, -1, name); if (duk_get_type(ctx, -1) == DUK_TYPE_STRING) ret = irc_util_strdup(duk_get_string(ctx, -1)); duk_pop(ctx); } duk_pop(ctx); return ret ? ret : irc_util_strdup("unknown"); } static void push_modes(duk_context *ctx, char **modes) { size_t i = 0; duk_push_array(ctx); for (char **mode = modes; mode && *mode; ++mode) { duk_push_string(ctx, *mode); duk_put_prop_index(ctx, -2, i++); } } static void push_names(duk_context *ctx, const struct irc_event *ev) { const char *token; char *p = ev->names.names; duk_push_array(ctx); for (size_t i = 0; (token = strtok_r(p, " ", &p)); ++i) { irc_server_strip(ev->server, &token); duk_push_string(ctx, token); duk_put_prop_index(ctx, -2, i); } } static void push_whois(duk_context *ctx, const struct irc_event *ev) { duk_push_object(ctx); duk_push_string(ctx, ev->whois.nickname); duk_put_prop_string(ctx, -2, "nickname"); duk_push_string(ctx, ev->whois.username); duk_put_prop_string(ctx, -2, "username"); duk_push_string(ctx, ev->whois.realname); duk_put_prop_string(ctx, -2, "realname"); duk_push_string(ctx, ev->whois.hostname); duk_put_prop_string(ctx, -2, "hostname"); duk_push_array(ctx); for (size_t i = 0; i < ev->whois.channelsz; ++i) { duk_push_object(ctx); duk_push_string(ctx, ev->whois.channels[i].name); duk_put_prop_string(ctx, -2, "channel"); duk_push_int(ctx, ev->whois.channels[i].modes); duk_put_prop_string(ctx, -2, "modes"); duk_put_prop_index(ctx, -2, i); } duk_put_prop_string(ctx, -2, "channels"); } static void log_trace(struct self *self) { char *stack, *token, *p; int linenumber; duk_get_prop_string(self->ctx, -1, "stack"); stack = strdup(duk_opt_string(self->ctx, -1, "")); duk_pop(self->ctx); duk_get_prop_string(self->ctx, -1, "lineNumber"); linenumber = duk_get_int(self->ctx, -1); duk_pop(self->ctx); irc_log_warn("plugin %s: %s:%d", self->plugin.name, self->location, linenumber); /* We can't put a '\n' in irc_log_warn so loop for them. */ for (p = stack; *stack && (token = strtok_r(p, "\n", &p)); ) irc_log_warn("plugin %s: %s", self->plugin.name, token); free(stack); } static const char ** get_table(duk_context *ctx, const char *name, char ***ptable) { char **list = NULL; size_t listsz = 0; duk_get_global_string(ctx, name); duk_enum(ctx, -1, 0); for (size_t i = 0; duk_next(ctx, -1, 1); ++i) { list = irc_util_reallocarray(list, ++listsz, sizeof (char *)); list[i] = irc_util_strdup(duk_to_string(ctx, -2)); duk_pop_n(ctx, 2); } duk_pop_n(ctx, 2); /* Add a NULL sentinel value. */ list = irc_util_reallocarray(list, listsz + 1, sizeof (char *)); list[listsz] = NULL; freelist(*ptable); *ptable = list; return (const char **)list; } static void set_key_value(duk_context *ctx, const char *table, const char *key, const char *value) { duk_get_global_string(ctx, table); duk_push_string(ctx, value); duk_put_prop_string(ctx, -2, key); duk_pop(ctx); } static const char * get_value(duk_context *ctx, const char *table, const char *key) { const char *ret; duk_get_global_string(ctx, table); duk_get_prop_string(ctx, -1, key); ret = duk_to_string(ctx, -1); duk_pop_n(ctx, 2); return ret; } static void set_template(struct irc_plugin *plg, const char *key, const char *value) { struct self *js = plg->data; set_key_value(js->ctx, JSAPI_PLUGIN_PROP_TEMPLATES, key, value); } static const char * get_template(struct irc_plugin *plg, const char *key) { struct self *js = plg->data; return get_value(js->ctx, JSAPI_PLUGIN_PROP_TEMPLATES, key); } static const char ** get_templates(struct irc_plugin *plg) { struct self *js = plg->data; return get_table(js->ctx, JSAPI_PLUGIN_PROP_TEMPLATES, &js->templates); } static void set_path(struct irc_plugin *plg, const char *key, const char *value) { struct self *js = plg->data; set_key_value(js->ctx, JSAPI_PLUGIN_PROP_PATHS, key, value); } static const char * get_path(struct irc_plugin *plg, const char *key) { struct self *js = plg->data; return get_value(js->ctx, JSAPI_PLUGIN_PROP_PATHS, key); } static const char ** get_paths(struct irc_plugin *plg) { struct self *js = plg->data; return get_table(js->ctx, JSAPI_PLUGIN_PROP_PATHS, &js->paths); } static void set_option(struct irc_plugin *plg, const char *key, const char *value) { struct self *js = plg->data; set_key_value(js->ctx, JSAPI_PLUGIN_PROP_OPTIONS, key, value); } static const char * get_option(struct irc_plugin *plg, const char *key) { struct self *js = plg->data; return get_value(js->ctx, JSAPI_PLUGIN_PROP_OPTIONS, key); } static const char ** get_options(struct irc_plugin *plg) { struct self *js = plg->data; return get_table(js->ctx, JSAPI_PLUGIN_PROP_OPTIONS, &js->options); } static int vcall(struct irc_plugin *plg, const char *function, const char *fmt, va_list ap) { struct self *self = plg->data; int nargs = 0, ret = 0; duk_get_global_string(self->ctx, function); if (!duk_is_function(self->ctx, -1)) { duk_pop(self->ctx); return ret; } for (const char *f = fmt; *f; ++f) { void (*push)(duk_context *, void *); switch (*f) { case 'S': jsapi_server_push(self->ctx, va_arg(ap, struct irc_server *)); break; case 's': duk_push_string(self->ctx, va_arg(ap, const char *)); break; case 'x': push = va_arg(ap, void (*)(duk_context *, void *)); push(self->ctx, va_arg(ap, void *)); break; default: continue; } ++nargs; } if (duk_pcall(self->ctx, nargs) != 0) { log_trace(plg->data); ret = -1; } duk_pop(self->ctx); return ret; } static int call(struct irc_plugin *plg, const char *function, const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); ret = vcall(plg, function, fmt, ap); va_end(ap); return ret; } static void handle(struct irc_plugin *plg, const struct irc_event *ev) { (void)plg; (void)ev; switch (ev->type) { case IRC_EVENT_COMMAND: call(plg, "onCommand", "Ss ss", ev->server, ev->message.origin, ev->message.channel, ev->message.message); break; case IRC_EVENT_CONNECT: call(plg, "onConnect", "S", ev->server); break; case IRC_EVENT_DISCONNECT: call(plg, "onDisconnect", "S", ev->server); break; case IRC_EVENT_INVITE: call(plg, "onInvite", "Ss s", ev->server, ev->invite.origin, ev->invite.channel); break; case IRC_EVENT_JOIN: call(plg, "onJoin", "Ss s", ev->server, ev->join.origin, ev->join.channel); break; case IRC_EVENT_KICK: call(plg, "onKick", "Ss sss", ev->server, ev->kick.origin, ev->kick.channel, ev->kick.target, ev->kick.reason); break; case IRC_EVENT_ME: call(plg, "onMe", "Ss ss", ev->server, ev->message.origin, ev->message.channel, ev->message.message); break; case IRC_EVENT_MESSAGE: call(plg, "onMessage", "Ss ss", ev->server, ev->message.origin, ev->message.channel, ev->message.message); break; case IRC_EVENT_MODE: call(plg, "onMode", "Ss ssx", ev->server, ev->mode.origin, ev->mode.channel, ev->mode.mode, push_modes, ev->mode.args); break; case IRC_EVENT_NAMES: call(plg, "onNames", "Ss x", ev->server, ev->names.channel, push_names, ev); break; case IRC_EVENT_NICK: call(plg, "onNick", "Ss s", ev->server, ev->nick.origin, ev->nick.nickname); break; case IRC_EVENT_NOTICE: call(plg, "onNotice", "Ss ss", ev->server, ev->notice.origin, ev->notice.channel, ev->notice.notice); break; case IRC_EVENT_PART: call(plg, "onPart", "Ss ss", ev->server, ev->part.origin, ev->part.channel, ev->part.reason); break; case IRC_EVENT_TOPIC: call(plg, "onTopic", "Ss ss", ev->server, ev->topic.origin, ev->topic.channel, ev->topic.topic); break; case IRC_EVENT_WHOIS: call(plg, "onWhois", "Sx", ev->server, push_whois, ev); break; default: break; } } static char * eat(const char *path) { int fd = -1; char *ret = NULL; struct stat st; if ((fd = open(path, O_RDONLY)) < 0) goto err; if (fstat(fd, &st) < 0) goto err; if (!(ret = calloc(1, st.st_size + 1))) goto err; if (read(fd, ret, st.st_size) != st.st_size) goto err; close(fd); return ret; err: if (fd != -1) close(fd); free(ret); return NULL; } static void * wrap_malloc(void *udata, size_t size) { (void)udata; return irc_util_malloc(size); } static void * wrap_realloc(void *udata, void *ptr, size_t size) { (void)udata; return irc_util_realloc(ptr, size); } static void wrap_free(void *udata, void *ptr) { (void)udata; free(ptr); } static struct self * init(const char *name, const char *path, const char *script) { struct self *js; js = irc_util_calloc(1, sizeof (*js)); js->ctx = duk_create_heap(wrap_malloc, wrap_realloc, wrap_free, NULL, NULL); irc_util_strlcpy(js->plugin.name, name, sizeof (js->plugin.name)); /* Copy path because Duktape has no notions of it. */ irc_util_strlcpy(js->location, path, sizeof (js->location)); /* Tables used to retrieve data. */ duk_push_object(js->ctx); duk_put_global_string(js->ctx, JSAPI_PLUGIN_PROP_OPTIONS); duk_push_object(js->ctx); duk_put_global_string(js->ctx, JSAPI_PLUGIN_PROP_TEMPLATES); duk_push_object(js->ctx); duk_put_global_string(js->ctx, JSAPI_PLUGIN_PROP_PATHS); /* Load Javascript APIs. */ jsapi_load(js->ctx); jsapi_chrono_load(js->ctx); jsapi_directory_load(js->ctx); jsapi_file_load(js->ctx); jsapi_hook_load(js->ctx); jsapi_logger_load(js->ctx); jsapi_plugin_load(js->ctx, &js->plugin); jsapi_rule_load(js->ctx); jsapi_server_load(js->ctx); jsapi_system_load(js->ctx); jsapi_timer_load(js->ctx); jsapi_unicode_load(js->ctx); jsapi_util_load(js->ctx); /* Finally execute the script. */ if (duk_peval_string(js->ctx, script) != 0) { log_trace(js); duk_destroy_heap(js->ctx); free(js); return NULL; } js->plugin.license = js->license = metadata(js->ctx, "license"); js->plugin.version = js->version = metadata(js->ctx, "version"); js->plugin.author = js->author = metadata(js->ctx, "author"); js->plugin.description = js->description = metadata(js->ctx, "summary"); return js; } static int load(struct irc_plugin *plg) { return call(plg, "onLoad", ""); } static void reload(struct irc_plugin *plg) { call(plg, "onReload", ""); } static void unload(struct irc_plugin *plg) { call(plg, "onUnload", ""); } static void finish(struct irc_plugin *plg) { struct self *self = plg->data; if (self->ctx) duk_destroy_heap(self->ctx); freelist(self->options); freelist(self->templates); freelist(self->paths); free(self->license); free(self->version); free(self->author); free(self->description); free(self); } static struct irc_plugin * wrap_open(struct irc_plugin_loader *ldr, const char *name, const char *path) { (void)ldr; return js_plugin_open(name, path); } duk_context * js_plugin_get_context(struct irc_plugin *js) { struct self *self = js->data; return self->ctx; } struct irc_plugin * js_plugin_open(const char *name, const char *path) { assert(path); char *script = NULL; struct self *self; /* * Duktape can't open script from file path so we need to read the * whole script at once. */ if (!(script = eat(path))) { if (errno != ENOENT) irc_log_warn("plugin: %s: %s", path, strerror(errno)); return NULL; } /* Init already log errors. */ if (!(self = init(name, path, script))) { free(script); return NULL; } self->plugin.data = self; self->plugin.set_template = set_template; self->plugin.get_template = get_template; self->plugin.get_templates = get_templates; self->plugin.set_path = set_path; self->plugin.get_path = get_path; self->plugin.get_paths = get_paths; self->plugin.set_option = set_option; self->plugin.get_option = get_option; self->plugin.get_options = get_options; self->plugin.load = load; self->plugin.reload = reload; self->plugin.unload = unload; self->plugin.handle = handle; self->plugin.finish = finish; /* No longer needed. */ free(script); return &self->plugin; } struct irc_plugin_loader * js_plugin_loader_new(void) { struct irc_plugin_loader *ldr; ldr = irc_util_calloc(1, sizeof (*ldr)); ldr->open = wrap_open; irc_util_strlcpy(ldr->extensions, "js", sizeof (ldr->extensions)); irc_util_strlcpy(ldr->paths, IRCCD_LIBDIR "/irccd", sizeof (ldr->paths)); return ldr; }