diff options
Diffstat (limited to 'src/core/special-vars.c')
-rw-r--r-- | src/core/special-vars.c | 843 |
1 files changed, 843 insertions, 0 deletions
diff --git a/src/core/special-vars.c b/src/core/special-vars.c new file mode 100644 index 0000000..802fcb3 --- /dev/null +++ b/src/core/special-vars.c @@ -0,0 +1,843 @@ +/* + special-vars.c : irssi + + Copyright (C) 2000 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/expandos.h> +#include <irssi/src/core/misc.h> +#include <irssi/src/core/refstrings.h> +#include <irssi/src/core/servers.h> +#include <irssi/src/core/settings.h> +#include <irssi/src/core/signals.h> +#include <irssi/src/core/special-vars.h> +#include <irssi/src/core/utf8.h> + +#define isvarchar(c) \ + (i_isalnum(c) || (c) == '_') + +#define isarg(c) \ + (i_isdigit(c) || (c) == '*' || (c) == '~' || (c) == '-') + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* fuzzer should not exhaust memory here */ +#define ALIGN_MAX 512 +#else +#define ALIGN_MAX 222488 +#endif + +static SPECIAL_HISTORY_FUNC history_func = NULL; +static GSList *special_collector; +static GSList *special_cache; + +static char *get_argument(char **cmd, char **arglist) +{ + GString *str; + char *ret; + int max, arg, argcount; + + arg = 0; + max = -1; + + argcount = arglist == NULL ? 0 : g_strv_length(arglist); + + if (**cmd == '*') { + /* get all arguments */ + } else if (**cmd == '~') { + /* get last argument */ + arg = max = argcount-1; + } else { + if (i_isdigit(**cmd)) { + /* first argument */ + arg = max = (**cmd)-'0'; + (*cmd)++; + } + + if (**cmd == '-') { + /* get more than one argument */ + (*cmd)++; + if (!i_isdigit(**cmd)) + max = -1; /* get all the rest */ + else { + max = (**cmd)-'0'; + (*cmd)++; + } + } + (*cmd)--; + } + + str = g_string_new(NULL); + while (arg >= 0 && arg < argcount && (arg <= max || max == -1)) { + g_string_append(str, arglist[arg]); + g_string_append_c(str, ' '); + arg++; + } + if (str->len > 0) g_string_truncate(str, str->len-1); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +static char *get_long_variable_value(const char *key, SERVER_REC *server, + void *item, int *free_ret) +{ + EXPANDO_FUNC func; + const char *ret; + SETTINGS_REC *rec; + + *free_ret = FALSE; + + /* expando? */ + func = expando_find_long(key); + if (func != NULL) { + current_expando = key; + return func(server, item, free_ret); + } + + /* internal setting? */ + rec = settings_get_record(key); + if (rec != NULL) { + *free_ret = TRUE; + return settings_get_print(rec); + } + + /* environment variable? */ + ret = g_getenv(key); + if (ret != NULL) + return (char *) ret; + + return NULL; +} + +static gboolean cache_find(GSList **cache, const char *var, char **ret) +{ + GSList *tmp; + GSList *prev = NULL; + + if (cache == NULL) + return FALSE; + + for (tmp = *cache; tmp;) { + if (g_strcmp0(var, tmp->data) == 0) { + *ret = tmp->next->data; + if (prev != NULL) + prev->next->next = tmp->next->next; + else + *cache = tmp->next->next; + + g_slist_free_1(tmp->next); + g_slist_free_1(tmp); + return TRUE; + } + prev = tmp; + tmp = tmp->next->next; + } + return FALSE; +} + +static gboolean cache_find_char(GSList **cache, char var, char **ret) +{ + char varn[] = { var, '\0' }; + return cache_find(cache, varn, ret); +} + +static char *get_long_variable(char **cmd, SERVER_REC *server, void *item, int *free_ret, + int getname, GSList **collector, GSList **cache) +{ + char *start, *var, *ret; + + /* get variable name */ + start = *cmd; + while (isvarchar((*cmd)[1])) (*cmd)++; + + var = g_strndup(start, (int) (*cmd-start)+1); + if (getname) { + *free_ret = TRUE; + return var; + } + if (cache_find(cache, var, &ret)) { + g_free(var); + return ret; + } + ret = get_long_variable_value(var, server, item, free_ret); + if (collector != NULL) { + *collector = g_slist_prepend(*collector, g_strdup(ret)); + *collector = g_slist_prepend(*collector, i_refstr_intern(var)); + } + g_free(var); + return ret; +} + +/* return the value of the variable found from `cmd'. + if 'getname' is TRUE, return the name of the variable instead it's value */ +static char *get_variable(char **cmd, SERVER_REC *server, void *item, char **arglist, int *free_ret, + int *arg_used, int getname, GSList **collector, GSList **cache) +{ + EXPANDO_FUNC func; + + if (isarg(**cmd)) { + /* argument */ + *free_ret = TRUE; + if (arg_used != NULL) *arg_used = TRUE; + return getname ? g_strdup_printf("%c", **cmd) : + get_argument(cmd, arglist); + } + + if (i_isalpha(**cmd) && isvarchar((*cmd)[1])) { + /* long variable name.. */ + return get_long_variable(cmd, server, item, free_ret, getname, collector, cache); + } + + /* single character variable. */ + if (getname) { + *free_ret = TRUE; + return g_strdup_printf("%c", **cmd); + } + *free_ret = FALSE; + { + char *ret; + if (cache_find_char(cache, **cmd, &ret)) { + return ret; + } + } + func = expando_find_char(**cmd); + if (func == NULL) + return NULL; + else { + char str[2]; + char *ret; + + str[0] = **cmd; str[1] = '\0'; + current_expando = str; + ret = func(server, item, free_ret); + if (**cmd != 'Z' && collector != NULL) { + *collector = g_slist_prepend(*collector, g_strdup(ret)); + *collector = g_slist_prepend(*collector, i_refstr_intern(str)); + } + return ret; + } +} + +static char *get_history(char **cmd, void *item, int *free_ret) +{ + char *start, *text, *ret; + + /* get variable name */ + start = ++(*cmd); + while (**cmd != '\0' && **cmd != '!') (*cmd)++; + + if (history_func == NULL) + ret = NULL; + else { + text = g_strndup(start, (int) (*cmd-start)); + ret = history_func(text, item, free_ret); + g_free(text); + } + + if (**cmd == '\0') (*cmd)--; + return ret; +} + +static char *get_special_value(char **cmd, SERVER_REC *server, void *item, char **arglist, + int *free_ret, int *arg_used, int flags, GSList **collector, + GSList **cache) +{ + char command, *value, *p; + int len; + + if ((flags & PARSE_FLAG_ONLY_ARGS) && !isarg(**cmd)) { + *free_ret = TRUE; + return g_strdup_printf("$%c", **cmd); + } + + if (**cmd == '!') { + /* find text from command history */ + if (flags & PARSE_FLAG_GETNAME) + return "!"; + + return get_history(cmd, item, free_ret); + } + + command = 0; + if (**cmd == '#' || **cmd == '@') { + command = **cmd; + if ((*cmd)[1] != '\0') + (*cmd)++; + else { + /* default to $* */ + char *temp_cmd = "*"; + + if (flags & PARSE_FLAG_GETNAME) + return "*"; + + *free_ret = TRUE; + return get_argument(&temp_cmd, arglist); + } + } + + value = get_variable(cmd, server, item, arglist, free_ret, arg_used, + flags & PARSE_FLAG_GETNAME, collector, cache); + + if (flags & PARSE_FLAG_GETNAME) + return value; + + if (command == '#') { + /* number of words */ + if (value == NULL || *value == '\0') { + if (value != NULL && *free_ret) { + g_free(value); + *free_ret = FALSE; + } + return "0"; + } + + len = 1; + for (p = value; *p != '\0'; p++) { + if (*p == ' ' && (p[1] != ' ' && p[1] != '\0')) + len++; + } + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return g_strdup_printf("%d", len); + } + + if (command == '@') { + /* number of characters */ + if (value == NULL) return "0"; + + len = strlen(value); + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return g_strdup_printf("%d", len); + } + + return value; +} + +/* get alignment arguments (inside the []) */ +static int get_alignment_args(char **data, int *align, int *flags, char *pad) +{ + char *str; + char *endptr; + guint align_; + + *align = 0; + *flags = ALIGN_CUT|ALIGN_PAD; + *pad = ' '; + + /* '!' = don't cut, '-' = right padding */ + str = *data; + while (*str != '\0' && *str != ']' && !i_isdigit(*str)) { + if (*str == '!') + *flags &= ~ALIGN_CUT; + else if (*str == '-') + *flags |= ALIGN_RIGHT; + else if (*str == '.') + *flags &= ~ALIGN_PAD; + str++; + } + if (!i_isdigit(*str)) + return FALSE; /* expecting number */ + + /* get the alignment size */ + if (!parse_uint(str, &endptr, 10, &align_)) { + return FALSE; + } + /* alignment larger than supported */ + if (align_ > ALIGN_MAX) { + return FALSE; + } + str = endptr; + *align = align_; + + /* get the pad character */ + while (*str != '\0' && *str != ']') { + *pad = *str; + str++; + } + + if (*str++ != ']') return FALSE; + + *data = str; + return TRUE; +} + +/* return the aligned text */ +char *get_alignment(const char *text, int align, int flags, char pad) +{ + GString *str; + char *ret; + int policy; + unsigned int cut_bytes; + + g_return_val_if_fail(text != NULL, NULL); + + policy = string_policy(text); + + str = g_string_new(text); + + /* cut */ + if ((flags & ALIGN_CUT) && align > 0 && string_width(text, policy) > align) { + string_chars_for_width(text, policy, align, &cut_bytes); + g_string_truncate(str, cut_bytes); + } + + /* add pad characters */ + if (flags & ALIGN_PAD) { + int pad_len = align - string_width(str->str, policy); + if (pad_len > 0) { + char *pad_full = g_strnfill(pad_len, pad); + if (flags & ALIGN_RIGHT) + g_string_prepend(str, pad_full); + else + g_string_append(str, pad_full); + g_free(pad_full); + } + } + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +/* Parse and expand text after '$' character. return value has to be + g_free()'d if `free_ret' is TRUE. */ +char *parse_special(char **cmd, SERVER_REC *server, void *item, + char **arglist, int *free_ret, int *arg_used, int flags) +{ + static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */ + char command, *value; + + char align_pad = '\0'; + int align = 0, align_flags = 0; + + char *nest_value; + int brackets, nest_free; + + *free_ret = FALSE; + if (**cmd == '\0') + return NULL; + + command = **cmd; (*cmd)++; + switch (command) { + case '[': + /* alignment */ + if (!get_alignment_args(cmd, &align, &align_flags, + &align_pad) || **cmd == '\0') { + (*cmd)--; + return NULL; + } + break; + default: + command = 0; + (*cmd)--; + } + + nest_free = FALSE; nest_value = NULL; +#if 0 /* this code is disabled due to security issues until it is fixed */ + if (**cmd == '(' && (*cmd)[1] != '\0') { + /* subvariable */ + int toplevel = nested_orig_cmd == NULL; + + if (toplevel) nested_orig_cmd = cmd; + (*cmd)++; + if (**cmd != '$') { + /* ... */ + nest_value = *cmd; + } else { + (*cmd)++; + nest_value = parse_special(cmd, server, item, arglist, + &nest_free, arg_used, + flags); + } + + if (nest_value == NULL || *nest_value == '\0') + return NULL; + + while ((*nested_orig_cmd)[1] != '\0') { + (*nested_orig_cmd)++; + if (**nested_orig_cmd == ')') + break; + } + cmd = &nest_value; + + if (toplevel) nested_orig_cmd = NULL; + } +#else + if (nested_orig_cmd) nested_orig_cmd = NULL; +#endif + + if (**cmd != '{') + brackets = FALSE; + else { + /* special value is inside {...} (foo${test}bar -> fooXXXbar) */ + if ((*cmd)[1] == '\0') + return NULL; + (*cmd)++; + brackets = TRUE; + } + + value = get_special_value(cmd, server, item, arglist, free_ret, arg_used, flags, + special_collector != NULL ? special_collector->data : NULL, + &special_cache); + if (**cmd == '\0') + g_error("parse_special() : buffer overflow!"); + + if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY)) + *arg_used = TRUE; + + if (brackets) { + while (**cmd != '}' && (*cmd)[1] != '\0') + (*cmd)++; + } + + if (nest_free) g_free(nest_value); + + if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) { + /* alignment */ + char *p; + + if (value == NULL) return ""; + + p = get_alignment(value, align, align_flags, align_pad); + if (*free_ret) g_free(value); + + *free_ret = TRUE; + return p; + } + + return value; +} + +static void gstring_append_escaped(GString *str, const char *text, int flags) +{ + char esc[4], *escpos; + + escpos = esc; + if (flags & PARSE_FLAG_ESCAPE_VARS) + *escpos++ = '%'; + if (flags & PARSE_FLAG_ESCAPE_THEME) { + *escpos++ = '{'; + *escpos++ = '}'; + } + + if (escpos == esc) { + g_string_append(str, text); + return; + } + + *escpos = '\0'; + while (*text != '\0') { + for (escpos = esc; *escpos != '\0'; escpos++) { + if (*text == *escpos) { + g_string_append_c(str, '%'); + break; + } + } + g_string_append_c(str, *text); + text++; + } +} + +/* parse the whole string. $ and \ chars are replaced */ +char *parse_special_string(const char *cmd, SERVER_REC *server, void *item, + const char *data, int *arg_used, int flags) +{ + char code, **arglist, *ret; + GString *str; + int need_free, chr; + + g_return_val_if_fail(cmd != NULL, NULL); + g_return_val_if_fail(data != NULL, NULL); + + /* create the argument list */ + arglist = g_strsplit(data, " ", -1); + + if (arg_used != NULL) *arg_used = FALSE; + code = 0; + str = g_string_new(NULL); + while (*cmd != '\0') { + if (code == '\\') { + if (*cmd == ';') + g_string_append_c(str, ';'); + else { + chr = expand_escape(&cmd); + g_string_append_c(str, chr != -1 ? chr : *cmd); + } + code = 0; + } else if (code == '$') { + char *ret; + + ret = parse_special((char **) &cmd, server, item, + arglist, &need_free, arg_used, + flags); + if (ret != NULL) { + gstring_append_escaped(str, ret, flags); + if (need_free) g_free(ret); + } + code = 0; + } else { + if (*cmd == '\\' || *cmd == '$') + code = *cmd; + else + g_string_append_c(str, *cmd); + } + + cmd++; + } + g_strfreev(arglist); + + ret = str->str; + g_string_free(str, FALSE); + return ret; +} + +#define is_split_char(str, start) \ + ((str)[0] == ';' && ((start) == (str) || \ + ((str)[-1] != '\\' && (str)[-1] != '$'))) + +/* execute the commands in string - commands can be split with ';' */ +void eval_special_string(const char *cmd, const char *data, + SERVER_REC *server, void *item) +{ + const char *cmdchars; + char *orig, *str, *start, *ret; + int arg_used, arg_used_ever; + GSList *commands; + + commands = NULL; + arg_used_ever = FALSE; + cmdchars = settings_get_str("cmdchars"); + + /* get a list of all the commands to run */ + orig = start = str = g_strdup(cmd); + do { + if (is_split_char(str, start)) { + *str++ = '\0'; + while (*str == ' ') str++; + } else if (*str != '\0') { + str++; + continue; + } + + ret = parse_special_string(start, server, item, + data, &arg_used, 0); + if (*ret != '\0') { + if (arg_used) arg_used_ever = TRUE; + + if (strchr(cmdchars, *ret) == NULL) { + /* no command char - let's put it there.. */ + char *old = ret; + + ret = g_strdup_printf("%c%s", *cmdchars, old); + g_free(old); + } + commands = g_slist_append(commands, ret); + } + start = str; + } while (*start != '\0'); + + /* run the command, if no arguments were ever used, append all of them + after each command */ + while (commands != NULL) { + ret = commands->data; + + if (!arg_used_ever && *data != '\0') { + char *old = ret; + + ret = g_strconcat(old, " ", data, NULL); + g_free(old); + } + + if (server != NULL) + server_ref(server); + signal_emit("send command", 3, ret, server, item); + + if (server != NULL && !server_unref(server)) { + /* the server was destroyed */ + server = NULL; + item = NULL; + } + + /* FIXME: window item would need reference counting as well, + eg. "/EVAL win close;say hello" wouldn't work now.. */ + + commands = g_slist_remove(commands, commands->data); + g_free(ret); + } + g_free(orig); +} + +void special_history_func_set(SPECIAL_HISTORY_FUNC func) +{ + history_func = func; +} + +void special_push_collector(GSList **list) +{ + special_collector = g_slist_prepend(special_collector, list); +} + +void special_pop_collector(void) +{ + special_collector = g_slist_delete_link(special_collector, special_collector); +} + +void special_fill_cache(GSList *list) +{ + g_slist_free(special_cache); + special_cache = g_slist_copy(list); +} + +static void update_signals_hash(GHashTable **hash, int *signals) +{ + void *signal_id; + int arg_type; + + if (*hash == NULL) { + *hash = g_hash_table_new((GHashFunc) g_direct_hash, + (GCompareFunc) g_direct_equal); + } + + while (*signals != -1) { + signal_id = GINT_TO_POINTER(*signals); + arg_type = GPOINTER_TO_INT(g_hash_table_lookup(*hash, signal_id)); + if (arg_type != 0 && arg_type != signals[1]) { + /* same signal is used for different purposes .. + not sure if this should ever happen, but change + the argument type to none so it will at least + work. */ + arg_type = EXPANDO_ARG_NONE; + } + + if (arg_type == 0) arg_type = signals[1]; + g_hash_table_insert(*hash, signal_id, + GINT_TO_POINTER(arg_type)); + signals += 2; + } +} + +static void get_signal_hash(void *signal_id, void *arg_type, int **pos) +{ + (*pos)[0] = GPOINTER_TO_INT(signal_id); + (*pos)[1] = GPOINTER_TO_INT(arg_type); + (*pos) += 2; +} + +static int *get_signals_list(GHashTable *hash) +{ + int *signals, *pos; + + if (hash == NULL) { + /* no expandos in text - never needs updating */ + return NULL; + } + + pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1); + g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos); + *pos = -1; + + g_hash_table_destroy(hash); + return signals; + +} + +#define TASK_BIND 1 +#define TASK_UNBIND 2 +#define TASK_GET_SIGNALS 3 + +static int *special_vars_signals_task(const char *text, int funccount, + SIGNAL_FUNC *funcs, int task) +{ + GHashTable *signals; + char *expando; + int need_free, *expando_signals; + + signals = NULL; + while (*text != '\0') { + if (*text == '\\' && text[1] != '\0') { + /* escape */ + text += 2; + } else if (*text == '$' && text[1] != '\0') { + /* expando */ + text++; + expando = parse_special((char **) &text, NULL, NULL, + NULL, &need_free, NULL, + PARSE_FLAG_GETNAME); + if (expando == NULL) + continue; + + switch (task) { + case TASK_BIND: + expando_bind(expando, funccount, funcs); + break; + case TASK_UNBIND: + expando_unbind(expando, funccount, funcs); + break; + case TASK_GET_SIGNALS: + expando_signals = expando_get_signals(expando); + if (expando_signals != NULL) { + update_signals_hash(&signals, + expando_signals); + g_free(expando_signals); + } + break; + } + if (need_free) g_free(expando); + } else { + /* just a char */ + text++; + } + } + + if (task == TASK_GET_SIGNALS) + return get_signals_list(signals); + + return NULL; +} + +void special_vars_add_signals(const char *text, + int funccount, SIGNAL_FUNC *funcs) +{ + special_vars_signals_task(text, funccount, funcs, TASK_BIND); +} + +void special_vars_remove_signals(const char *text, + int funccount, SIGNAL_FUNC *funcs) +{ + special_vars_signals_task(text, funccount, funcs, TASK_UNBIND); +} + +int *special_vars_get_signals(const char *text) +{ + return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS); +} + +void special_vars_init(void) +{ + special_cache = NULL; + special_collector = NULL; +} + +void special_vars_deinit(void) +{ + g_slist_free(special_cache); + g_slist_free(special_collector); +} |