/* * wee-eval.c - evaluate expressions with references to internal vars * * Copyright (C) 2012-2024 Sébastien Helleu * * This file is part of WeeChat, the extensible chat client. * * WeeChat 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 3 of the License, or * (at your option) any later version. * * WeeChat 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 WeeChat. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "weechat.h" #include "wee-eval.h" #include "wee-calc.h" #include "wee-config.h" #include "wee-hashtable.h" #include "wee-hdata.h" #include "wee-hook.h" #include "wee-secure.h" #include "wee-string.h" #include "wee-utf8.h" #include "wee-util.h" #include "../gui/gui-buffer.h" #include "../gui/gui-chat.h" #include "../gui/gui-color.h" #include "../gui/gui-window.h" #include "../plugins/plugin.h" #define EVAL_DEBUG_MSG(level, msg, argz...) \ debug_id = -1; \ if (eval_context->debug_level >= level) \ { \ debug_id = ++(eval_context->debug_id); \ (eval_context->debug_depth)++; \ eval_debug_message_vargs (eval_context, debug_id, msg, ##argz); \ } #define EVAL_DEBUG_RESULT(level, result) \ if (eval_context->debug_level >= level) \ { \ eval_debug_message (eval_context, debug_id, 1, result); \ (eval_context->debug_depth)--; \ } char *eval_logical_ops[EVAL_NUM_LOGICAL_OPS] = { "||", "&&" }; char *eval_comparisons[EVAL_NUM_COMPARISONS] = { "=~", "!~", /* regex */ "==*", "!!*", "=*", "!*", /* string match */ "==-", "!!-", "=-", "!-", /* includes */ "==", "!=", /* equal, not equal */ "<=", "<", ">=", ">", /* less than, greater than */ }; char *eval_range_chars[][2] = { { "digit", EVAL_RANGE_DIGIT }, { "xdigit", EVAL_RANGE_XDIGIT }, { "lower", EVAL_RANGE_LOWER }, { "upper", EVAL_RANGE_UPPER }, { "alpha", EVAL_RANGE_ALPHA }, { "alnum", EVAL_RANGE_ALNUM }, { NULL, NULL }, }; char *eval_replace_vars (const char *expr, struct t_eval_context *eval_context); char *eval_expression_condition (const char *expr, struct t_eval_context *eval_context); /* * Adds a debug message in the debug output. */ void eval_debug_message (struct t_eval_context *eval_context, int debug_id, int result, const char *message) { int i; char str_id[64]; if (*(eval_context->debug_output)[0]) string_dyn_concat (eval_context->debug_output, "\n", -1); /* indentation */ for (i = 1; i < eval_context->debug_depth; i++) { string_dyn_concat (eval_context->debug_output, " ", -1); } /* debug id */ if (debug_id >= 0) { snprintf (str_id, sizeof (str_id), "%d:", debug_id); string_dyn_concat (eval_context->debug_output, str_id, -1); } /* debug message */ if (result) { string_dyn_concat (eval_context->debug_output, "== ", -1); if (message) string_dyn_concat (eval_context->debug_output, "\"", -1); string_dyn_concat (eval_context->debug_output, (message) ? message : "null", -1); if (message) string_dyn_concat (eval_context->debug_output, "\"", -1); } else { string_dyn_concat (eval_context->debug_output, message, -1); } } /* * Adds a debug message in the debug output, with variable arguments. */ void eval_debug_message_vargs (struct t_eval_context *eval_context, int debug_id, const char *message, ...) { weechat_va_format (message); if (vbuffer) { eval_debug_message (eval_context, debug_id, 0, vbuffer); free (vbuffer); } } /* * Checks if a value is true: a value is true if string is non-NULL, non-empty * and different from "0". * * Returns: * 1: value is true * 0: value is false */ int eval_is_true (const char *value) { return (value && value[0] && (strcmp (value, "0") != 0)) ? 1 : 0; } /* * Searches a string in another at same level (skip sub-expressions between * prefix/suffix). * * If escape is 1, the prefix can be escaped with '\' (and then is ignored). * * For example: * eval_strstr_level ("(x || y) || z", "||", eval_context, "(", ")", 0) * will return a pointer on "|| z" (because the first "||" is * in a sub-expression, which is skipped). * * Returns pointer to string found, or NULL if not found. */ const char * eval_strstr_level (const char *string, const char *search, struct t_eval_context *eval_context, const char *extra_prefix, const char *extra_suffix, int escape) { const char *ptr_string; int level, length_search, debug_id; int length_prefix2, length_suffix2; ptr_string = NULL; EVAL_DEBUG_MSG(2, "eval_strstr_level(\"%s\", \"%s\", \"%s\", \"%s\", %d)", string, search, extra_prefix, extra_suffix, escape); if (!string || !search) { ptr_string = NULL; goto end; } length_search = strlen (search); length_prefix2 = (extra_prefix) ? strlen (extra_prefix) : 0; length_suffix2 = (extra_suffix) ? strlen (extra_suffix) : 0; ptr_string = string; level = 0; while (ptr_string[0]) { if (escape && (ptr_string[0] == '\\') && ((ptr_string[1] == eval_context->prefix[0]) || ((length_suffix2 > 0) && ptr_string[1] == extra_prefix[0]))) { ptr_string++; } else if (strncmp (ptr_string, eval_context->prefix, eval_context->length_prefix) == 0) { level++; ptr_string += eval_context->length_prefix; } else if ((length_prefix2 > 0) && (strncmp (ptr_string, extra_prefix, length_prefix2) == 0)) { level++; ptr_string += length_prefix2; } else if (strncmp (ptr_string, eval_context->suffix, eval_context->length_suffix) == 0) { if (level > 0) level--; ptr_string += eval_context->length_suffix; } else if ((length_suffix2 > 0) && (strncmp (ptr_string, extra_suffix, length_suffix2) == 0)) { if (level > 0) level--; ptr_string += length_suffix2; } else if ((level == 0) && (strncmp (ptr_string, search, length_search) == 0)) { goto end; } else { ptr_string++; } } ptr_string = NULL; end: EVAL_DEBUG_RESULT(2, ptr_string); return ptr_string; } /* * Evaluates a condition and returns boolean result: * "0" if false * "1" if true * * Note: result must be freed after use. */ char * eval_string_eval_cond (const char *text, struct t_eval_context *eval_context) { char *tmp; int rc; tmp = eval_expression_condition (text, eval_context); rc = eval_is_true (tmp); if (tmp) free (tmp); return strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE); } /* * Adds range of chars. * * Note: result must be freed after use. */ char * eval_string_range_chars (const char *range) { int i, char1, char2; const char *ptr_char; char **string, str_char[16], *result; string = NULL; result = NULL; for (i = 0; eval_range_chars[i][0]; i++) { if (strcmp (range, eval_range_chars[i][0]) == 0) return strdup (eval_range_chars[i][1]); } char1 = utf8_char_int (range); /* next char must be '-' */ ptr_char = utf8_next_char (range); if (!ptr_char || !ptr_char[0] || (ptr_char[0] != '-')) goto end; /* next char is the char2 */ ptr_char = utf8_next_char (ptr_char); if (!ptr_char || !ptr_char[0]) goto end; char2 = utf8_char_int (ptr_char); /* output is limited to 1024 chars (not bytes) */ if ((char1 > char2) || (char2 - char1 + 1 > 4096)) goto end; string = string_dyn_alloc (128); if (!string) goto end; for (i = char1; i <= char2; i++) { utf8_int_string (i, str_char); string_dyn_concat (string, str_char, -1); } end: if (string) result = string_dyn_free (string, 0); return (result) ? result : strdup (""); } /* * Hides chars in a string. * * Note: result must be freed after use. */ char * eval_string_hide (const char *text) { const char *ptr_string; char *hidden_string, *hide_char; int i, j, length, length_hide_char; hidden_string = NULL; ptr_string = strchr (text, (text[0] == ',') ? ';' : ','); if (!ptr_string) return strdup (""); hide_char = string_strndup (text, ptr_string - text); if (hide_char) { length_hide_char = strlen (hide_char); length = utf8_strlen (ptr_string + 1); hidden_string = malloc ((length * length_hide_char) + 1); if (hidden_string) { j = 0; for (i = 0; i < length; i++) { memcpy (hidden_string + j, hide_char, length_hide_char); j += length_hide_char; } hidden_string[length * length_hide_char] = '\0'; } free (hide_char); } return (hidden_string) ? hidden_string : strdup (""); } /* * Cuts string. * * Note: result must be freed after use. */ char * eval_string_cut (const char *text, int screen) { const char *pos, *pos2; char *tmp, *error, *value; int count_suffix; long number; count_suffix = 0; if (text[0] == '+') { text++; count_suffix = 1; } pos = strchr (text, ','); if (!pos) return strdup (""); pos2 = strchr (pos + 1, ','); if (!pos2) return strdup (""); tmp = string_strndup (text, pos - text); if (!tmp) return strdup (""); error = NULL; number = strtol (tmp, &error, 10); if (!error || error[0] || (number < 0)) { free (tmp); return strdup (""); } free (tmp); tmp = string_strndup (pos + 1, pos2 - pos - 1); if (!tmp) return strdup (""); value = string_cut (pos2 + 1, number, count_suffix, screen, tmp); free (tmp); return value; } /* * Repeats string. * * Note: result must be freed after use. */ char * eval_string_repeat (const char *text) { const char *pos; char *tmp, *error; long number; pos = strchr (text, ','); if (!pos) return strdup (""); tmp = string_strndup (text, pos - text); if (!tmp) return strdup (""); error = NULL; number = strtol (tmp, &error, 10); if (!error || error[0] || (number < 0)) { free (tmp); return strdup (""); } free (tmp); return string_repeat (pos + 1, number); } /* * Splits string. * * Format: number,separators,flags,string * * If number == "count", returns the number of items after split. * If number == "random", returns a random item. * If number > 0, return this index (empty string if not enough items). * If number < 0, return this index starting from the end (-1 = last item, * -2 = penultimate item, etc.). * * If separators is empty string, a comma is used by default. * * Flags is a list of flags, separated by "+": * strip_left * strip_right * collapse_seps * keep_eol * strip_items=xyz * max_items=N * * Examples: * ${split:1,,,abc,def,ghi} ==> abc * ${split:-1,,,abc,def,ghi} ==> ghi * ${split:count,,,abc,def,ghi} ==> 3 * ${split:random,,,abc,def,ghi} ==> def * ${split:3,,collapse_seps,abc,,,def,,,ghi} ==> ghi * ${split:3,,strip_items=-_,_-abc-_,_-def-_,_-ghi-_} ==> ghi * ${split:2, ,,this is a test} ==> is * ${split:2, ,strip_left+strip_right, this is a test } ==> is * ${split:2, ,keep_eol,this is a test} ==> is a test * * Note: result must be freed after use. */ char * eval_string_split (const char *text) { char *pos, *pos2, *pos3, *str_number, *separators, **items, *value, *error; char str_value[32], *str_flags, **list_flags, *strip_items; int i, num_items, count_items, random_item, flags; long number, max_items; str_number = NULL; separators = NULL; items = NULL; value = NULL; str_flags = NULL; list_flags = NULL; strip_items = NULL; count_items = 0; random_item = 0; flags = 0; max_items = 0; if (!text || !text[0]) goto end; pos = strchr (text, ','); if (!pos || (pos == text)) goto end; number = 0; str_number = string_strndup (text, pos - text); if (strcmp (str_number, "count") == 0) { count_items = 1; } else if (strcmp (str_number, "random") == 0) { random_item = 1; } else { error = NULL; number = strtol (str_number, &error, 10); if (!error || error[0] || (number == 0)) goto end; } pos++; pos2 = strchr (pos, ','); if (!pos2) goto end; if (pos2 == pos) separators = strdup (","); else separators = string_strndup (pos, pos2 - pos); pos2++; pos3 = strchr (pos2, ','); if (!pos3) goto end; str_flags = string_strndup (pos2, pos3 - pos2); list_flags = string_split (str_flags, "+", NULL, 0, 0, NULL); if (list_flags) { for (i = 0; list_flags[i]; i++) { if (strcmp (list_flags[i], "strip_left") == 0) flags |= WEECHAT_STRING_SPLIT_STRIP_LEFT; else if (strcmp (list_flags[i], "strip_right") == 0) flags |= WEECHAT_STRING_SPLIT_STRIP_RIGHT; else if (strcmp (list_flags[i], "collapse_seps") == 0) flags |= WEECHAT_STRING_SPLIT_COLLAPSE_SEPS; else if (strcmp (list_flags[i], "keep_eol") == 0) flags |= WEECHAT_STRING_SPLIT_KEEP_EOL; else if (strncmp (list_flags[i], "strip_items=", 12) == 0) { if (strip_items) free (strip_items); strip_items = strdup (list_flags[i] + 12); } else if (strncmp (list_flags[i], "max_items=", 10) == 0) { error = NULL; max_items = strtol (list_flags[i] + 10, &error, 10); if (!error || error[0] || (max_items < 0)) goto end; } } } pos3++; items = string_split (pos3, separators, strip_items, flags, max_items, &num_items); /* if "count" was asked, return the number of items found after split */ if (count_items) { snprintf (str_value, sizeof (str_value), "%d", num_items); value = strdup (str_value); goto end; } if (!items || (num_items < 1)) goto end; /* if "random" was asked, return a random item */ if (random_item) number = random () % num_items; else if (number > 0) number--; if (((number >= 0) && (number >= num_items)) || ((number < 0) && (labs (number) > num_items))) { goto end; } if (number < 0) number = num_items + number; value = strdup (items[number]); end: if (str_number) free (str_number); if (separators) free (separators); if (str_flags) free (str_flags); if (list_flags) string_free_split (list_flags); if (strip_items) free (strip_items); if (items) string_free_split (items); return (value) ? value : strdup (""); } /* * Splits shell arguments. * * Format: number,string * * If number == "count", returns the number of arguments. * If number == "random", returns a random argument. * If number > 0, return this index (empty string if not enough arguments). * If number < 0, return this index starting from the end (-1 = last argument, * -2 = penultimate argument, etc.). * * Examples: * ${split_shell:count,"arg 1" arg2} ==> "2" * ${split_shell:random,"arg 1" arg2} ==> "arg2" * ${split_shell:1,"arg 1" arg2} ==> "arg 1" * ${split_shell:-1,"arg 1" arg2} ==> "arg2" * * Note: result must be freed after use. */ char * eval_string_split_shell (const char *text) { char *pos, *str_number, **items, *value, *error, str_value[32]; int num_items, count_items, random_item; long number; str_number = NULL; items = NULL; value = NULL; count_items = 0; random_item = 0; if (!text || !text[0]) goto end; pos = strchr (text, ','); if (!pos || (pos == text)) goto end; number = 0; str_number = string_strndup (text, pos - text); if (strcmp (str_number, "count") == 0) { count_items = 1; } else if (strcmp (str_number, "random") == 0) { random_item = 1; } else { error = NULL; number = strtol (str_number, &error, 10); if (!error || error[0] || (number == 0)) goto end; } pos++; items = string_split_shell (pos, &num_items); /* if "count" was asked, return the number of items found after split */ if (count_items) { snprintf (str_value, sizeof (str_value), "%d", num_items); value = strdup (str_value); goto end; } if (!items || (num_items < 1)) goto end; /* if "random" was asked, return a random item */ if (random_item) number = random () % num_items; else if (number > 0) number--; if (((number >= 0) && (number >= num_items)) || ((number < 0) && (labs (number) > num_items))) { goto end; } if (number < 0) number = num_items + number; value = strdup (items[number]); end: if (str_number) free (str_number); if (items) string_free_split (items); return (value) ? value : strdup (""); } /* * Returns a regex group captured. * * Note: result must be freed after use. */ char * eval_string_regex_group (const char *text, struct t_eval_context *eval_context) { char str_value[64], *error; long number; if (!eval_context->regex || !eval_context->regex->result) return strdup (""); if (strcmp (text, "#") == 0) { snprintf (str_value, sizeof (str_value), "%d", eval_context->regex->last_match); return strdup (str_value); } if (strcmp (text, "repl_index") == 0) { snprintf (str_value, sizeof (str_value), "%d", eval_context->regex_replacement_index); return strdup (str_value); } if (strcmp (text, "+") == 0) { number = eval_context->regex->last_match; } else { error = NULL; number = strtol (text, &error, 10); if (!error || error[0]) number = -1; } if ((number >= 0) && (number <= eval_context->regex->last_match)) { return string_strndup ( eval_context->regex->result + eval_context->regex->match[number].rm_so, eval_context->regex->match[number].rm_eo - eval_context->regex->match[number].rm_so); } return strdup (""); } /* * Returns a string with color code. * * Note: result must be freed after use. */ char * eval_string_color (const char *text) { const char *ptr_value; ptr_value = gui_color_search_config (text); if (ptr_value) return strdup (ptr_value); ptr_value = gui_color_get_custom (text); return strdup ((ptr_value) ? ptr_value : ""); } /* * Returns a string modified by a modifier. * * Note: result must be freed after use. */ char * eval_string_modifier (const char *text) { const char *ptr_arguments, *ptr_string; char *value, *modifier_name, *modifier_data; value = NULL; ptr_arguments = strchr (text, ','); if (!ptr_arguments) return strdup (""); ptr_arguments++; ptr_string = strchr (ptr_arguments, ','); if (!ptr_string) return strdup (""); ptr_string++; modifier_name = string_strndup (text, ptr_arguments - 1 - text); modifier_data = string_strndup (ptr_arguments, ptr_string - 1 - ptr_arguments); value = hook_modifier_exec (NULL, modifier_name, modifier_data, ptr_string); if (modifier_name) free (modifier_name); if (modifier_data) free (modifier_data); return (value) ? value : strdup (""); } /* * Returns an info. * * Note: result must be freed after use. */ char * eval_string_info (const char *text) { const char *ptr_arguments; char *value, *info_name; value = NULL; ptr_arguments = strchr (text, ','); if (ptr_arguments) { info_name = string_strndup (text, ptr_arguments - text); ptr_arguments++; } else { info_name = strdup (text); } if (info_name) { value = hook_info_get (NULL, info_name, ptr_arguments); free (info_name); } return (value) ? value : strdup (""); } /* * Encodes a string in base 16, 32, or 64. * * Note: result must be freed after use. */ char * eval_string_base_encode (const char *text) { const char *ptr_string; char *value, *base, *result; int length; base = NULL; result = NULL; ptr_string = strchr (text, ','); if (!ptr_string) goto end; base = string_strndup (text, ptr_string - text); if (!base) goto end; ptr_string++; length = strlen (ptr_string); result = malloc ((length * 4) + 1); if (!result) goto end; if (string_base_encode (base, ptr_string, length, result) < 0) { free (result); result = NULL; } end: value = strdup ((result) ? result : ""); if (base) free (base); if (result) free (result); return value; } /* * Decodes a string encoded in base 16, 32, or 64. * * Note: result must be freed after use. */ char * eval_string_base_decode (const char *text) { const char *ptr_string; char *value, *base, *result; base = NULL; result = NULL; ptr_string = strchr (text, ','); if (!ptr_string) goto end; base = string_strndup (text, ptr_string - text); if (!base) goto end; ptr_string++; result = malloc (strlen (ptr_string) + 1); if (!result) goto end; if (string_base_decode (base, ptr_string, result) < 0) { free (result); result = NULL; } end: value = strdup ((result) ? result : ""); if (base) free (base); if (result) free (result); return value; } /* * Returns a date. * * Note: result must be freed after use. */ char * eval_string_date (const char *text) { char str_value[512]; struct timeval tv_now; int rc; gettimeofday (&tv_now, NULL); rc = util_strftimeval (str_value, sizeof (str_value), (text[0] == ':') ? text + 1 : "%F %T", &tv_now); return strdup ((rc > 0) ? str_value : ""); } /* * Evaluates a condition and returns evaluated if/else clause. * * Note: result must be freed after use. */ char * eval_string_if (const char *text, struct t_eval_context *eval_context) { const char *pos, *pos2; char *value, *condition, *tmp; int rc; value = NULL; pos = (char *)eval_strstr_level (text, "?", eval_context, NULL, NULL, 1); pos2 = (pos) ? (char *)eval_strstr_level (pos + 1, ":", eval_context, NULL, NULL, 1) : NULL; condition = (pos) ? string_strndup (text, pos - text) : strdup (text); if (!condition) return strdup (""); tmp = eval_expression_condition (condition, eval_context); rc = eval_is_true (tmp); if (tmp) free (tmp); if (rc) { /* * condition is true: return the "value_if_true" * (or EVAL_STR_TRUE if value is missing) */ if (pos) { tmp = (pos2) ? string_strndup (pos + 1, pos2 - pos - 1) : strdup (pos + 1); if (tmp) { value = eval_replace_vars (tmp, eval_context); free (tmp); } } else { value = strdup (EVAL_STR_TRUE); } } else { /* * condition is false: return the "value_if_false" * (or EVAL_STR_FALSE if both values are missing) */ if (pos2) { value = eval_replace_vars (pos2 + 1, eval_context); } else { if (!pos) value = strdup (EVAL_STR_FALSE); } } free (condition); return (value) ? value : strdup (""); } /* * Returns a random integer number. * * Note: result must be freed after use. */ char * eval_string_random (const char *text) { char *pos, *error, *tmp, result[128]; long long min_number, max_number; if (!text || !text[0]) goto error; pos = strchr (text, ','); if (!pos) goto error; tmp = string_strndup (text, pos - text); if (!tmp) goto error; error = NULL; min_number = strtoll (tmp, &error, 10); if (!error || error[0]) { free (tmp); goto error; } free (tmp); error = NULL; max_number = strtoll (pos + 1, &error, 10); if (!error || error[0]) goto error; if (min_number > max_number) goto error; if (max_number - min_number > RAND_MAX) goto error; /* * using modulo division on the random() value produces a biased result, * but this is enough for our usage here */ snprintf (result, sizeof (result), "%lld", min_number + (random () % (max_number - min_number + 1))); return strdup (result); error: return strdup ("0"); } /* * Translates text. * * Note: result must be freed after use. */ char * eval_string_translate (const char *text) { const char *ptr_string; if (!text || !text[0]) return strdup (""); ptr_string = gettext (text); return strdup ((ptr_string) ? ptr_string : ""); } /* * Defines a variable. */ void eval_string_define (const char *text, struct t_eval_context *eval_context) { char *pos, *name; pos = strchr (text, ','); if (!pos) return; name = string_strndup (text, pos - text); if (!name) return; hashtable_set (eval_context->user_vars, name, pos + 1); free (name); } /* * Gets value of hdata using "path" to a variable. * * Note: result must be freed after use. */ char * eval_hdata_get_value (struct t_hdata *hdata, void *pointer, const char *path, struct t_eval_context *eval_context) { char *value, *var_name, str_value[128], *pos, *property; const char *ptr_value, *hdata_name, *ptr_var_name, *pos_open_paren; int type, debug_id; struct t_hashtable *hashtable; EVAL_DEBUG_MSG(1, "eval_hdata_get_value(\"%s\", 0x%lx, \"%s\")", (hdata) ? hdata->name : "(null)", pointer, path); value = NULL; var_name = NULL; /* NULL pointer? return empty string */ if (!pointer) { value = strdup (""); goto end; } /* no path? just return current pointer as string */ if (!path || !path[0]) { snprintf (str_value, sizeof (str_value), "0x%lx", (unsigned long)pointer); value = strdup (str_value); goto end; } if (!hdata) goto end; /* * look for name of hdata, for example in "window.buffer.full_name", the * hdata name is "window" */ pos = strchr (path, '.'); if (pos > path) var_name = string_strndup (path, pos - path); else var_name = strdup (path); if (!var_name) goto end; /* search type of variable in hdata */ hdata_get_index_and_name (var_name, NULL, &ptr_var_name); type = hdata_get_var_type (hdata, ptr_var_name); if (type < 0) goto end; /* build a string with the value or variable */ switch (type) { case WEECHAT_HDATA_CHAR: snprintf (str_value, sizeof (str_value), "%c", hdata_char (hdata, pointer, var_name)); value = strdup (str_value); break; case WEECHAT_HDATA_INTEGER: snprintf (str_value, sizeof (str_value), "%d", hdata_integer (hdata, pointer, var_name)); value = strdup (str_value); break; case WEECHAT_HDATA_LONG: snprintf (str_value, sizeof (str_value), "%ld", hdata_long (hdata, pointer, var_name)); value = strdup (str_value); break; case WEECHAT_HDATA_LONGLONG: snprintf (str_value, sizeof (str_value), "%lld", hdata_longlong (hdata, pointer, var_name)); value = strdup (str_value); break; case WEECHAT_HDATA_STRING: case WEECHAT_HDATA_SHARED_STRING: ptr_value = hdata_string (hdata, pointer, var_name); value = (ptr_value) ? strdup (ptr_value) : NULL; break; case WEECHAT_HDATA_POINTER: pointer = hdata_pointer (hdata, pointer, var_name); snprintf (str_value, sizeof (str_value), "0x%lx", (unsigned long)pointer); value = strdup (str_value); break; case WEECHAT_HDATA_TIME: snprintf (str_value, sizeof (str_value), "%lld", (long long)hdata_time (hdata, pointer, var_name)); value = strdup (str_value); break; case WEECHAT_HDATA_HASHTABLE: pointer = hdata_hashtable (hdata, pointer, var_name); if (pos) { /* * for a hashtable, if there is a "." after name of hdata: * 1) If "()" is at the end, it is a function call to * hashtable_get_string(). * 2) Otherwise, get the value for this key in hashtable. */ hashtable = pointer; pos_open_paren = strchr (pos, '('); if (pos_open_paren && (pos_open_paren > pos + 1) && (pos_open_paren[1] == ')')) { property = string_strndup (pos + 1, pos_open_paren - pos - 1); ptr_value = hashtable_get_string (hashtable, property); if (property) free (property); value = (ptr_value) ? strdup (ptr_value) : NULL; break; } ptr_value = hashtable_get (hashtable, pos + 1); if (ptr_value) { switch (hashtable->type_values) { case HASHTABLE_INTEGER: snprintf (str_value, sizeof (str_value), "%d", *((int *)ptr_value)); value = strdup (str_value); break; case HASHTABLE_STRING: value = strdup (ptr_value); break; case HASHTABLE_POINTER: case HASHTABLE_BUFFER: snprintf (str_value, sizeof (str_value), "0x%lx", (unsigned long)ptr_value); value = strdup (str_value); break; case HASHTABLE_TIME: snprintf (str_value, sizeof (str_value), "%lld", (long long)(*((time_t *)ptr_value))); value = strdup (str_value); break; case HASHTABLE_NUM_TYPES: break; } } } else { snprintf (str_value, sizeof (str_value), "0x%lx", (unsigned long)pointer); value = strdup (str_value); } break; } /* * if we are on a pointer and that something else is in path (after "."), * go on with this pointer and remaining path */ if ((type == WEECHAT_HDATA_POINTER) && pos) { hdata_name = hdata_get_var_hdata (hdata, var_name); if (!hdata_name) goto end; hdata = hook_hdata_get (NULL, hdata_name); if (value) free (value); value = eval_hdata_get_value (hdata, pointer, pos + 1, eval_context); } end: if (var_name) free (var_name); EVAL_DEBUG_RESULT(1, value); return value; } /* * Returns a string using hdata. * * Note: result must be freed after use. */ char * eval_string_hdata (const char *text, struct t_eval_context *eval_context) { const char *pos_vars, *pos1, *pos2; char *value, *hdata_name, *pointer_name, *tmp; void *pointer; struct t_hdata *hdata; int rc; unsigned long ptr; value = NULL; hdata_name = NULL; pointer_name = NULL; pointer = NULL; pos_vars = strchr (text, '.'); if (pos_vars > text) hdata_name = string_strndup (text, pos_vars - text); else hdata_name = strdup (text); if (pos_vars) pos_vars++; if (!hdata_name) goto end; pos1 = strchr (hdata_name, '['); if (pos1 > hdata_name) { pos2 = strchr (pos1 + 1, ']'); if (pos2) { if (pos2 > pos1 + 1) pointer_name = string_strndup (pos1 + 1, pos2 - pos1 - 1); else goto end; } tmp = string_strndup (hdata_name, pos1 - hdata_name); if (tmp) { free (hdata_name); hdata_name = tmp; } } hdata = hook_hdata_get (NULL, hdata_name); if (!hdata) { if (pos_vars || pointer_name) goto end; /* case of a single pointer which is not hdata, eg: ${my_pointer} */ pointer = hashtable_get (eval_context->pointers, hdata_name); value = eval_hdata_get_value (NULL, pointer, pos_vars, eval_context); goto end; } if (pointer_name) { if (strncmp (pointer_name, "0x", 2) == 0) { rc = sscanf (pointer_name, "%lx", &ptr); if ((rc != EOF) && (rc != 0)) { pointer = (void *)ptr; if (!hdata_check_pointer (hdata, NULL, pointer)) goto end; } else goto end; } else { pointer = hdata_get_list (hdata, pointer_name); if (!pointer) { pointer = hashtable_get (eval_context->pointers, pointer_name); if (!pointer) goto end; if (!hdata_check_pointer (hdata, NULL, pointer)) goto end; } } } if (!pointer) { pointer = hashtable_get (eval_context->pointers, hdata_name); if (!pointer) goto end; if (!hdata_check_pointer (hdata, NULL, pointer)) goto end; } value = eval_hdata_get_value (hdata, pointer, pos_vars, eval_context); end: if (hdata_name) free (hdata_name); if (pointer_name) free (pointer_name); return (value) ? value : strdup (""); } /* * Returns text with syntax highlighting (using markers, to be replaced by * colors later). * * Note: result must be freed after use. */ char * eval_syntax_highlight_add_markers (const char *prefix, const char *text, const char *suffix) { char **value; value = string_dyn_alloc (128); if (!value) return NULL; string_dyn_concat (value, EVAL_SYNTAX_HL_INC, -1); string_dyn_concat (value, prefix, -1); if (text) string_dyn_concat (value, text, -1); string_dyn_concat (value, suffix, -1); string_dyn_concat (value, EVAL_SYNTAX_HL_DEC, -1); return string_dyn_free (value, 0); } /* * Replaces raw highlight markers with color codes defined in option * weechat.color.eval_syntax_colors. * * Note: result must be freed after use. */ char * eval_syntax_highlight_colorize (const char *value) { const char *ptr_value; char **result, *pos; int color; if (!value) return NULL; result = string_dyn_alloc (128); if (!result) return NULL; color = -1; ptr_value = value; while (ptr_value && ptr_value[0]) { pos = strstr (ptr_value, EVAL_SYNTAX_HL_MARKER); if (!pos) { string_dyn_concat (result, ptr_value, -1); break; } string_dyn_concat (result, ptr_value, pos - ptr_value); ptr_value = pos + strlen (EVAL_SYNTAX_HL_MARKER); if (config_num_eval_syntax_colors > 0) { if (ptr_value[0] == '+') color++; else if (ptr_value[0] == '-') color--; } ptr_value++; if (config_num_eval_syntax_colors > 0) { string_dyn_concat ( result, gui_color_get_custom ( (color >= 0) ? config_eval_syntax_colors[color % config_num_eval_syntax_colors] : "reset"), -1); } } return string_dyn_free (result, 0); } /* * Adds syntax highlighting in text. * * Note: result must be freed after use. */ char * eval_syntax_highlight (const char *text, struct t_eval_context *eval_context) { char *value, *value2; eval_context->syntax_highlight++; value = eval_replace_vars (text, eval_context); value2 = eval_syntax_highlight_colorize (value); if (value) free (value); eval_context->syntax_highlight--; return value2; } /* * Replaces variables, which can be, by order of priority: * - ${raw_hl:string}: the string itself without evaluation but with syntax highlighting * - ${raw:string}: the string itself without evaluation * - ${hl:string}: the string with syntax highlighting * - ${name}: the variable from hashtable "user_vars" or "extra_vars" * - ${weechat_config_dir}: WeeChat config directory * - ${weechat_data_dir}: WeeChat data directory * - ${weechat_cache_dir}: WeeChat cache directory * - ${weechat_runtmie_dir}: WeeChat runtime directory * - ${eval:string}: the evaluated string * - ${eval_cond:string}: the evaluated condition * - ${esc:string} or ${\\string}: the string with escaped chars * - ${chars:range}: the string with a range of chars, "range" is one of: * "digit", "xdigit", "lower", "upper", "alpha", "alnum" or "c1-c2" * ("c1" and "c2" are code points with c1 ≤ c2) * - ${lower:string}: the string converted to lower case * - ${upper:string}: the string converted to upper case * - ${hide:char,string}: the string with hidden chars * - ${cut:max,suffix,string}: the string with max chars (excluding the suffix) * - ${cut:+max,suffix,string}: the string with max chars (including the suffix) * - ${cutscr:max,suffix,string}: the string with max chars displayed on screen * (excluding the suffix) * - ${cutscr:+max,suffix,string}: the string with max chars displayed on screen * (including the suffix) * - ${rev:string}: the reversed string * - ${revscr:string}: the reversed string for display (color codes are not reversed) * - ${repeat:count,string}: the repeated string * - ${length:string}: the length of the string (number of UTF-8 chars) * - ${lengthscr:string}: the length of the string on screen (sum of the width * of each UTF-8 char displayed on screen, colors codes are ignored) * - ${split:n,separators,flags,string}: split of the string * (n can be an integer ≥ 1, an integer ≤ -1, "count" or "random") * - ${split_shell:n,string}: split of shell arguments * (n can be an integer ≥ 1, an integer ≤ -1, "count" or "random") * - ${re:N} or ${re:+}: a regex group captured (0 ≤ N ≤ 99) * - ${color:name}: the color * - ${modifier:name,data,string}: the modifier * - ${info:name,arguments}: the info (arguments are optional) * - ${base_encode:base,string}: the string encoded to base: 16, 32, 64 or 64url * - ${base_decode:base,string}: the string decoded from base: 16, 32, 64 or 64url * - ${date} or ${date:format}: current date/time * - ${env:NAME}: the environment variable * - ${if:condition?value_if_true:value_if_false}: the result of ternary operator * - ${calc:expression}: the result of the expression with parentheses and operators * (+, -, *, /, //, %, **) * - ${random:min,max}: a random integer number between "min" and "max" (inclusive) * - ${translate:string}: the translated string * - ${define:name,value}: declaration of a user variable (return an empty string) * - ${sec.data.xxx}: the value of the secured data "xxx" * - ${file.section.option}: the value of the config option * - ${name}: the local variable in buffer * - ${name}: the pointer name from hashtable "pointers" * - ${hdata.var1.var2}: hdata with name * - ${hdata[list].var1.var2}: hdata with list * - ${hdata[ptr].var1.var2}: hdata with pointer * - ${hdata[ptr_name].var1.var2}: hdata with name of pointer * * See `/help eval` in WeeChat for examples. * * Note: result must be freed after use. */ char * eval_replace_vars_cb (void *data, const char *prefix, const char *text, const char *suffix) { struct t_eval_context *eval_context; struct t_config_option *ptr_option; struct t_gui_buffer *ptr_buffer; char str_value[512], *value, *tmp; const char *ptr_value; int length, debug_id; value = NULL; eval_context = (struct t_eval_context *)data; EVAL_DEBUG_MSG(1, "eval_replace_vars_cb(\"%s\")", text); if (eval_context->syntax_highlight) return eval_syntax_highlight_add_markers (prefix, text, suffix); /* raw text (no evaluation at all), with syntax highlighting */ if (strncmp (text, "raw_hl:", 7) == 0) { value = eval_syntax_highlight (text + 7, eval_context); goto end; } /* raw text (no evaluation at all) */ if (strncmp (text, "raw:", 4) == 0) { value = strdup (text + 4); goto end; } /* syntax highlighting */ if (strncmp (text, "hl:", 3) == 0) { value = eval_syntax_highlight (text + 3, eval_context); goto end; } /* variable in hashtable "user_vars" or "extra_vars" */ ptr_value = hashtable_get (eval_context->user_vars, text); if (ptr_value) { value = strdup (ptr_value); goto end; } if (eval_context->extra_vars) { ptr_value = hashtable_get (eval_context->extra_vars, text); if (ptr_value) { if (eval_context->extra_vars_eval) { tmp = strdup (ptr_value); if (!tmp) goto end; hashtable_remove (eval_context->extra_vars, text); value = eval_replace_vars (tmp, eval_context); hashtable_set (eval_context->extra_vars, text, tmp); free (tmp); goto end; } else { value = strdup (ptr_value); goto end; } } } /* WeeChat home directory */ if (strcmp (text, "weechat_config_dir") == 0) { value = strdup (weechat_config_dir); goto end; } if (strcmp (text, "weechat_data_dir") == 0) { value = strdup (weechat_data_dir); goto end; } if (strcmp (text, "weechat_cache_dir") == 0) { value = strdup (weechat_cache_dir); goto end; } if (strcmp (text, "weechat_runtime_dir") == 0) { value = strdup (weechat_runtime_dir); goto end; } /* * force evaluation of string (recursive call) * --> use with caution: the text must be safe! */ if (strncmp (text, "eval:", 5) == 0) { value = eval_replace_vars (text + 5, eval_context); goto end; } /* * force evaluation of condition (recursive call) * --> use with caution: the text must be safe! */ if (strncmp (text, "eval_cond:", 10) == 0) { value = eval_string_eval_cond (text + 10, eval_context); goto end; } /* convert escaped chars */ if (strncmp (text, "esc:", 4) == 0) { value = string_convert_escaped_chars (text + 4); goto end; } if ((text[0] == '\\') && text[1] && (text[1] != '\\')) { value = string_convert_escaped_chars (text); goto end; } /* range of chars */ if (strncmp (text, "chars:", 6) == 0) { value = eval_string_range_chars (text + 6); goto end; } /* convert to lower case */ if (strncmp (text, "lower:", 6) == 0) { value = string_tolower (text + 6); goto end; } /* convert to upper case */ if (strncmp (text, "upper:", 6) == 0) { value = string_toupper (text + 6); goto end; } /* hide chars: replace all chars by a given char/string */ if (strncmp (text, "hide:", 5) == 0) { value = eval_string_hide (text + 5); goto end; } /* * cut chars: * cut: max number of chars, and add an optional suffix when the * string is cut * cutscr: max number of chars displayed on screen, and add an optional * suffix when the string is cut */ if (strncmp (text, "cut:", 4) == 0) { value = eval_string_cut (text + 4, 0); goto end; } if (strncmp (text, "cutscr:", 7) == 0) { value = eval_string_cut (text + 7, 1); goto end; } /* reverse string */ if (strncmp (text, "rev:", 4) == 0) { value = string_reverse (text + 4); goto end; } if (strncmp (text, "revscr:", 7) == 0) { value = string_reverse_screen (text + 7); goto end; } /* repeated string */ if (strncmp (text, "repeat:", 7) == 0) { value = eval_string_repeat (text + 7); goto end; } /* * length of string: * length: number of chars * lengthscr: number of chars displayed on screen */ if (strncmp (text, "length:", 7) == 0) { length = gui_chat_strlen (text + 7); snprintf (str_value, sizeof (str_value), "%d", length); value = strdup (str_value); goto end; } if (strncmp (text, "lengthscr:", 10) == 0) { length = gui_chat_strlen_screen (text + 10); snprintf (str_value, sizeof (str_value), "%d", length); value = strdup (str_value); goto end; } /* split string */ if (strncmp (text, "split:", 6) == 0) { value = eval_string_split (text + 6); goto end; } /* split shell */ if (strncmp (text, "split_shell:", 12) == 0) { value = eval_string_split_shell (text + 12); goto end; } /* regex group captured */ if (strncmp (text, "re:", 3) == 0) { value = eval_string_regex_group (text + 3, eval_context); goto end; } /* color code */ if (strncmp (text, "color:", 6) == 0) { value = eval_string_color (text + 6); goto end; } /* modifier */ if (strncmp (text, "modifier:", 9) == 0) { value = eval_string_modifier (text + 9); goto end; } /* info */ if (strncmp (text, "info:", 5) == 0) { value = eval_string_info (text + 5); goto end; } /* base_encode/base_decode */ if (strncmp (text, "base_encode:", 12) == 0) { value = eval_string_base_encode (text + 12); goto end; } if (strncmp (text, "base_decode:", 12) == 0) { value = eval_string_base_decode (text + 12); goto end; } /* current date/time */ if ((strncmp (text, "date", 4) == 0) && (!text[4] || (text[4] == ':'))) { value = eval_string_date (text + 4); goto end; } /* environment variable */ if (strncmp (text, "env:", 4) == 0) { ptr_value = getenv (text + 4); value = strdup ((ptr_value) ? ptr_value : ""); goto end; } /* ternary operator: if:condition?value_if_true:value_if_false */ if (strncmp (text, "if:", 3) == 0) { value = eval_string_if (text + 3, eval_context); goto end; } /* * calculate the result of an expression * (with number, operators and parentheses) */ if (strncmp (text, "calc:", 5) == 0) { value = calc_expression (text + 5); goto end; } /* * random number */ if (strncmp (text, "random:", 7) == 0) { value = eval_string_random (text + 7); goto end; } /* * translated text */ if (strncmp (text, "translate:", 10) == 0) { value = eval_string_translate (text + 10); goto end; } /* define a variable */ if (strncmp (text, "define:", 7) == 0) { eval_string_define (text + 7, eval_context); value = strdup (""); goto end; } /* option: if found, return this value */ if (strncmp (text, "sec.data.", 9) == 0) { ptr_value = hashtable_get (secure_hashtable_data, text + 9); value = strdup ((ptr_value) ? ptr_value : ""); goto end; } config_file_search_with_string (text, NULL, NULL, &ptr_option, NULL); if (ptr_option) { if (!ptr_option->value) { value = strdup (""); goto end; } switch (ptr_option->type) { case CONFIG_OPTION_TYPE_BOOLEAN: value = strdup (CONFIG_BOOLEAN(ptr_option) ? EVAL_STR_TRUE : EVAL_STR_FALSE); goto end; case CONFIG_OPTION_TYPE_INTEGER: snprintf (str_value, sizeof (str_value), "%d", CONFIG_INTEGER(ptr_option)); value = strdup (str_value); goto end; case CONFIG_OPTION_TYPE_STRING: value = strdup (CONFIG_STRING(ptr_option)); goto end; case CONFIG_OPTION_TYPE_COLOR: value = strdup (gui_color_get_name (CONFIG_COLOR(ptr_option))); goto end; case CONFIG_OPTION_TYPE_ENUM: value = strdup (ptr_option->string_values[CONFIG_ENUM(ptr_option)]); goto end; case CONFIG_NUM_OPTION_TYPES: value = strdup (""); goto end; } } /* local variable in buffer */ ptr_buffer = hashtable_get (eval_context->pointers, "buffer"); if (ptr_buffer) { ptr_value = hashtable_get (ptr_buffer->local_variables, text); if (ptr_value) { value = strdup (ptr_value); goto end; } } /* hdata */ value = eval_string_hdata (text, eval_context); end: EVAL_DEBUG_RESULT(1, value); return value; } /* * Replaces variables in a string. * * Note: result must be freed after use. */ char * eval_replace_vars (const char *expr, struct t_eval_context *eval_context) { const char *no_replace_prefix_list_std[] = { "if:", "raw:", "raw_hl:", NULL }; const char *no_replace_prefix_list_col[] = { "raw:", "raw_hl:", NULL }; char *result; int debug_id; EVAL_DEBUG_MSG(1, "eval_replace_vars(\"%s\")", expr); eval_context->recursion_count++; if (eval_context->recursion_count < EVAL_RECURSION_MAX) { result = string_replace_with_callback ( expr, eval_context->prefix, eval_context->suffix, (eval_context->syntax_highlight) ? 0 : 1, (eval_context->syntax_highlight) ? no_replace_prefix_list_col : no_replace_prefix_list_std, &eval_replace_vars_cb, eval_context, NULL); } else { result = strdup (""); } eval_context->recursion_count--; EVAL_DEBUG_RESULT(1, result); return result; } /* * Compares two expressions. * * Returns: * "1": comparison is true * "0": comparison is false * * Examples: * "15 > 2": returns "1" * "abc == def": returns "0" * * Note: result must be freed after use. */ char * eval_compare (const char *expr1, int comparison, const char *expr2, struct t_eval_context *eval_context) { int rc, string_compare, length1, length2, debug_id; regex_t regex; double value1, value2; char *error, *value; EVAL_DEBUG_MSG(1, "eval_compare(\"%s\", \"%s\", \"%s\")", expr1, eval_comparisons[comparison], expr2); rc = 0; string_compare = 0; if (!expr1 || !expr2) goto end; if ((comparison == EVAL_COMPARE_REGEX_MATCHING) || (comparison == EVAL_COMPARE_REGEX_NOT_MATCHING)) { if (string_regcomp (®ex, expr2, REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) { goto end; } rc = (regexec (®ex, expr1, 0, NULL, 0) == 0) ? 1 : 0; regfree (®ex); if (comparison == EVAL_COMPARE_REGEX_NOT_MATCHING) rc ^= 1; goto end; } else if ((comparison == EVAL_COMPARE_STRING_MATCHING_CASE_SENSITIVE) || (comparison == EVAL_COMPARE_STRING_NOT_MATCHING_CASE_SENSITIVE)) { rc = string_match (expr1, expr2, 1); if (comparison == EVAL_COMPARE_STRING_NOT_MATCHING_CASE_SENSITIVE) rc ^= 1; goto end; } else if ((comparison == EVAL_COMPARE_STRING_MATCHING) || (comparison == EVAL_COMPARE_STRING_NOT_MATCHING)) { rc = string_match (expr1, expr2, 0); if (comparison == EVAL_COMPARE_STRING_NOT_MATCHING) rc ^= 1; goto end; } else if ((comparison == EVAL_COMPARE_INCLUDE_CASE_SENSITIVE) || (comparison == EVAL_COMPARE_NOT_INCLUDE_CASE_SENSITIVE)) { rc = (strstr (expr1, expr2)) ? 1 : 0; if (comparison == EVAL_COMPARE_NOT_INCLUDE_CASE_SENSITIVE) rc ^= 1; goto end; } else if ((comparison == EVAL_COMPARE_INCLUDE) || (comparison == EVAL_COMPARE_NOT_INCLUDE)) { rc = (string_strcasestr (expr1, expr2)) ? 1 : 0; if (comparison == EVAL_COMPARE_NOT_INCLUDE) rc ^= 1; goto end; } length1 = strlen (expr1); length2 = strlen (expr2); /* * string comparison is forced if expr1 and expr2 have double quotes at * beginning/end */ if (((length1 == 0) || ((expr1[0] == '"') && expr1[length1 - 1] == '"')) && ((length2 == 0) || ((expr2[0] == '"') && expr2[length2 - 1] == '"'))) { string_compare = 1; } if (!string_compare) { value1 = strtod (expr1, &error); if (!error || error[0]) { string_compare = 1; } else { value2 = strtod (expr2, &error); if (!error || error[0]) string_compare = 1; } } if (string_compare) rc = strcmp (expr1, expr2); else rc = (value1 < value2) ? -1 : ((value1 > value2) ? 1 : 0); switch (comparison) { case EVAL_COMPARE_EQUAL: rc = (rc == 0); break; case EVAL_COMPARE_NOT_EQUAL: rc = (rc != 0); break; case EVAL_COMPARE_LESS_EQUAL: rc = (rc <= 0); break; case EVAL_COMPARE_LESS: rc = (rc < 0); break; case EVAL_COMPARE_GREATER_EQUAL: rc = (rc >= 0); break; case EVAL_COMPARE_GREATER: rc = (rc > 0); break; case EVAL_NUM_COMPARISONS: break; } end: value = strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE); EVAL_DEBUG_RESULT(1, value); return value; } /* * Evaluates a condition (this function must not be called directly). * * For return value, see function eval_expression(). * * Note: result must be freed after use (if not NULL). */ char * eval_expression_condition (const char *expr, struct t_eval_context *eval_context) { int logic, comp, length, level, rc, debug_id; const char *pos, *pos_end; char *expr2, *sub_expr, *value, *tmp_value, *tmp_value2; EVAL_DEBUG_MSG(1, "eval_expression_condition(\"%s\")", expr); value = NULL; expr2 = NULL; if (!expr) goto end; if (!expr[0]) { value = strdup (expr); goto end; } /* skip spaces at beginning of string */ while (expr[0] == ' ') { expr++; } if (!expr[0]) { value = strdup (expr); goto end; } /* skip spaces at end of string */ pos_end = expr + strlen (expr) - 1; while ((pos_end > expr) && (pos_end[0] == ' ')) { pos_end--; } expr2 = string_strndup (expr, pos_end + 1 - expr); if (!expr2) goto end; /* * search for a logical operator, and if one is found: * - split expression into two sub-expressions * - evaluate first sub-expression * - if needed, evaluate second sub-expression * - return result */ for (logic = 0; logic < EVAL_NUM_LOGICAL_OPS; logic++) { pos = eval_strstr_level (expr2, eval_logical_ops[logic], eval_context, "(", ")", 0); if (pos > expr2) { pos_end = pos - 1; while ((pos_end > expr2) && (pos_end[0] == ' ')) { pos_end--; } sub_expr = string_strndup (expr2, pos_end + 1 - expr2); if (!sub_expr) goto end; tmp_value = eval_expression_condition (sub_expr, eval_context); free (sub_expr); rc = eval_is_true (tmp_value); if (tmp_value) free (tmp_value); /* * if rc == 0 with "&&" or rc == 1 with "||", no need to * evaluate second sub-expression, just return the rc */ if ((!rc && (logic == EVAL_LOGICAL_OP_AND)) || (rc && (logic == EVAL_LOGICAL_OP_OR))) { value = strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE); goto end; } pos += strlen (eval_logical_ops[logic]); while (pos[0] == ' ') { pos++; } tmp_value = eval_expression_condition (pos, eval_context); rc = eval_is_true (tmp_value); if (tmp_value) free (tmp_value); value = strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE); goto end; } } /* * search for a comparison, and if one is found: * - split expression into two sub-expressions * - evaluate the two sub-expressions * - compare sub-expressions * - return result */ for (comp = 0; comp < EVAL_NUM_COMPARISONS; comp++) { pos = eval_strstr_level (expr2, eval_comparisons[comp], eval_context, "(", ")", 0); if (pos >= expr2) { if (pos > expr2) { pos_end = pos - 1; while ((pos_end > expr2) && (pos_end[0] == ' ')) { pos_end--; } sub_expr = string_strndup (expr2, pos_end + 1 - expr2); } else { sub_expr = strdup (""); } if (!sub_expr) goto end; pos += strlen (eval_comparisons[comp]); while (pos[0] == ' ') { pos++; } if ((comp == EVAL_COMPARE_REGEX_MATCHING) || (comp == EVAL_COMPARE_REGEX_NOT_MATCHING)) { /* for regex: just replace vars in both expressions */ tmp_value = eval_replace_vars (sub_expr, eval_context); tmp_value2 = eval_replace_vars (pos, eval_context); } else { /* other comparison: fully evaluate both expressions */ tmp_value = eval_expression_condition (sub_expr, eval_context); tmp_value2 = eval_expression_condition (pos, eval_context); } free (sub_expr); value = eval_compare (tmp_value, comp, tmp_value2, eval_context); if (tmp_value) free (tmp_value); if (tmp_value2) free (tmp_value2); goto end; } } /* * evaluate sub-expressions between parentheses and replace them with their * values */ while (expr2[0] == '(') { level = 0; pos = expr2 + 1; while (pos[0]) { if (pos[0] == '(') level++; else if (pos[0] == ')') { if (level == 0) break; level--; } pos++; } /* closing parenthesis not found */ if (pos[0] != ')') goto end; sub_expr = string_strndup (expr2 + 1, pos - expr2 - 1); if (!sub_expr) goto end; tmp_value = eval_expression_condition (sub_expr, eval_context); free (sub_expr); if (!pos[1]) { /* * nothing around parentheses, then return value of * sub-expression as-is */ value = tmp_value; goto end; } length = ((tmp_value) ? strlen (tmp_value) : 0) + 1 + strlen (pos + 1) + 1; tmp_value2 = malloc (length); if (!tmp_value2) { if (tmp_value) free (tmp_value); goto end; } tmp_value2[0] = '\0'; if (tmp_value) strcat (tmp_value2, tmp_value); strcat (tmp_value2, " "); strcat (tmp_value2, pos + 1); free (expr2); expr2 = tmp_value2; if (tmp_value) free (tmp_value); } /* * at this point, there is no more logical operator neither comparison, * so we just replace variables in string and return the result */ value = eval_replace_vars (expr2, eval_context); end: if (expr2) free (expr2); EVAL_DEBUG_RESULT(1, value); return value; } /* * Replaces text in a string using a regular expression and replacement text. * * The argument "regex" is a pointer to a regex compiled with WeeChat function * string_regcomp (or function regcomp). * * The argument "replace" is evaluated and can contain any valid expression, * and these ones: * ${re:0} .. ${re:99} match 0 to 99 (0 is whole match, 1 .. 99 are groups * captured) * ${re:+} the last match (with highest number) * * Examples: * * string | regex | replace | result * ----------+---------------+----------------------------+------------- * test foo | test | Z | Z foo * test foo | ^(test +)(.*) | ${re:2} | foo * test foo | ^(test +)(.*) | ${re:1}/ ${hide:*,${re:2}} | test / *** * test foo | ^(test +)(.*) | ${hide:%,${re:+}} | %%% * * Note: result must be freed after use. */ char * eval_replace_regex (const char *string, regex_t *regex, const char *replace, struct t_eval_context *eval_context) { char *result, *result2, *str_replace; int length, length_replace, start_offset, i, rc, end, debug_id; int empty_replace_allowed; struct t_eval_regex eval_regex; result = NULL; EVAL_DEBUG_MSG(1, "eval_replace_regex(\"%s\", 0x%lx, \"%s\")", string, regex, replace); if (!string || !regex || !replace) goto end; length = strlen (string) + 1; result = malloc (length); if (!result) goto end; snprintf (result, length, "%s", string); eval_context->regex = &eval_regex; eval_context->regex_replacement_index = 1; start_offset = 0; /* we allow one empty replace if input string is empty */ empty_replace_allowed = (result[0]) ? 0 : 1; while (result) { for (i = 0; i < 100; i++) { eval_regex.match[i].rm_so = -1; } rc = regexec (regex, result + start_offset, 100, eval_regex.match, 0); /* no match found: exit the loop */ if ((rc != 0) || (eval_regex.match[0].rm_so < 0)) break; /* * if empty string is matching, continue only if empty replace is * still allowed (to prevent infinite loop) */ if (eval_regex.match[0].rm_eo <= 0) { if (!empty_replace_allowed) break; empty_replace_allowed = 0; } /* adjust the start/end offsets */ eval_regex.last_match = 0; for (i = 0; i < 100; i++) { if (eval_regex.match[i].rm_so >= 0) { eval_regex.last_match = i; eval_regex.match[i].rm_so += start_offset; eval_regex.match[i].rm_eo += start_offset; } } /* check if the regex matched the end of string */ end = !result[eval_regex.match[0].rm_eo]; eval_regex.result = result; str_replace = eval_replace_vars (replace, eval_context); length_replace = (str_replace) ? strlen (str_replace) : 0; length = eval_regex.match[0].rm_so + length_replace + strlen (result + eval_regex.match[0].rm_eo) + 1; result2 = malloc (length); if (!result2) { free (result); result = NULL; goto end; } result2[0] = '\0'; if (eval_regex.match[0].rm_so > 0) { memcpy (result2, result, eval_regex.match[0].rm_so); result2[eval_regex.match[0].rm_so] = '\0'; } if (str_replace) strcat (result2, str_replace); strcat (result2, result + eval_regex.match[0].rm_eo); free (result); result = result2; if (str_replace) free (str_replace); if (end) break; start_offset = eval_regex.match[0].rm_so + length_replace; if (!result[start_offset]) break; (eval_context->regex_replacement_index)++; } end: EVAL_DEBUG_RESULT(1, result); return result; } /* * Evaluates an expression. * * The hashtable "pointers" must have string for keys, pointer for values. * The hashtable "extra_vars" must have string for keys and values. * The hashtable "options" must have string for keys and values. * * Supported options: * - prefix: change the default prefix before variables to replace ("${") * - suffix: change the default suffix after variables to replace ('}") * - type: * - condition: evaluate as a condition (use operators/parentheses, * return a boolean) * * If the expression is a condition, it can contain: * - conditions: == != < <= > >= * - logical operators: && || * - parentheses for priority * * Examples of simple expression without condition (the [ ] are NOT part of * result): * >> ${window.buffer.number} * == [2] * >> buffer:${window.buffer.full_name} * == [buffer:irc.libera.#weechat] * >> ${window.win_width} * == [112] * >> ${window.win_height} * == [40] * * Examples of conditions: * >> ${window.buffer.full_name} == irc.libera.#weechat * == [1] * >> ${window.buffer.full_name} == irc.libera.#test * == [0] * >> ${window.win_width} >= 30 && ${window.win_height} >= 20 * == [1] * * Note: result must be freed after use (if not NULL). */ char * eval_expression (const char *expr, struct t_hashtable *pointers, struct t_hashtable *extra_vars, struct t_hashtable *options) { struct t_eval_context context, *eval_context; struct t_hashtable *user_vars; int condition, rc, pointers_allocated, regex_allocated, debug_id; int ptr_window_added, ptr_buffer_added; long number; char *value, *error; const char *default_prefix = EVAL_DEFAULT_PREFIX; const char *default_suffix = EVAL_DEFAULT_SUFFIX; const char *ptr_value, *regex_replace; struct t_gui_window *window; regex_t *regex; if (!expr) return NULL; condition = 0; user_vars = NULL; pointers_allocated = 0; regex_allocated = 0; regex = NULL; regex_replace = NULL; ptr_window_added = 0; ptr_buffer_added = 0; if (pointers) { regex = (regex_t *)hashtable_get (pointers, "regex"); } else { /* create hashtable pointers if it's NULL */ pointers = hashtable_new (32, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_POINTER, NULL, NULL); if (!pointers) return NULL; pointers_allocated = 1; } user_vars = hashtable_new (32, WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING, NULL, NULL); eval_context = &context; eval_context->pointers = pointers; eval_context->extra_vars = extra_vars; eval_context->user_vars = user_vars; eval_context->extra_vars_eval = 0; eval_context->prefix = default_prefix; eval_context->length_prefix = strlen (eval_context->prefix); eval_context->suffix = default_suffix; eval_context->length_suffix = strlen (eval_context->suffix); eval_context->regex = NULL; eval_context->regex_replacement_index = 1; eval_context->recursion_count = 0; eval_context->syntax_highlight = 0; eval_context->debug_level = 0; eval_context->debug_depth = 0; eval_context->debug_id = 0; eval_context->debug_output = NULL; /* * set window/buffer with pointer to current window/buffer * (if not already defined in the hashtable) */ if (gui_current_window) { if (!hashtable_has_key (pointers, "window")) { hashtable_set (pointers, "window", gui_current_window); ptr_window_added = 1; } if (!hashtable_has_key (pointers, "buffer")) { window = (struct t_gui_window *)hashtable_get (pointers, "window"); if (window) { hashtable_set (pointers, "buffer", window->buffer); ptr_buffer_added = 1; } } } /* read options */ if (options) { /* check the type of evaluation */ ptr_value = hashtable_get (options, "type"); if (ptr_value && (strcmp (ptr_value, "condition") == 0)) condition = 1; /* check if extra vars must be evaluated */ ptr_value = hashtable_get (options, "extra"); if (ptr_value && (strcmp (ptr_value, "eval") == 0)) eval_context->extra_vars_eval = 1; /* check for custom prefix */ ptr_value = hashtable_get (options, "prefix"); if (ptr_value && ptr_value[0]) { eval_context->prefix = ptr_value; eval_context->length_prefix = strlen (eval_context->prefix); } /* check for custom suffix */ ptr_value = hashtable_get (options, "suffix"); if (ptr_value && ptr_value[0]) { eval_context->suffix = ptr_value; eval_context->length_suffix = strlen (eval_context->suffix); } /* check for regex */ ptr_value = hashtable_get (options, "regex"); if (ptr_value) { regex = malloc (sizeof (*regex)); if (string_regcomp (regex, ptr_value, REG_EXTENDED | REG_ICASE) == 0) { regex_allocated = 1; } else { free (regex); regex = NULL; } } /* check for regex replacement (evaluated later) */ ptr_value = hashtable_get (options, "regex_replace"); if (ptr_value) { regex_replace = ptr_value; } /* check for debug */ ptr_value = hashtable_get (options, "debug"); if (ptr_value && ptr_value[0]) { error = NULL; number = strtol (ptr_value, &error, 10); if (error && !error[0] && (number >= 1)) { eval_context->debug_level = (int)number; eval_context->debug_output = string_dyn_alloc (256); } } } EVAL_DEBUG_MSG(1, "eval_expression(\"%s\")", expr); /* evaluate expression */ if (condition) { /* evaluate as condition (return a boolean: "0" or "1") */ value = eval_expression_condition (expr, eval_context); rc = eval_is_true (value); if (value) free (value); value = strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE); } else { if (regex && regex_replace) { /* replace with regex */ value = eval_replace_regex (expr, regex, regex_replace, eval_context); } else { /* only replace variables in expression */ value = eval_replace_vars (expr, eval_context); } } if (pointers_allocated) { hashtable_free (pointers); } else { if (ptr_window_added) hashtable_remove (pointers, "window"); if (ptr_buffer_added) hashtable_remove (pointers, "buffer"); } if (user_vars) hashtable_free (user_vars); if (regex && regex_allocated) { regfree (regex); free (regex); } EVAL_DEBUG_RESULT(1, value); /* set debug in options hashtable */ if (options && eval_context->debug_output) hashtable_set (options, "debug_output", *(eval_context->debug_output)); if (eval_context->debug_output) string_dyn_free (eval_context->debug_output, 1); return value; }