summaryrefslogtreecommitdiffstats
path: root/src/core/special-vars.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/special-vars.c')
-rw-r--r--src/core/special-vars.c843
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);
+}