weechat/src/core/wee-string.c

3899 lines
102 KiB
C

/*
* wee-string.c - string functions
*
* Copyright (C) 2003-2019 Sébastien Helleu <flashcode@flashtux.org>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <wctype.h>
#include <regex.h>
#include <wchar.h>
#include <stdint.h>
#ifdef HAVE_ICONV
#include <iconv.h>
#endif
#ifndef ICONV_CONST
#ifdef ICONV_2ARG_IS_CONST
#define ICONV_CONST const
#else
#define ICONV_CONST
#endif
#endif /* ICONV_CONST */
#include "weechat.h"
#include "wee-string.h"
#include "wee-config.h"
#include "wee-eval.h"
#include "wee-hashtable.h"
#include "wee-utf8.h"
#include "../gui/gui-chat.h"
#include "../gui/gui-color.h"
#include "../plugins/plugin.h"
#define IS_OCTAL_DIGIT(c) ((c >= '0') && (c <= '7'))
#define HEX2DEC(c) (((c >= 'a') && (c <= 'f')) ? c - 'a' + 10 : \
((c >= 'A') && (c <= 'F')) ? c - 'A' + 10 : \
c - '0')
struct t_hashtable *string_hashtable_shared = NULL;
/*
* Defines a "strndup" function for systems where this function does not exist
* (FreeBSD and maybe others).
*
* Note: result must be freed after use.
*/
char *
string_strndup (const char *string, int length)
{
char *result;
if (!string || (length < 0))
return NULL;
if ((int)strlen (string) < length)
return strdup (string);
result = malloc (length + 1);
if (!result)
return NULL;
memcpy (result, string, length);
result[length] = '\0';
return result;
}
/*
* Cuts a string after max "length" chars, adds an optional suffix
* after the string if it is cut.
*
* If count_suffix == 1, the length of suffix is counted in the max length.
*
* If screen == 1, the cut is based on width of chars displayed.
*
* Note: result must be freed after use.
*/
char *
string_cut (const char *string, int length, int count_suffix, int screen,
const char *cut_suffix)
{
int length_result, length_cut_suffix;
char *result;
const char *ptr_string;
if (!string)
return NULL;
if (screen)
ptr_string = gui_chat_string_add_offset_screen (string, length);
else
ptr_string = gui_chat_string_add_offset (string, length);
if (!ptr_string || !ptr_string[0])
{
/* no cut */
return strdup (string);
}
if (cut_suffix && cut_suffix[0])
{
length_cut_suffix = strlen (cut_suffix);
if (count_suffix)
{
if (screen)
length -= utf8_strlen_screen (cut_suffix);
else
length -= utf8_strlen (cut_suffix);
if (length < 0)
return strdup ("");
if (screen)
ptr_string = gui_chat_string_add_offset_screen (string, length);
else
ptr_string = gui_chat_string_add_offset (string, length);
if (!ptr_string || !ptr_string[0])
{
/* no cut */
return strdup (string);
}
}
length_result = (ptr_string - string) + length_cut_suffix + 1;
result = malloc (length_result);
if (!result)
return NULL;
memcpy (result, string, ptr_string - string);
memcpy (result + (ptr_string - string), cut_suffix,
length_cut_suffix + 1);
return result;
}
else
{
return string_strndup (string, ptr_string - string);
}
}
/*
* Reverses a string.
*
* Note: result must be freed after use.
*/
char *
string_reverse (const char *string)
{
int length, char_size;
const char *ptr_string;
char *result, *ptr_result;
if (!string)
return NULL;
if (!string[0])
return strdup (string);
length = strlen (string);
result = malloc (length + 1);
if (!result)
return NULL;
ptr_string = string;
ptr_result = result + length;
ptr_result[0] = '\0';
while (ptr_string && ptr_string[0])
{
char_size = utf8_char_size (ptr_string);
ptr_result -= char_size;
memcpy (ptr_result, ptr_string, char_size);
ptr_string += char_size;
}
return result;
}
/*
* Repeats a string a given number of times.
*
* Note: result must be freed after use.
*/
char *
string_repeat (const char *string, int count)
{
int length_string, length_result, i;
char *result;
if (!string)
return NULL;
if (!string[0] || (count <= 0))
return strdup ("");
if (count == 1)
return strdup (string);
length_string = strlen (string);
length_result = (length_string * count) + 1;
result = malloc (length_result);
if (!result)
return NULL;
i = 0;
while (count > 0)
{
memcpy (result + i, string, length_string);
count--;
i += length_string;
}
result[length_result - 1] = '\0';
return result;
}
/*
* Converts string to lower case (locale independent).
*/
void
string_tolower (char *string)
{
while (string && string[0])
{
if ((string[0] >= 'A') && (string[0] <= 'Z'))
string[0] += ('a' - 'A');
string = (char *)utf8_next_char (string);
}
}
/*
* Converts string to upper case (locale independent).
*/
void
string_toupper (char *string)
{
while (string && string[0])
{
if ((string[0] >= 'a') && (string[0] <= 'z'))
string[0] -= ('a' - 'A');
string = (char *)utf8_next_char (string);
}
}
/*
* Compares two strings (locale and case independent).
*
* Returns:
* -1: string1 < string2
* 0: string1 == string2
* 1: string1 > string2
*/
int
string_strcasecmp (const char *string1, const char *string2)
{
int diff;
if (!string1 || !string2)
return (string1) ? 1 : ((string2) ? -1 : 0);
while (string1[0] && string2[0])
{
diff = utf8_charcasecmp (string1, string2);
if (diff != 0)
return (diff < 0) ? -1 : 1;
string1 = utf8_next_char (string1);
string2 = utf8_next_char (string2);
}
return (string1[0]) ? 1 : ((string2[0]) ? -1 : 0);
}
/*
* Compares two strings (locale and case independent) using a range.
*
* The range is the number of chars which can be converted from upper to lower
* case. For example 26 = all letters of alphabet, 29 = all letters + 3 chars.
*
* Examples:
* - range = 26: A-Z ==> a-z
* - range = 29: A-Z [ \ ] ==> a-z { | }
* - range = 30: A-Z [ \ ] ^ ==> a-z { | } ~
* (ranges 29 and 30 are used by some protocols like IRC)
*
* Returns:
* -1: string1 < string2
* 0: string1 == string2
* 1: string1 > string2
*/
int
string_strcasecmp_range (const char *string1, const char *string2, int range)
{
int diff;
if (!string1 || !string2)
return (string1) ? 1 : ((string2) ? -1 : 0);
while (string1[0] && string2[0])
{
diff = utf8_charcasecmp_range (string1, string2, range);
if (diff != 0)
return (diff < 0) ? -1 : 1;
string1 = utf8_next_char (string1);
string2 = utf8_next_char (string2);
}
return (string1[0]) ? 1 : ((string2[0]) ? -1 : 0);
}
/*
* Compares two strings with max length (locale and case independent).
*
* Returns:
* -1: string1 < string2
* 0: string1 == string2
* 1: string1 > string2
*/
int
string_strncasecmp (const char *string1, const char *string2, int max)
{
int count, diff;
if (!string1 || !string2)
return (string1) ? 1 : ((string2) ? -1 : 0);
count = 0;
while ((count < max) && string1[0] && string2[0])
{
diff = utf8_charcasecmp (string1, string2);
if (diff != 0)
return (diff < 0) ? -1 : 1;
string1 = utf8_next_char (string1);
string2 = utf8_next_char (string2);
count++;
}
if (count >= max)
return 0;
else
return (string1[0]) ? 1 : ((string2[0]) ? -1 : 0);
}
/*
* Compares two strings with max length (locale and case independent) using a
* range.
*
* The range is the number of chars which can be converted from upper to lower
* case. For example 26 = all letters of alphabet, 29 = all letters + 3 chars.
*
* Examples:
* - range = 26: A-Z ==> a-z
* - range = 29: A-Z [ \ ] ==> a-z { | }
* - range = 30: A-Z [ \ ] ^ ==> a-z { | } ~
* (ranges 29 and 30 are used by some protocols like IRC)
*
* Returns:
* -1: string1 < string2
* 0: string1 == string2
* 1: string1 > string2
*/
int
string_strncasecmp_range (const char *string1, const char *string2, int max,
int range)
{
int count, diff;
if (!string1 || !string2)
return (string1) ? 1 : ((string2) ? -1 : 0);
count = 0;
while ((count < max) && string1[0] && string2[0])
{
diff = utf8_charcasecmp_range (string1, string2, range);
if (diff != 0)
return (diff < 0) ? -1 : 1;
string1 = utf8_next_char (string1);
string2 = utf8_next_char (string2);
count++;
}
if (count >= max)
return 0;
else
return (string1[0]) ? 1 : ((string2[0]) ? -1 : 0);
}
/*
* Compares two strings, ignoring some chars.
*
* Returns:
* -1: string1 < string2
* 0: string1 == string2
* 1: string1 > string2
*/
int
string_strcmp_ignore_chars (const char *string1, const char *string2,
const char *chars_ignored, int case_sensitive)
{
int diff;
if (!string1 || !string2)
return (string1) ? 1 : ((string2) ? -1 : 0);
while (string1 && string1[0] && string2 && string2[0])
{
/* skip ignored chars */
while (string1 && string1[0] && strchr (chars_ignored, string1[0]))
{
string1 = utf8_next_char (string1);
}
while (string2 && string2[0] && strchr (chars_ignored, string2[0]))
{
string2 = utf8_next_char (string2);
}
/* end of one (or both) string(s) ? */
if ((!string1 || !string1[0]) && (!string2 || !string2[0]))
return 0;
if ((!string1 || !string1[0]) && string2 && string2[0])
return -1;
if (string1 && string1[0] && (!string2 || !string2[0]))
return 1;
/* look at diff */
diff = (case_sensitive) ?
(int)string1[0] - (int)string2[0] : utf8_charcasecmp (string1, string2);
if (diff != 0)
return (diff < 0) ? -1 : 1;
string1 = utf8_next_char (string1);
string2 = utf8_next_char (string2);
/* skip ignored chars */
while (string1 && string1[0] && strchr (chars_ignored, string1[0]))
{
string1 = utf8_next_char (string1);
}
while (string2 && string2[0] && strchr (chars_ignored, string2[0]))
{
string2 = utf8_next_char (string2);
}
}
if ((!string1 || !string1[0]) && string2 && string2[0])
return -1;
if (string1 && string1[0] && (!string2 || !string2[0]))
return 1;
return 0;
}
/*
* Searches for a string in another string (locale and case independent).
*
* Returns pointer to string found, or NULL if not found.
*/
const char *
string_strcasestr (const char *string, const char *search)
{
int length_search;
length_search = utf8_strlen (search);
if (!string || !search || (length_search == 0))
return NULL;
while (string[0])
{
if (string_strncasecmp (string, search, length_search) == 0)
return (char *)string;
string = utf8_next_char (string);
}
return NULL;
}
/*
* Checks if a string matches a mask.
*
* The mask can contain wildcards ("*"), each wildcard matches 0 or more chars
* in the string.
*
* Returns:
* 1: string matches mask
* 0: string does not match mask
*/
int
string_match (const char *string, const char *mask, int case_sensitive)
{
const char *ptr_string, *ptr_mask, *pos_word, *pos_word2, *pos_end;
char *word;
int wildcard, length_word;
if (!string || !mask || !mask[0])
return 0;
ptr_string = string;
ptr_mask = mask;
while (ptr_mask[0])
{
wildcard = 0;
/* if we are on a wildcard, set the wildcard flag and skip it */
if (ptr_mask[0] == '*')
{
wildcard = 1;
ptr_mask++;
while (ptr_mask[0] == '*')
{
ptr_mask++;
}
if (!ptr_mask[0])
return 1;
}
/* no match if some mask without string */
if (!string[0])
return 0;
/* search the next wildcard (after the word) */
pos_end = strchr (ptr_mask, '*');
/* extract the word before the wildcard (or the end of mask) */
if (pos_end)
{
length_word = pos_end - ptr_mask;
}
else
{
length_word = strlen (ptr_mask);
pos_end = ptr_mask + length_word;
}
word = string_strndup (ptr_mask, length_word);
if (!word)
return 0;
/* check if the word is matching */
if (wildcard)
{
/*
* search the word anywhere in the string (from current position),
* multiple times if needed
*/
pos_word = (case_sensitive) ?
strstr (ptr_string, word) : string_strcasestr (ptr_string, word);
if (!pos_word)
{
free (word);
return 0;
}
while (1)
{
pos_word2 = (case_sensitive) ?
strstr (pos_word + length_word, word) :
string_strcasestr (pos_word + length_word, word);
if (!pos_word2)
break;
pos_word = pos_word2;
}
ptr_string = pos_word + length_word;
}
else
{
/* check if word is at beginning of string */
if ((case_sensitive
&& (strncmp (ptr_string, word, length_word) != 0))
|| (!case_sensitive
&& (string_strncasecmp (ptr_string, word,
utf8_strlen (word)) != 0)))
{
free (word);
return 0;
}
ptr_string += length_word;
}
free (word);
ptr_mask = pos_end;
}
/* match if no more string/mask */
if (!ptr_string[0] && !ptr_mask[0])
return 1;
/* no match in other cases */
return 0;
}
/*
* Checks if a string matches a list of masks. Negative masks are allowed
* with "!mask" to exclude this mask and have higher priority than standard
* masks.
*
* Each mask is compared with the function string_match.
*
* Example of masks to allow anything by default, but "toto" and "abc" are
* forbidden:
* "*", "!toto", "!abc"
*
* Returns:
* 1: string matches list of masks
* 0: string does not match list of masks
*/
int
string_match_list (const char *string, const char **masks, int case_sensitive)
{
int match, i;
const char *ptr_mask;
if (!string || !masks)
return 0;
match = 0;
for (i = 0; masks[i]; i++)
{
ptr_mask = (masks[i][0] == '!') ? masks[i] + 1 : masks[i];
if (string_match (string, ptr_mask, case_sensitive))
{
if (masks[i][0] == '!')
return 0;
else
match = 1;
}
}
return match;
}
/*
* Expands home in a path.
*
* Example: "~/file.txt" => "/home/xxx/file.txt"
*
* Note: result must be freed after use.
*/
char *
string_expand_home (const char *path)
{
char *ptr_home, *str;
int length;
if (!path)
return NULL;
if (!path[0] || (path[0] != '~')
|| ((path[1] && path[1] != DIR_SEPARATOR_CHAR)))
{
return strdup (path);
}
ptr_home = getenv ("HOME");
if (!ptr_home)
return NULL;
length = strlen (ptr_home) + strlen (path + 1) + 1;
str = malloc (length);
if (!str)
return strdup (path);
snprintf (str, length, "%s%s", ptr_home, path + 1);
return str;
}
/*
* Evaluate a path by replacing (in this order):
* 1. "%h" (at beginning of string) by WeeChat home directory.
* 2. "~" by user home directory (call to string_expand_home)
* 3. evaluated variables (see /help eval)
*
* Returns the evaluated path, NULL if error.
*
* Note: result must be freed after use.
*/
char *
string_eval_path_home (const char *path,
struct t_hashtable *pointers,
struct t_hashtable *extra_vars,
struct t_hashtable *options)
{
char *path1, *path2, *path3;
int length;
if (!path)
return NULL;
path1 = NULL;
path2 = NULL;
path3 = NULL;
/* replace "%h" by WeeChat home */
if (strncmp (path, "%h", 2) == 0)
{
length = strlen (weechat_home) + strlen (path + 2) + 1;
path1 = malloc (length);
if (path1)
snprintf (path1, length, "%s%s", weechat_home, path + 2);
}
else
path1 = strdup (path);
if (!path1)
goto end;
/* replace "~" by user home */
path2 = string_expand_home (path1);
if (!path2)
goto end;
/* evaluate content of path */
path3 = eval_expression (path2, pointers, extra_vars, options);
end:
if (path1)
free (path1);
if (path2)
free (path2);
return path3;
}
/*
* Removes quotes at beginning/end of string (ignores spaces if there are before
* first quote or after last quote).
*
* Note: result must be freed after use.
*/
char *
string_remove_quotes (const char *string, const char *quotes)
{
int length;
const char *pos_start, *pos_end;
if (!string || !quotes)
return NULL;
if (!string[0])
return strdup (string);
pos_start = string;
while (pos_start[0] == ' ')
{
pos_start++;
}
length = strlen (string);
pos_end = string + length - 1;
while ((pos_end[0] == ' ') && (pos_end > pos_start))
{
pos_end--;
}
if (!pos_start[0] || !pos_end[0] || (pos_end <= pos_start))
return strdup (string);
if (strchr (quotes, pos_start[0]) && (pos_end[0] == pos_start[0]))
{
if (pos_end == (pos_start + 1))
return strdup ("");
return string_strndup (pos_start + 1, pos_end - pos_start - 1);
}
return strdup (string);
}
/*
* Strips chars at beginning/end of string.
*
* Note: result must be freed after use.
*/
char *
string_strip (const char *string, int left, int right, const char *chars)
{
const char *ptr_start, *ptr_end;
if (!string)
return NULL;
if (!string[0] || !chars)
return strdup (string);
ptr_start = string;
ptr_end = string + strlen (string) - 1;
if (left)
{
while (ptr_start[0] && strchr (chars, ptr_start[0]))
{
ptr_start++;
}
if (!ptr_start[0])
return strdup (ptr_start);
}
if (right)
{
while ((ptr_end >= ptr_start) && strchr (chars, ptr_end[0]))
{
ptr_end--;
}
if (ptr_end < ptr_start)
return strdup ("");
}
return string_strndup (ptr_start, ptr_end - ptr_start + 1);
}
/*
* Converts escaped chars to their value.
*
* Following escaped chars are supported:
* \" double quote
* \\ backslash
* \a alert (BEL)
* \b backspace
* \e escape
* \f form feed
* \n new line
* \r carriage return
* \t horizontal tab
* \v vertical tab
* \0ooo char as octal value (ooo is 0 to 3 digits)
* \xhh char as hexadecimal value (hh is 1 to 2 digits)
* \uhhhh unicode char as hexadecimal value (hhhh is 1 to 4 digits)
* \Uhhhhhhhh unicode char as hexadecimal value (hhhhhhhh is 1 to 8 digits)
*
* Note: result must be freed after use.
*/
char *
string_convert_escaped_chars (const char *string)
{
const unsigned char *ptr_string;
char *output, utf_char[16];
int pos_output, i, length;
unsigned int value;
if (!string)
return NULL;
/* the output length is always <= to string length */
output = malloc (strlen (string) + 1);
if (!output)
return NULL;
pos_output = 0;
ptr_string = (const unsigned char *)string;
while (ptr_string && ptr_string[0])
{
if (ptr_string[0] == '\\')
{
ptr_string++;
switch (ptr_string[0])
{
case '"': /* double quote */
output[pos_output++] = '"';
ptr_string++;
break;
case '\\': /* backslash */
output[pos_output++] = '\\';
ptr_string++;
break;
case 'a': /* alert */
output[pos_output++] = 7;
ptr_string++;
break;
case 'b': /* backspace */
output[pos_output++] = 8;
ptr_string++;
break;
case 'e': /* escape */
output[pos_output++] = 27;
ptr_string++;
break;
case 'f': /* form feed */
output[pos_output++] = 12;
ptr_string++;
break;
case 'n': /* new line */
output[pos_output++] = 10;
ptr_string++;
break;
case 'r': /* carriage return */
output[pos_output++] = 13;
ptr_string++;
break;
case 't': /* horizontal tab */
output[pos_output++] = 9;
ptr_string++;
break;
case 'v': /* vertical tab */
output[pos_output++] = 11;
ptr_string++;
break;
case '0': /* char as octal value (0 to 3 digits) */
value = 0;
for (i = 0; (i < 3) && IS_OCTAL_DIGIT(ptr_string[i + 1]); i++)
{
value = (value * 8) + (ptr_string[i + 1] - '0');
}
output[pos_output++] = value;
ptr_string += 1 + i;
break;
case 'x': /* char as hexadecimal value (1 to 2 digits) */
case 'X':
if (isxdigit (ptr_string[1]))
{
value = 0;
for (i = 0; (i < 2) && isxdigit (ptr_string[i + 1]); i++)
{
value = (value * 16) + HEX2DEC(ptr_string[i + 1]);
}
output[pos_output++] = value;
ptr_string += 1 + i;
}
else
{
output[pos_output++] = ptr_string[0];
ptr_string++;
}
break;
case 'u': /* unicode char as hexadecimal (1 to 4 digits) */
case 'U': /* unicode char as hexadecimal (1 to 8 digits) */
if (isxdigit (ptr_string[1]))
{
value = 0;
for (i = 0;
(i < ((ptr_string[0] == 'u') ? 4 : 8))
&& isxdigit (ptr_string[i + 1]);
i++)
{
value = (value * 16) + HEX2DEC(ptr_string[i + 1]);
}
utf8_int_string (value, utf_char);
if (utf_char[0])
{
length = strlen (utf_char);
memcpy (output + pos_output, utf_char, length);
pos_output += length;
}
ptr_string += 1 + i;
}
else
{
output[pos_output++] = ptr_string[0];
ptr_string++;
}
break;
default:
if (ptr_string[0])
{
output[pos_output++] = '\\';
output[pos_output++] = ptr_string[0];
ptr_string++;
}
break;
}
}
else
{
output[pos_output++] = ptr_string[0];
ptr_string++;
}
}
output[pos_output] = '\0';
return output;
}
/*
* Checks if first char of string is a "word char".
*
* The word chars are customizable with options "weechat.look.word_chars_*".
*
* Returns:
* 1: first char is a word char
* 0: first char is not a word char
*/
int
string_is_word_char (const char *string,
struct t_config_look_word_char_item *word_chars,
int word_chars_count)
{
wint_t c;
int i, match;
c = utf8_wide_char (string);
if (c == WEOF)
return 0;
for (i = 0; i < word_chars_count; i++)
{
if (word_chars[i].wc_class != (wctype_t)0)
{
match = iswctype (c, word_chars[i].wc_class);
}
else
{
if ((word_chars[i].char1 == 0)
&& (word_chars[i].char2 == 0))
{
match = 1;
}
else
{
match = ((c >= word_chars[i].char1) &&
(c <= word_chars[i].char2));
}
}
if (match)
return (word_chars[i].exclude) ? 0 : 1;
}
/* not a word char */
return 0;
}
/*
* Checks if first char of string is a "word char" (for highlight).
*
* The word chars for highlights are customizable with option
* "weechat.look.word_chars_highlight".
*
* Returns:
* 1: first char is a word char
* 0: first char is not a word char
*/
int
string_is_word_char_highlight (const char *string)
{
return string_is_word_char (string,
config_word_chars_highlight,
config_word_chars_highlight_count);
}
/*
* Checks if first char of string is a "word char" (for input).
*
* The word chars for input are customizable with option
* "weechat.look.word_chars_input".
*
* Returns:
* 1: first char is a word char
* 0: first char is not a word char
*/
int
string_is_word_char_input (const char *string)
{
return string_is_word_char (string,
config_word_chars_input,
config_word_chars_input_count);
}
/*
* Converts a mask (string with only "*" as wildcard) to a regex, paying
* attention to special chars in a regex.
*
* Note: result must be freed after use.
*/
char *
string_mask_to_regex (const char *mask)
{
char *result;
const char *ptr_mask;
int index_result;
char *regex_special_char = ".[]{}()?+|^$\\";
if (!mask)
return NULL;
result = malloc ((strlen (mask) * 2) + 1);
if (!result)
return NULL;
result[0] = '\0';
index_result = 0;
ptr_mask = mask;
while (ptr_mask[0])
{
/* '*' in string ? then replace by '.*' */
if (ptr_mask[0] == '*')
{
result[index_result++] = '.';
result[index_result++] = '*';
}
/* special regex char in string ? escape it with '\' */
else if (strchr (regex_special_char, ptr_mask[0]))
{
result[index_result++] = '\\';
result[index_result++] = ptr_mask[0];
}
/* standard char, just copy it */
else
result[index_result++] = ptr_mask[0];
ptr_mask++;
}
/* add final '\0' */
result[index_result] = '\0';
return result;
}
/*
* Extracts flags and regex from a string.
*
* Format of flags is: (?eins-eins)string
* Flags are:
* e: POSIX extended regex (REG_EXTENDED)
* i: case insensitive (REG_ICASE)
* n: match-any-character operators don't match a newline (REG_NEWLINE)
* s: support for substring addressing of matches is not required (REG_NOSUB)
*
* Examples (with default_flags = REG_EXTENDED):
* "(?i)toto" : regex "toto", flags = REG_EXTENDED | REG_ICASE
* "(?i)toto" : regex "toto", flags = REG_EXTENDED | REG_ICASE
* "(?i-e)toto": regex "toto", flags = REG_ICASE
*/
const char *
string_regex_flags (const char *regex, int default_flags, int *flags)
{
const char *ptr_regex, *ptr_flags;
int set_flag, flag;
char *pos;
if (flags)
*flags = default_flags;
if (!regex)
return NULL;
ptr_regex = regex;
while (strncmp (ptr_regex, "(?", 2) == 0)
{
pos = strchr (ptr_regex, ')');
if (!pos)
break;
if (!isalpha ((unsigned char)ptr_regex[2]) && (ptr_regex[2] != '-'))
break;
if (flags)
{
set_flag = 1;
for (ptr_flags = ptr_regex + 2; ptr_flags < pos; ptr_flags++)
{
flag = 0;
switch (ptr_flags[0])
{
case '-':
set_flag = 0;
break;
case 'e':
flag = REG_EXTENDED;
break;
case 'i':
flag = REG_ICASE;
break;
case 'n':
flag = REG_NEWLINE;
break;
case 's':
flag = REG_NOSUB;
break;
}
if (flag > 0)
{
if (set_flag)
*flags |= flag;
else
*flags &= ~flag;
}
}
}
ptr_regex = pos + 1;
}
return ptr_regex;
}
/*
* Compiles a regex using optional flags at beginning of string (for format of
* flags in regex, see string_regex_flags()).
*
* Returns:
* 0: successful compilation
* other value: compilation failed
*/
int
string_regcomp (void *preg, const char *regex, int default_flags)
{
const char *ptr_regex;
int flags;
if (!regex)
return -1;
ptr_regex = string_regex_flags (regex, default_flags, &flags);
return regcomp ((regex_t *)preg,
(ptr_regex && ptr_regex[0]) ? ptr_regex : "^",
flags);
}
/*
* Checks if a string has a highlight (using list of words to highlight).
*
* Returns:
* 1: string has a highlight
* 0: string has no highlight
*/
int
string_has_highlight (const char *string, const char *highlight_words)
{
const char *match, *match_pre, *match_post, *msg_pos;
char *msg, *highlight, *pos, *pos_end;
int end, length, startswith, endswith, wildcard_start, wildcard_end, flags;
if (!string || !string[0] || !highlight_words || !highlight_words[0])
return 0;
msg = strdup (string);
if (!msg)
return 0;
highlight = strdup (highlight_words);
if (!highlight)
{
free (msg);
return 0;
}
pos = highlight;
end = 0;
while (!end)
{
flags = 0;
pos = (char *)string_regex_flags (pos, REG_ICASE, &flags);
pos_end = strchr (pos, ',');
if (!pos_end)
{
pos_end = strchr (pos, '\0');
end = 1;
}
/* error parsing string! */
if (!pos_end)
{
free (msg);
free (highlight);
return 0;
}
length = pos_end - pos;
pos_end[0] = '\0';
if (length > 0)
{
if ((wildcard_start = (pos[0] == '*')))
{
pos++;
length--;
}
if ((wildcard_end = (*(pos_end - 1) == '*')))
{
*(pos_end - 1) = '\0';
length--;
}
}
if (length > 0)
{
msg_pos = msg;
while (1)
{
match = (flags & REG_ICASE) ?
string_strcasestr (msg_pos, pos) : strstr (msg_pos, pos);
if (!match)
break;
match_pre = utf8_prev_char (msg, match);
if (!match_pre)
match_pre = match - 1;
match_post = match + length;
startswith = ((match == msg) || (!string_is_word_char_highlight (match_pre)));
endswith = ((!match_post[0]) || (!string_is_word_char_highlight (match_post)));
if ((wildcard_start && wildcard_end) ||
(!wildcard_start && !wildcard_end &&
startswith && endswith) ||
(wildcard_start && endswith) ||
(wildcard_end && startswith))
{
/* highlight found! */
free (msg);
free (highlight);
return 1;
}
msg_pos = match_post;
}
}
if (!end)
pos = pos_end + 1;
}
free (msg);
free (highlight);
/* no highlight found */
return 0;
}
/*
* Checks if a string has a highlight using a compiled regular expression (any
* match in string must be surrounded by delimiters).
*/
int
string_has_highlight_regex_compiled (const char *string, regex_t *regex)
{
int rc, startswith, endswith;
regmatch_t regex_match;
const char *match_pre;
if (!string || !regex)
return 0;
while (string && string[0])
{
rc = regexec (regex, string, 1, &regex_match, 0);
/*
* no match found: exit the loop (if rm_eo == 0, it is an empty match
* at beginning of string: we consider there is no match, to prevent an
* infinite loop)
*/
if ((rc != 0) || (regex_match.rm_so < 0) || (regex_match.rm_eo <= 0))
break;
startswith = (regex_match.rm_so == 0);
if (!startswith)
{
match_pre = utf8_prev_char (string, string + regex_match.rm_so);
startswith = !string_is_word_char_highlight (match_pre);
}
endswith = 0;
if (startswith)
{
endswith = ((regex_match.rm_eo == (int)strlen (string))
|| !string_is_word_char_highlight (string + regex_match.rm_eo));
}
if (startswith && endswith)
return 1;
string += regex_match.rm_eo;
}
/* no highlight found */
return 0;
}
/*
* Checks if a string has a highlight using a regular expression (any match in
* string must be surrounded by delimiters).
*/
int
string_has_highlight_regex (const char *string, const char *regex)
{
regex_t reg;
int rc;
if (!string || !regex || !regex[0])
return 0;
if (string_regcomp (&reg, regex, REG_EXTENDED | REG_ICASE) != 0)
return 0;
rc = string_has_highlight_regex_compiled (string, &reg);
regfree (&reg);
return rc;
}
/*
* Replaces a string by new one in a string.
*
* Note: result must be freed after use.
*/
char *
string_replace (const char *string, const char *search, const char *replace)
{
const char *pos;
char *new_string;
int length1, length2, length_new, count;
if (!string || !search || !replace)
return NULL;
length1 = strlen (search);
length2 = strlen (replace);
/* count number of strings to replace */
count = 0;
pos = string;
while (pos && pos[0] && (pos = strstr (pos, search)))
{
count++;
pos += length1;
}
/* easy: no string to replace! */
if (count == 0)
return strdup (string);
/* compute needed memory for new string */
length_new = strlen (string) - (count * length1) + (count * length2) + 1;
/* allocate new string */
new_string = malloc (length_new);
if (!new_string)
return strdup (string);
/* replace all occurrences */
new_string[0] = '\0';
while (string && string[0])
{
pos = strstr (string, search);
if (pos)
{
strncat (new_string, string, pos - string);
strcat (new_string, replace);
pos += length1;
}
else
strcat (new_string, string);
string = pos;
}
return new_string;
}
/*
* Get replacement string for a regex, using array of "match"
* (for more info, see function "string_replace_regex").
*
* Note: result must be freed after use.
*/
char *
string_replace_regex_get_replace (const char *string, regmatch_t *regex_match,
int last_match, const char *replace,
const char reference_char,
char *(*callback)(void *data, const char *text),
void *callback_data)
{
int length, length_current, length_add, match;
const char *ptr_replace, *ptr_add;
char *result, *result2, *modified_replace, *temp, char_replace;
/* default length is length*2, it will grow later if needed */
length = (strlen (string) * 2);
result = malloc (length + 1);
if (!result)
return NULL;
result[0] = '\0';
length_current = 0;
ptr_replace = replace;
while (ptr_replace && ptr_replace[0])
{
ptr_add = NULL;
length_add = 0;
modified_replace = NULL;
if ((ptr_replace[0] == '\\') && (ptr_replace[1] == reference_char))
{
/* escaped reference char */
ptr_add = ptr_replace + 1;
length_add = 1;
ptr_replace += 2;
}
else if (ptr_replace[0] == reference_char)
{
if ((ptr_replace[1] == '+') || isdigit ((unsigned char)ptr_replace[1]))
{
if (ptr_replace[1] == '+')
{
/* reference to last match */
match = last_match;
ptr_replace += 2;
}
else
{
/* reference to match 0 .. 99 */
if (isdigit ((unsigned char)ptr_replace[2]))
{
match = ((ptr_replace[1] - '0') * 10) + (ptr_replace[2] - '0');
ptr_replace += 3;
}
else
{
match = ptr_replace[1] - '0';
ptr_replace += 2;
}
}
if (regex_match[match].rm_so >= 0)
{
if (callback)
{
temp = string_strndup (string + regex_match[match].rm_so,
regex_match[match].rm_eo - regex_match[match].rm_so);
if (temp)
{
modified_replace = (*callback) (callback_data, temp);
if (modified_replace)
{
ptr_add = modified_replace;
length_add = strlen (modified_replace);
}
free (temp);
}
}
if (!ptr_add)
{
ptr_add = string + regex_match[match].rm_so;
length_add = regex_match[match].rm_eo - regex_match[match].rm_so;
}
}
}
else if ((ptr_replace[1] == '.')
&& (ptr_replace[2] >= 32) && (ptr_replace[2] <= 126)
&& ((ptr_replace[3] == '+') || isdigit ((unsigned char)ptr_replace[3])))
{
char_replace = ptr_replace[2];
if (ptr_replace[3] == '+')
{
/* reference to last match */
match = last_match;
ptr_replace += 4;
}
else
{
/* reference to match 0 .. 99 */
if (isdigit ((unsigned char)ptr_replace[4]))
{
match = ((ptr_replace[3] - '0') * 10) + (ptr_replace[4] - '0');
ptr_replace += 5;
}
else
{
match = ptr_replace[3] - '0';
ptr_replace += 4;
}
}
if (regex_match[match].rm_so >= 0)
{
temp = string_strndup (string + regex_match[match].rm_so,
regex_match[match].rm_eo - regex_match[match].rm_so);
if (temp)
{
length_add = utf8_strlen (temp);
modified_replace = malloc (length_add + 1);
if (modified_replace)
{
memset (modified_replace, char_replace, length_add);
modified_replace[length_add] = '\0';
ptr_add = modified_replace;
}
free (temp);
}
}
}
else
{
/* just ignore the reference char */
ptr_replace++;
}
}
else
{
ptr_add = ptr_replace;
length_add = utf8_char_size (ptr_replace);
ptr_replace += length_add;
}
if (ptr_add)
{
if (length_current + length_add > length)
{
length = (length * 2 >= length_current + length_add) ?
length * 2 : length_current + length_add;
result2 = realloc (result, length + 1);
if (!result2)
{
if (modified_replace)
free (modified_replace);
free (result);
return NULL;
}
result = result2;
}
memcpy (result + length_current, ptr_add, length_add);
length_current += length_add;
result[length_current] = '\0';
}
if (modified_replace)
free (modified_replace);
}
return result;
}
/*
* 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" can contain references to matches:
* $0 .. $99 match 0 to 99 (0 is whole match, 1 .. 99 are groups captured)
* $+ the last match (with highest number)
* $.*N match N (can be '+' or 0 to 99), with all chars replaced by '*'
* (the char '*' can be replaced by any char between space (32)
* and '~' (126))
*
* If the callback is not NULL, it is called for every reference to a match
* (except for matches replaced by a char).
* If not NULL, the string returned by the callback (which must have been newly
* allocated) is used and freed after use.
*
* Examples:
*
* string | regex | replace | result
* ----------+---------------+----------+-------------
* test foo | test | Z | Z foo
* test foo | ^(test +)(.*) | $2 | foo
* test foo | ^(test +)(.*) | $1/ $.*2 | test / ***
* test foo | ^(test +)(.*) | $.%+ | %%%
*
* Note: result must be freed after use.
*/
char *
string_replace_regex (const char *string, void *regex, const char *replace,
const char reference_char,
char *(*callback)(void *data, const char *text),
void *callback_data)
{
char *result, *result2, *str_replace;
int length, length_replace, start_offset, i, rc, end, last_match;
regmatch_t regex_match[100];
if (!string || !regex)
return NULL;
length = strlen (string) + 1;
result = malloc (length);
if (!result)
return NULL;
snprintf (result, length, "%s", string);
start_offset = 0;
while (result && result[start_offset])
{
for (i = 0; i < 100; i++)
{
regex_match[i].rm_so = -1;
}
rc = regexec ((regex_t *)regex, result + start_offset, 100, regex_match,
0);
/*
* no match found: exit the loop (if rm_eo == 0, it is an empty match
* at beginning of string: we consider there is no match, to prevent an
* infinite loop)
*/
if ((rc != 0)
|| (regex_match[0].rm_so < 0) || (regex_match[0].rm_eo <= 0))
{
break;
}
/* adjust the start/end offsets */
last_match = 0;
for (i = 0; i < 100; i++)
{
if (regex_match[i].rm_so >= 0)
{
last_match = i;
regex_match[i].rm_so += start_offset;
regex_match[i].rm_eo += start_offset;
}
}
/* check if the regex matched the end of string */
end = !result[regex_match[0].rm_eo];
str_replace = string_replace_regex_get_replace (result,
regex_match,
last_match,
replace,
reference_char,
callback,
callback_data);
length_replace = (str_replace) ? strlen (str_replace) : 0;
length = regex_match[0].rm_so + length_replace +
strlen (result + regex_match[0].rm_eo) + 1;
result2 = malloc (length);
if (!result2)
{
free (result);
return NULL;
}
result2[0] = '\0';
if (regex_match[0].rm_so > 0)
{
memcpy (result2, result, regex_match[0].rm_so);
result2[regex_match[0].rm_so] = '\0';
}
if (str_replace)
strcat (result2, str_replace);
strcat (result2, result + regex_match[0].rm_eo);
free (result);
result = result2;
if (str_replace)
free (str_replace);
if (end)
break;
start_offset = regex_match[0].rm_so + length_replace;
}
return result;
}
/*
* Splits a string according to separators.
*
* This function must not be called directly (call string_split or
* string_split_shared instead).
*
* The flags is a combination of flags:
* - WEECHAT_STRING_SPLIT_STRIP_LEFT: strip separators on the left
* (beginning of string)
* - WEECHAT_STRING_SPLIT_STRIP_RIGHT: strip separators on the right
* (end of string)
* - WEECHAT_STRING_SPLIT_COLLAPSE_SEPS: collapse multiple consecutive
* separators into a single one
* - WEECHAT_STRING_SPLIT_KEEP_EOL: keep end of line for each value
*
* Examples:
* string_split ("abc de fghi ", " ", 0, 0, &argc)
* ==> array[0] == "abc"
* array[1] == "de"
* array[2] == ""
* array[3] == "fghi"
* array[4] == ""
* array[5] == NULL
* argc == 5
* string_split ("abc de fghi ", " ",
* WEECHAT_STRING_SPLIT_STRIP_LEFT
* | WEECHAT_STRING_SPLIT_STRIP_RIGHT
* | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
* 0, &argc)
* ==> array[0] == "abc"
* array[1] == "de"
* array[2] == "fghi"
* array[3] == NULL
* argc == 3
* string_split ("abc de fghi ", " ",
* WEECHAT_STRING_SPLIT_STRIP_LEFT
* | WEECHAT_STRING_SPLIT_STRIP_RIGHT
* | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS
* | WEECHAT_STRING_SPLIT_KEEP_EOL,
* 0, &argc)
* ==> array[0] == "abc de fghi"
* array[1] == "de fghi"
* array[2] == "fghi"
* array[3] == NULL
* argc == 3
* string_split ("abc de fghi ", " ",
* WEECHAT_STRING_SPLIT_STRIP_LEFT
* | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS
* | WEECHAT_STRING_SPLIT_KEEP_EOL,
* 0, &argc)
* ==> array[0] == "abc de fghi "
* array[1] == "de fghi "
* array[2] == "fghi "
* array[3] == NULL
* argc == 3
*/
char **
string_split_internal (const char *string, const char *separators, int flags,
int num_items_max, int *num_items, int shared)
{
int i, j, count_items;
char *string2, **array;
char *ptr, *ptr1, *ptr2;
const char *str_shared;
if (num_items)
*num_items = 0;
if (!string || !string[0] || !separators || !separators[0])
return NULL;
string2 = string_strip (
string,
(flags & WEECHAT_STRING_SPLIT_STRIP_LEFT) ? 1 : 0,
(flags & WEECHAT_STRING_SPLIT_STRIP_RIGHT) ? 1 : 0,
separators);
if (!string2)
return NULL;
if (!string2[0])
{
free (string2);
return NULL;
}
/* calculate number of items */
ptr = string2;
i = 1;
while ((ptr = strpbrk (ptr, separators)))
{
if (flags & WEECHAT_STRING_SPLIT_COLLAPSE_SEPS)
{
while (ptr[0] && strchr (separators, ptr[0]))
{
ptr++;
}
if (ptr[0])
i++;
}
else
{
ptr++;
i++;
}
}
count_items = i;
if ((num_items_max != 0) && (count_items > num_items_max))
count_items = num_items_max;
array = malloc ((count_items + 1) * sizeof (array[0]));
if (!array)
{
free (string2);
return NULL;
}
for (i = 0; i < count_items + 1; i++)
{
array[i] = NULL;
}
ptr1 = string2;
for (i = 0; i < count_items; i++)
{
if (flags & WEECHAT_STRING_SPLIT_COLLAPSE_SEPS)
{
/* skip separators to find the beginning of item */
while (ptr1[0] && strchr (separators, ptr1[0]))
{
ptr1++;
}
}
/* search the end of item */
if (i == (count_items - 1))
{
ptr2 = strpbrk (ptr1, separators);
if (!ptr2)
ptr2 = strchr (ptr1, '\0');
}
else
{
if ((ptr2 = strpbrk (ptr1, separators)) == NULL)
{
if ((ptr2 = strchr (ptr1, '\r')) == NULL)
{
if ((ptr2 = strchr (ptr1, '\n')) == NULL)
{
ptr2 = strchr (ptr1, '\0');
}
}
}
}
if (!ptr1 || !ptr2)
{
array[i] = NULL;
}
else
{
if (ptr2 > ptr1)
{
if (flags & WEECHAT_STRING_SPLIT_KEEP_EOL)
{
array[i] = (shared) ? (char *)string_shared_get (ptr1) : strdup (ptr1);
if (!array[i])
goto error;
}
else
{
array[i] = malloc (ptr2 - ptr1 + 1);
if (!array[i])
goto error;
strncpy (array[i], ptr1, ptr2 - ptr1);
array[i][ptr2 - ptr1] = '\0';
if (shared)
{
str_shared = string_shared_get (array[i]);
if (!str_shared)
goto error;
free (array[i]);
array[i] = (char *)str_shared;
}
}
if (!(flags & WEECHAT_STRING_SPLIT_COLLAPSE_SEPS)
&& strchr (separators, ptr2[0]))
{
ptr2++;
}
ptr1 = ptr2;
}
else
{
array[i] = (shared) ? (char *)string_shared_get ("") : strdup ("");
if (ptr1[0] != '\0')
ptr1++;
}
}
}
array[i] = NULL;
if (num_items)
*num_items = i;
free (string2);
return array;
error:
for (j = 0; j < count_items; j++)
{
if (array[j])
{
if (shared)
string_shared_free (array[j]);
else
free (array[j]);
}
}
free (array);
free (string2);
return NULL;
}
/*
* Splits a string according to separators.
*
* For full description, see function string_split_internal.
*/
char **
string_split (const char *string, const char *separators, int flags,
int num_items_max, int *num_items)
{
return string_split_internal (string, separators, flags,
num_items_max, num_items, 0);
}
/*
* Splits a string according to separators, and use shared strings for the
* strings in the array returned.
*
* For full description, see function string_split_internal.
*/
char **
string_split_shared (const char *string, const char *separators, int flags,
int num_items_max, int *num_items)
{
return string_split_internal (string, separators, flags,
num_items_max, num_items, 1);
}
/*
* Splits a string like the shell does for a command with arguments.
*
* This function is a C conversion of Python class "shlex"
* (file: Lib/shlex.py in Python repository)
* Doc: https://docs.python.org/3/library/shlex.html
*
* Copyrights in shlex.py:
* Module and documentation by Eric S. Raymond, 21 Dec 1998
* Input stacking and error message cleanup added by ESR, March 2000
* push_source() and pop_source() made explicit by ESR, January 2001.
* Posix compliance, split(), string arguments, and
* iterator interface by Gustavo Niemeyer, April 2003.
*
* Note: result must be freed after use with function string_free_split().
*/
char **
string_split_shell (const char *string, int *num_items)
{
int temp_len, num_args, add_char_to_temp, add_temp_to_args, quoted;
char *string2, *temp, **args, **args2, state, escapedstate;
char *ptr_string, *ptr_next, saved_char;
if (num_items)
*num_items = 0;
if (!string)
return NULL;
string2 = strdup (string);
if (!string2)
return NULL;
/*
* prepare "args" with one pointer to NULL, the "args" will be reallocated
* later, each time a new argument is added
*/
num_args = 0;
args = malloc ((num_args + 1) * sizeof (args[0]));
if (!args)
{
free (string2);
return NULL;
}
args[0] = NULL;
/* prepare a temp string for working (adding chars one by one) */
temp = malloc ((2 * strlen (string)) + 1);
if (!temp)
{
free (string2);
free (args);
return NULL;
}
temp[0] = '\0';
temp_len = 0;
state = ' ';
escapedstate = ' ';
quoted = 0;
ptr_string = string2;
while (ptr_string[0])
{
add_char_to_temp = 0;
add_temp_to_args = 0;
ptr_next = (char *)utf8_next_char (ptr_string);
saved_char = ptr_next[0];
ptr_next[0] = '\0';
if (state == ' ')
{
if ((ptr_string[0] == ' ') || (ptr_string[0] == '\t')
|| (ptr_string[0] == '\r') || (ptr_string[0] == '\n'))
{
if (temp[0] || quoted)
add_temp_to_args = 1;
}
else if (ptr_string[0] == '\\')
{
escapedstate = 'a';
state = ptr_string[0];
}
else if ((ptr_string[0] == '\'') || (ptr_string[0] == '"'))
{
state = ptr_string[0];
}
else
{
add_char_to_temp = 1;
state = 'a';
}
}
else if ((state == '\'') || (state == '"'))
{
quoted = 1;
if (ptr_string[0] == state)
{
state = 'a';
}
else if ((state == '"') && (ptr_string[0] == '\\'))
{
escapedstate = state;
state = ptr_string[0];
}
else
{
add_char_to_temp = 1;
}
}
else if (state == '\\')
{
if (((escapedstate == '\'') || (escapedstate == '"'))
&& (ptr_string[0] != state) && (ptr_string[0] != escapedstate))
{
temp[temp_len] = state;
temp_len++;
temp[temp_len] = '\0';
}
add_char_to_temp = 1;
state = escapedstate;
}
else if (state == 'a')
{
if ((ptr_string[0] == ' ') || (ptr_string[0] == '\t')
|| (ptr_string[0] == '\r') || (ptr_string[0] == '\n'))
{
state = ' ';
if (temp[0] || quoted)
add_temp_to_args = 1;
}
else if (ptr_string[0] == '\\')
{
escapedstate = 'a';
state = ptr_string[0];
}
else if ((ptr_string[0] == '\'') || (ptr_string[0] == '"'))
{
state = ptr_string[0];
}
else
{
add_char_to_temp = 1;
}
}
if (add_char_to_temp)
{
memcpy (temp + temp_len, ptr_string, ptr_next - ptr_string);
temp_len += (ptr_next - ptr_string);
temp[temp_len] = '\0';
}
if (add_temp_to_args)
{
num_args++;
args2 = realloc (args, (num_args + 1) * sizeof (args[0]));
if (!args2)
{
free (string2);
free (temp);
return args;
}
args = args2;
args[num_args - 1] = strdup (temp);
args[num_args] = NULL;
temp[0] = '\0';
temp_len = 0;
escapedstate = ' ';
quoted = 0;
}
ptr_next[0] = saved_char;
ptr_string = ptr_next;
}
if (temp[0] || (state != ' '))
{
num_args++;
args2 = realloc (args, (num_args + 1) * sizeof (args[0]));
if (!args2)
{
free (string2);
free (temp);
return args;
}
args = args2;
args[num_args - 1] = strdup (temp);
args[num_args] = NULL;
temp[0] = '\0';
/*temp_len = 0;*/
}
free (string2);
free (temp);
if (num_items)
*num_items = num_args;
return args;
}
/*
* Frees a split string.
*/
void
string_free_split (char **split_string)
{
int i;
if (split_string)
{
for (i = 0; split_string[i]; i++)
free (split_string[i]);
free (split_string);
}
}
/*
* Frees a split string (using shared strings).
*/
void
string_free_split_shared (char **split_string)
{
int i;
if (split_string)
{
for (i = 0; split_string[i]; i++)
string_shared_free (split_string[i]);
free (split_string);
}
}
/*
* Builds a string with a split string.
*
* Note: result must be free after use.
*/
char *
string_build_with_split_string (const char **split_string,
const char *separator)
{
int i, length, length_separator;
char *result;
if (!split_string)
return NULL;
length = 0;
length_separator = (separator) ? strlen (separator) : 0;
for (i = 0; split_string[i]; i++)
{
length += strlen (split_string[i]) + length_separator;
}
result = malloc (length + 1);
if (result)
{
result[0] = '\0';
for (i = 0; split_string[i]; i++)
{
strcat (result, split_string[i]);
if (separator && split_string[i + 1])
strcat (result, separator);
}
}
return result;
}
/*
* Splits a list of commands separated by 'separator' and escaped with '\'.
* Empty commands are removed, spaces on the left of each commands are stripped.
*
* Note: result must be freed after use with function
* string_free_split_command().
*/
char **
string_split_command (const char *command, char separator)
{
int nb_substr, arr_idx, str_idx, type;
char **array, **array2;
char *buffer, *p;
const char *ptr;
if (!command || !command[0])
return NULL;
nb_substr = 1;
ptr = command;
while ((p = strchr (ptr, separator)) != NULL)
{
nb_substr++;
ptr = ++p;
}
array = malloc ((nb_substr + 1) * sizeof (array[0]));
if (!array)
return NULL;
buffer = malloc (strlen (command) + 1);
if (!buffer)
{
free (array);
return NULL;
}
ptr = command;
str_idx = 0;
arr_idx = 0;
while (*ptr != '\0')
{
type = 0;
if (*ptr == separator)
{
if (ptr == command)
type = 1;
else if ( *(ptr-1) != '\\')
type = 1;
else if ( *(ptr-1) == '\\')
type = 2;
}
if (type == 1)
{
buffer[str_idx] = '\0';
str_idx = -1;
p = buffer;
/* strip white spaces a the beginning of the line */
while (*p == ' ') p++;
if (p && p[0])
array[arr_idx++] = strdup (p);
}
else if (type == 2)
buffer[--str_idx] = *ptr;
else
buffer[str_idx] = *ptr;
str_idx++;
ptr++;
}
buffer[str_idx] = '\0';
p = buffer;
while (*p == ' ') p++;
if (p && p[0])
array[arr_idx++] = strdup (p);
array[arr_idx] = NULL;
free (buffer);
array2 = realloc (array, (arr_idx + 1) * sizeof (array[0]));
if (!array2)
{
if (array)
free (array);
return NULL;
}
return array2;
}
/*
* Frees a command split.
*/
void
string_free_split_command (char **split_command)
{
int i;
if (split_command)
{
for (i = 0; split_command[i]; i++)
free (split_command[i]);
free (split_command);
}
}
/*
* Splits tags in an array of tags.
*
* The format of tags is a list of tags separated by commas (logical OR),
* and for each item, multiple tags can be separated by "+" (logical AND).
*
* For example:
* irc_join
* irc_join,irc_quit
* irc_join+nick_toto,irc_quit
*/
char ***
string_split_tags (const char *tags, int *num_tags)
{
char ***tags_array, **tags_array_temp;
int i, tags_count;
tags_array = NULL;
tags_count = 0;
if (tags)
{
tags_array_temp = string_split (tags, ",",
WEECHAT_STRING_SPLIT_STRIP_LEFT
| WEECHAT_STRING_SPLIT_STRIP_RIGHT
| WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
0, &tags_count);
if (tags_array_temp && (tags_count > 0))
{
tags_array = malloc ((tags_count + 1) * sizeof (*tags_array));
if (tags_array)
{
for (i = 0; i < tags_count; i++)
{
tags_array[i] = string_split_shared (tags_array_temp[i],
"+", 0, 0,
NULL);
}
tags_array[tags_count] = NULL;
}
}
if (tags_array_temp)
string_free_split (tags_array_temp);
}
if (num_tags)
*num_tags = tags_count;
return tags_array;
}
/*
* Frees tags split.
*/
void
string_free_split_tags (char ***split_tags)
{
int i;
if (split_tags)
{
for (i = 0; split_tags[i]; i++)
{
string_free_split_shared (split_tags[i]);
}
free (split_tags);
}
}
/*
* Converts a string to another charset.
*
* Note: result must be freed after use.
*/
char *
string_iconv (int from_utf8, const char *from_code, const char *to_code,
const char *string)
{
char *outbuf;
#ifdef HAVE_ICONV
iconv_t cd;
char *inbuf, *ptr_outbuf;
const char *ptr_inbuf, *ptr_inbuf_shift, *next_char;
int done;
size_t err, inbytesleft, outbytesleft;
#endif /* HAVE_ICONV */
if (!string)
return NULL;
#ifdef HAVE_ICONV
if (from_code && from_code[0] && to_code && to_code[0]
&& (string_strcasecmp (from_code, to_code) != 0))
{
cd = iconv_open (to_code, from_code);
if (cd == (iconv_t)(-1))
outbuf = strdup (string);
else
{
inbuf = strdup (string);
if (!inbuf)
return NULL;
ptr_inbuf = inbuf;
inbytesleft = strlen (inbuf);
outbytesleft = inbytesleft * 4;
outbuf = malloc (outbytesleft + 2);
if (!outbuf)
return inbuf;
ptr_outbuf = outbuf;
ptr_inbuf_shift = NULL;
done = 0;
while (!done)
{
err = iconv (cd, (ICONV_CONST char **)(&ptr_inbuf), &inbytesleft,
&ptr_outbuf, &outbytesleft);
if (err == (size_t)(-1))
{
switch (errno)
{
case EINVAL:
done = 1;
break;
case E2BIG:
done = 1;
break;
case EILSEQ:
if (from_utf8)
{
next_char = utf8_next_char (ptr_inbuf);
if (next_char)
{
inbytesleft -= next_char - ptr_inbuf;
ptr_inbuf = next_char;
}
else
{
inbytesleft--;
ptr_inbuf++;
}
}
else
{
ptr_inbuf++;
inbytesleft--;
}
ptr_outbuf[0] = '?';
ptr_outbuf++;
outbytesleft--;
break;
}
}
else
{
if (!ptr_inbuf_shift)
{
ptr_inbuf_shift = ptr_inbuf;
ptr_inbuf = NULL;
inbytesleft = 0;
}
else
done = 1;
}
}
if (ptr_inbuf_shift)
ptr_inbuf = ptr_inbuf_shift;
ptr_outbuf[0] = '\0';
free (inbuf);
iconv_close (cd);
}
}
else
outbuf = strdup (string);
#else
/* make C compiler happy */
(void) from_utf8;
(void) from_code;
(void) to_code;
outbuf = strdup (string);
#endif /* HAVE_ICONV */
return outbuf;
}
/*
* Converts a string to WeeChat internal storage charset (UTF-8).
*
* Note: result must be freed after use.
*/
char *
string_iconv_to_internal (const char *charset, const char *string)
{
char *input, *output;
if (!string)
return NULL;
input = strdup (string);
if (!input)
return NULL;
/*
* optimized for UTF-8: if charset is NULL => we use term charset => if
* this charset is already UTF-8, then no iconv is needed
*/
if (local_utf8 && (!charset || !charset[0]))
return input;
if (utf8_has_8bits (input) && utf8_is_valid (input, -1, NULL))
return input;
output = string_iconv (0,
(charset && charset[0]) ?
charset : weechat_local_charset,
WEECHAT_INTERNAL_CHARSET,
input);
if (!output)
return input;
utf8_normalize (output, '?');
free (input);
return output;
}
/*
* Converts internal string to terminal charset, for display.
*
* Note: result must be freed after use.
*/
char *
string_iconv_from_internal (const char *charset, const char *string)
{
char *input, *output;
if (!string)
return NULL;
input = strdup (string);
if (!input)
return NULL;
/*
* optimized for UTF-8: if charset is NULL => we use term charset => if
* this charset is already UTF-8, then no iconv is needed
*/
if (local_utf8 && (!charset || !charset[0]))
return input;
utf8_normalize (input, '?');
output = string_iconv (1,
WEECHAT_INTERNAL_CHARSET,
(charset && charset[0]) ?
charset : weechat_local_charset,
input);
if (!output)
return input;
free (input);
return output;
}
/*
* Encodes a string to terminal charset and calls fprintf.
*
* Returns:
* 1: OK
* 0: error
*/
int
string_fprintf (FILE *file, const char *data, ...)
{
char *buf2;
int rc, num_written;
rc = 0;
if (!data)
return rc;
weechat_va_format (data);
if (vbuffer)
{
buf2 = string_iconv_from_internal (NULL, vbuffer);
num_written = fprintf (file, "%s", (buf2) ? buf2 : vbuffer);
rc = (num_written == (int)strlen ((buf2) ? buf2 : vbuffer)) ? 1 : 0;
if (buf2)
free (buf2);
free (vbuffer);
}
return rc;
}
/*
* Formats a string with size and unit name (bytes, KB, MB, GB).
*
* Note: result must be freed after use.
*/
char *
string_format_size (unsigned long long size)
{
char *unit_name[] = { "",
/* TRANSLATORS: file size unit "kilobyte" */
N_("KB"),
/* TRANSLATORS: file size unit "megabyte" */
N_("MB"),
/* TRANSLATORS: file size unit "gigabyte" */
N_("GB"),
/* TRANSLATORS: file size unit "terabyte" */
N_("TB") };
char *unit_format[] = { "%.0f", "%.1f", "%.02f", "%.02f", "%.02f" };
float unit_divide[] = { 1.0,
1000.0,
1000.0 * 1000.0,
1000.0 * 1000.0 * 1000.0,
1000.0 * 1000.0 * 1000.0 * 1000.0 };
char format_size[128], str_size[128];
int num_unit;
float size_float;
str_size[0] = '\0';
if (size < 10ULL * 1000ULL)
num_unit = 0;
else if (size < 1000ULL * 1000ULL)
num_unit = 1;
else if (size < 1000ULL * 1000ULL * 1000ULL)
num_unit = 2;
else if (size < 1000ULL * 1000ULL * 1000ULL * 1000ULL)
num_unit = 3;
else
num_unit = 4;
snprintf (format_size, sizeof (format_size),
"%s %%s",
unit_format[num_unit]);
size_float = ((float)size) / ((float)(unit_divide[num_unit]));
snprintf (str_size, sizeof (str_size),
format_size,
size_float,
(num_unit == 0) ?
NG_("byte", "bytes", size_float) : _(unit_name[num_unit]));
return strdup (str_size);
}
/*
* Encodes a string in base16 (hexadecimal).
*
* Argument "length" is number of bytes in "from" to convert (commonly
* strlen(from)).
*
* Returns length of string in "*to" (it does not count final \0).
*/
int
string_base16_encode (const char *from, int length, char *to)
{
int i, count;
const char *hexa = "0123456789ABCDEF";
if (!from || !to)
return -1;
count = 0;
for (i = 0; i < length; i++)
{
to[count++] = hexa[((unsigned char)from[i]) / 16];
to[count++] = hexa[((unsigned char)from[i]) % 16];
}
to[count] = '\0';
return count;
}
/*
* Decodes a base16 string (hexadecimal).
*
* Returns length of string in "*to" (it does not count final \0).
*/
int
string_base16_decode (const char *from, char *to)
{
int length, i, pos, count;
unsigned char value;
if (!from || !to)
return 0;
count = 0;
length = strlen (from) / 2;
for (i = 0; i < length; i++)
{
pos = i * 2;
value = 0;
/* 4 bits on the left */
if ((from[pos] >= '0') && (from[pos] <= '9'))
value |= (from[pos] - '0') << 4;
else if ((from[pos] >= 'a') && (from[pos] <= 'f'))
value |= (from[pos] - 'a' + 10) << 4;
else if ((from[pos] >= 'A') && (from[pos] <= 'F'))
value |= (from[pos] - 'A' + 10) << 4;
/* 4 bits on the right */
pos++;
if ((from[pos] >= '0') && (from[pos] <= '9'))
value |= from[pos] - '0';
else if ((from[pos] >= 'a') && (from[pos] <= 'f'))
value |= from[pos] - 'a' + 10;
else if ((from[pos] >= 'A') && (from[pos] <= 'F'))
value |= from[pos] - 'A' + 10;
to[count++] = value;
}
to[count] = '\0';
return count;
}
/*
* Encodes a string in base32.
*
* Argument "length" is number of bytes in "from" to convert (commonly
* strlen(from)).
*
* This function is inspired by:
* https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c
*
* Original copyright:
*
* Copyright 2010 Google Inc.
* Author: Markus Gutschke
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Returns length of string in "*to" (it does not count final \0).
*/
int
string_base32_encode (const char *from, int length, char *to)
{
unsigned char base32_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
int count, value, next, bits_left, pad, index;
int length_padding[8] = { 0, 0, 6, 0, 4, 3, 0, 2 };
if (!from || !to)
return -1;
count = 0;
if (length > 0)
{
value = from[0];
next = 1;
bits_left = 8;
while ((bits_left > 0) || (next < length))
{
if (bits_left < 5)
{
if (next < length)
{
value <<= 8;
value |= from[next++] & 0xFF;
bits_left += 8;
}
else
{
pad = 5 - bits_left;
value <<= pad;
bits_left += pad;
}
}
index = 0x1F & (value >> (bits_left - 5));
bits_left -= 5;
to[count++] = base32_table[index];
}
}
pad = length_padding[count % 8];
while (pad > 0)
{
to[count++] = '=';
pad--;
}
to[count] = '\0';
return count;
}
/*
* Decodes a base32 string.
*
* This function is inspired by:
* https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c
*
* Original copyright:
*
* Copyright 2010 Google Inc.
* Author: Markus Gutschke
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* Returns length of string in "*to" (it does not count final \0).
*/
int
string_base32_decode (const char *from, char *to)
{
const char *ptr_from;
int value, bits_left, count;
unsigned char c;
if (!from || !to)
return -1;
ptr_from = from;
value = 0;
bits_left = 0;
count = 0;
while (ptr_from[0])
{
c = (unsigned char)ptr_from[0];
value <<= 5;
if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')))
{
c = (c & 0x1F) - 1;
}
else if ((c >= '2') && (c <= '7'))
{
c -= '2' - 26;
}
else if (c == '=')
{
/* padding */
break;
}
else
{
/* invalid base32 char */
return -1;
}
value |= c;
bits_left += 5;
if (bits_left >= 8)
{
to[count++] = value >> (bits_left - 8);
bits_left -= 8;
}
ptr_from++;
}
to[count] = '\0';
return count;
}
/*
* Converts 3 bytes of 8 bits in 4 bytes of 6 bits.
*/
void
string_convbase64_8x3_to_6x4 (const char *from, char *to)
{
unsigned char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789+/";
to[0] = base64_table [ (from[0] & 0xfc) >> 2 ];
to[1] = base64_table [ ((from[0] & 0x03) << 4) + ((from[1] & 0xf0) >> 4) ];
to[2] = base64_table [ ((from[1] & 0x0f) << 2) + ((from[2] & 0xc0) >> 6) ];
to[3] = base64_table [ from[2] & 0x3f ];
}
/*
* Encodes a string in base64.
*
* Argument "length" is number of bytes in "from" to convert (commonly
* strlen(from)).
*
* Returns length of string in "*to" (it does not count final \0).
*/
int
string_base64_encode (const char *from, int length, char *to)
{
const char *ptr_from;
char rest[3];
int count;
if (!from || !to)
return -1;
ptr_from = from;
count = 0;
while (length >= 3)
{
string_convbase64_8x3_to_6x4 (ptr_from, to + count);
ptr_from += 3;
count += 4;
length -= 3;
}
if (length > 0)
{
rest[0] = 0;
rest[1] = 0;
rest[2] = 0;
switch (length)
{
case 1 :
rest[0] = ptr_from[0];
string_convbase64_8x3_to_6x4 (rest, to + count);
count += 2;
to[count] = '=';
count++;
to[count] = '=';
break;
case 2 :
rest[0] = ptr_from[0];
rest[1] = ptr_from[1];
string_convbase64_8x3_to_6x4 (rest, to + count);
count += 3;
to[count] = '=';
break;
}
count++;
to[count] = '\0';
}
else
to[count] = '\0';
return count;
}
/*
* Converts 4 bytes of 6 bits to 3 bytes of 8 bits.
*/
void
string_convbase64_6x4_to_8x3 (const unsigned char *from, unsigned char *to)
{
to[0] = from[0] << 2 | from[1] >> 4;
to[1] = from[1] << 4 | from[2] >> 2;
to[2] = from[2] << 6 | from[3];
}
/*
* Decodes a base64 string.
*
* Returns length of string in "*to" (it does not count final \0).
*/
int
string_base64_decode (const char *from, char *to)
{
const char *ptr_from;
int length, to_length, i;
char *ptr_to;
unsigned char c, in[4], out[3];
unsigned char base64_table[]="|$$$}rstuvwxyz{$$$$$$$>?"
"@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq";
if (!from || !to)
return 0;
ptr_from = from;
ptr_to = to;
ptr_to[0] = '\0';
to_length = 0;
while (ptr_from && ptr_from[0])
{
length = 0;
in[0] = 0;
in[1] = 0;
in[2] = 0;
in[3] = 0;
for (i = 0; i < 4; i++)
{
if (!ptr_from[0])
break;
c = (unsigned char) ptr_from[0];
ptr_from++;
c = ((c < 43) || (c > 122)) ? 0 : base64_table[c - 43];
if (c)
c = (c == '$') ? 0 : c - 61;
if (c)
{
length++;
in[i] = c - 1;
}
else
break;
}
if (length > 0)
{
string_convbase64_6x4_to_8x3 (in, out);
for (i = 0; i < length - 1; i++)
{
ptr_to[0] = out[i];
ptr_to++;
to_length++;
}
}
}
ptr_to[0] = '\0';
return to_length;
}
/*
* Dumps a data buffer as hexadecimal + ascii.
*
* Note: result must be freed after use.
*/
char *
string_hex_dump (const char *data, int data_size, int bytes_per_line,
const char *prefix, const char *suffix)
{
char *buf, *str_hexa, *str_ascii, str_format_line[64], *str_line;
int length_hexa, length_ascii, length_prefix, length_suffix, length_line;
int hexa_pos, ascii_pos, i;
if (!data || (data_size < 1) || (bytes_per_line < 1))
return NULL;
str_hexa = NULL;
str_ascii = NULL;
str_line = NULL;
buf = NULL;
length_hexa = bytes_per_line * 3;
str_hexa = malloc (length_hexa + 1);
if (!str_hexa)
goto end;
length_ascii = bytes_per_line * 2;
str_ascii = malloc (length_ascii + 1);
if (!str_ascii)
goto end;
length_prefix = (prefix) ? strlen (prefix) : 0;
length_suffix = (suffix) ? strlen (suffix) : 0;
length_line = length_prefix + (bytes_per_line * 3) + 2 + length_ascii +
length_suffix;
str_line = malloc (length_line + 1);
if (!str_line)
goto end;
buf = malloc ((((data_size / bytes_per_line) + 1) * (length_line + 1)) + 1);
if (!buf)
goto end;
buf[0] = '\0';
snprintf (str_format_line, sizeof (str_format_line),
"%%s%%-%ds %%-%ds%%s",
length_hexa,
length_ascii);
hexa_pos = 0;
ascii_pos = 0;
for (i = 0; i < data_size; i++)
{
snprintf (str_hexa + hexa_pos, 4,
"%02X ", (unsigned char)(data[i]));
hexa_pos += 3;
snprintf (str_ascii + ascii_pos, 3, "%c ",
((((unsigned char)data[i]) < 32)
|| (((unsigned char)data[i]) > 127)) ?
'.' : (unsigned char)(data[i]));
ascii_pos += 2;
if (ascii_pos == bytes_per_line * 2)
{
if (buf[0])
strcat (buf, "\n");
str_ascii[ascii_pos - 1] = '\0';
snprintf (str_line, length_line + 1,
str_format_line,
(prefix) ? prefix : "",
str_hexa,
str_ascii,
(suffix) ? suffix : "");
strcat (buf, str_line);
hexa_pos = 0;
ascii_pos = 0;
}
}
if (ascii_pos > 0)
{
if (buf[0])
strcat (buf, "\n");
str_ascii[ascii_pos - 1] = '\0';
str_ascii[ascii_pos] = '\0';
snprintf (str_line, length_line + 1,
str_format_line,
(prefix) ? prefix : "",
str_hexa,
str_ascii,
(suffix) ? suffix : "");
strcat (buf, str_line);
}
end:
if (str_hexa)
free (str_hexa);
if (str_ascii)
free (str_ascii);
if (str_line)
free (str_line);
return buf;
}
/*
* Checks if a string is a command.
*
* Returns:
* 1: first char of string is a command char
* 0: string is not a command
*/
int
string_is_command_char (const char *string)
{
const char *ptr_command_chars;
if (!string)
return 0;
if (string[0] == '/')
return 1;
ptr_command_chars = CONFIG_STRING(config_look_command_chars);
if (!ptr_command_chars || !ptr_command_chars[0])
return 0;
while (ptr_command_chars && ptr_command_chars[0])
{
if (utf8_charcmp (ptr_command_chars, string) == 0)
return 1;
ptr_command_chars = utf8_next_char (ptr_command_chars);
}
return 0;
}
/*
* Gets pointer to input text for buffer.
*
* Returns pointer inside "string" argument or NULL if it's a command (by
* default a command starts with a single '/').
*/
const char *
string_input_for_buffer (const char *string)
{
char *pos_slash, *pos_space;
const char *next_char;
if (!string)
return NULL;
/* a single "/" is not a command */
if (strcmp (string, "/") == 0)
return string;
/* "/ " is not a command */
if (strncmp (string, "/ ", 2) == 0)
return string;
/* special case for C comments pasted in input line */
if (strncmp (string, "/*", 2) == 0)
return string;
/*
* special case if string starts with '/': to allow to paste a path line
* "/path/to/file.txt", we check if next '/' is after a space or not
*/
if (string[0] == '/')
{
pos_slash = strchr (string + 1, '/');
pos_space = strchr (string + 1, ' ');
/*
* if there are no other '/' or if '/' is after first space,
* then it is a command, and return NULL
*/
if (!pos_slash || (pos_space && pos_slash > pos_space))
return NULL;
return (string[1] == '/') ? string + 1 : string;
}
/* if string does not start with a command char, then it's not command */
if (!string_is_command_char (string))
return string;
next_char = utf8_next_char (string);
/* there's no next char, then it's a not command */
if (!next_char || !next_char[0])
return string;
/* next char is a space, then it's not a command */
if (next_char[0] == ' ')
return string;
/* check if first char is doubled: if yes, then it's not a command */
if (utf8_charcmp (string, next_char) == 0)
return next_char;
/* string is a command */
return NULL;
}
/*
* Replaces ${vars} using a callback that returns replacement value (this value
* must be newly allocated because it will be freed in this function).
*
* Nested variables are supported, for example: "${var1:${var2}}".
*
* Argument "list_prefix_no_replace" is a list to prevent replacements in
* string if beginning with one of the prefixes. For example if the list is
* { "if:", NULL } and string is: "${if:cond?true:false}${test${abc}}"
* then the "if:cond?true:false" is NOT replaced (via a recursive call) but
* "test${abc}" will be replaced.
*
* Argument "errors" (if not NULL) is set with number of keys not found by
* callback.
*
* Note: result must be freed after use.
*/
char *
string_replace_with_callback (const char *string,
const char *prefix,
const char *suffix,
const char **list_prefix_no_replace,
char *(*callback)(void *data, const char *text),
void *callback_data,
int *errors)
{
int length_prefix, length_suffix, length, length_value, index_string;
int index_result, sub_count, sub_level, sub_errors, replace, i;
char *result, *result2, *key, *key2, *value;
const char *pos_end_name;
if (errors)
*errors = 0;
if (!string || !prefix || !prefix[0] || !suffix || !suffix[0] || !callback)
return NULL;
length_prefix = strlen (prefix);
length_suffix = strlen (suffix);
length = strlen (string) + 1;
result = malloc (length);
if (result)
{
index_string = 0;
index_result = 0;
while (string[index_string])
{
if ((string[index_string] == '\\')
&& (string[index_string + 1] == prefix[0]))
{
index_string++;
result[index_result++] = string[index_string++];
}
else if (strncmp (string + index_string, prefix, length_prefix) == 0)
{
sub_count = 0;
sub_level = 0;
pos_end_name = string + index_string + length_prefix;
while (pos_end_name[0])
{
if (strncmp (pos_end_name, suffix, length_suffix) == 0)
{
if (sub_level == 0)
break;
sub_level--;
}
if ((pos_end_name[0] == '\\')
&& (pos_end_name[1] == prefix[0]))
{
pos_end_name++;
}
else if (strncmp (pos_end_name, prefix, length_prefix) == 0)
{
sub_count++;
sub_level++;
}
pos_end_name++;
}
/* prefix without matching suffix => error! */
if (!pos_end_name[0])
{
result[index_result] = '\0';
if (errors)
(*errors)++;
return result;
}
key = string_strndup (string + index_string + length_prefix,
pos_end_name - (string + index_string + length_prefix));
if (key)
{
if (sub_count > 0)
{
replace = 1;
if (list_prefix_no_replace)
{
for (i = 0; list_prefix_no_replace[i]; i++)
{
if (strncmp (
key, list_prefix_no_replace[i],
strlen (list_prefix_no_replace[i])) == 0)
{
replace = 0;
break;
}
}
}
if (replace)
{
sub_errors = 0;
key2 = string_replace_with_callback (
key,
prefix,
suffix,
list_prefix_no_replace,
callback,
callback_data,
&sub_errors);
if (errors)
(*errors) += sub_errors;
free (key);
key = key2;
}
}
value = (*callback) (callback_data, (key) ? key : "");
if (value)
{
length_value = strlen (value);
if (length_value > 0)
{
length += length_value;
result2 = realloc (result, length);
if (!result2)
{
if (result)
free (result);
if (key)
free (key);
free (value);
return NULL;
}
result = result2;
strcpy (result + index_result, value);
index_result += length_value;
}
index_string = pos_end_name - string + length_suffix;
free (value);
}
else
{
result[index_result++] = string[index_string++];
if (errors)
(*errors)++;
}
if (key)
free (key);
}
else
result[index_result++] = string[index_string++];
}
else
result[index_result++] = string[index_string++];
}
result[index_result] = '\0';
}
return result;
}
/*
* Hashes a shared string.
* The string starts after the reference count, which is skipped.
*
* Returns the hash of the shared string (variant of djb2).
*/
unsigned long long
string_shared_hash_key (struct t_hashtable *hashtable,
const void *key)
{
/* make C compiler happy */
(void) hashtable;
return hashtable_hash_key_djb2 (((const char *)key) + sizeof (string_shared_count_t));
}
/*
* Compares two shared strings.
* Each string starts after the reference count, which is skipped.
*
* Returns:
* < 0: key1 < key2
* 0: key1 == key2
* > 0: key1 > key2
*/
int
string_shared_keycmp (struct t_hashtable *hashtable,
const void *key1, const void *key2)
{
/* make C compiler happy */
(void) hashtable;
return strcmp (((const char *)key1) + sizeof (string_shared_count_t),
((const char *)key2) + sizeof (string_shared_count_t));
}
/*
* Frees a shared string.
*/
void
string_shared_free_key (struct t_hashtable *hashtable, void *key)
{
/* make C compiler happy */
(void) hashtable;
free (key);
}
/*
* Gets a pointer to a shared string.
*
* A shared string is an entry in the hashtable "string_hashtable_shared", with:
* - key: reference count (unsigned integer on 32 bits) + string
* - value: NULL pointer (not used)
*
* The initial reference count is set to 1 and is incremented each time this
* function is called for a same string (string content, not the pointer).
*
* Returns the pointer to the shared string (start of string in key, after the
* reference count), NULL if error.
* The string returned has exactly same content as string received in argument,
* but the pointer to the string is different.
*
* IMPORTANT: the returned string must NEVER be changed in any way, because it
* is used itself as the key of the hashtable.
*/
const char *
string_shared_get (const char *string)
{
struct t_hashtable_item *ptr_item;
char *key;
int length;
if (!string_hashtable_shared)
{
/*
* use large htable inside hashtable to prevent too many collisions,
* which would slow down search of a string in the hashtable
*/
string_hashtable_shared = hashtable_new (1024,
WEECHAT_HASHTABLE_POINTER,
WEECHAT_HASHTABLE_POINTER,
&string_shared_hash_key,
&string_shared_keycmp);
if (!string_hashtable_shared)
return NULL;
string_hashtable_shared->callback_free_key = &string_shared_free_key;
}
length = sizeof (string_shared_count_t) + strlen (string) + 1;
key = malloc (length);
if (!key)
return NULL;
*((string_shared_count_t *)key) = 1;
strcpy (key + sizeof (string_shared_count_t), string);
ptr_item = hashtable_get_item (string_hashtable_shared, key, NULL);
if (ptr_item)
{
/*
* the string already exists in the hashtable, then just increase the
* reference count on the string
*/
(*((string_shared_count_t *)(ptr_item->key)))++;
free (key);
}
else
{
/* add the shared string in the hashtable */
ptr_item = hashtable_set (string_hashtable_shared, key, NULL);
if (!ptr_item)
free (key);
}
return (ptr_item) ?
((const char *)ptr_item->key) + sizeof (string_shared_count_t) : NULL;
}
/*
* Frees a shared string.
*
* The reference count of the string is decremented. If it becomes 0, then the
* shared string is removed from the hashtable (and then the string is really
* destroyed).
*/
void
string_shared_free (const char *string)
{
string_shared_count_t *ptr_count;
if (!string)
return;
ptr_count = (string_shared_count_t *)(string - sizeof (string_shared_count_t));
(*ptr_count)--;
if (*ptr_count == 0)
hashtable_remove (string_hashtable_shared, ptr_count);
}
/*
* Allocates a dynamic string (with a variable length).
*
* The parameter size_alloc is the initial allocated size, which must be
* greater than zero.
*
* Returns the pointer to the allocated string, which is initialized as empty
* string.
*
* The string returned can be used with following restrictions:
* - changes are allowed in the string, between the first char and the final
* '\0', which must not be removed nor moved,
* - no other '\0' must be added in the string,
* - content can be added in the string with function string_dyn_concat(),
* - string can be freed with function string_dyn_free() (do NEVER call
* directly free() on the string).
*
* Note: result must be freed after use with function string_dyn_free().
*/
char **
string_dyn_alloc (int size_alloc)
{
struct t_string_dyn *string_dyn;
if (size_alloc <= 0)
return NULL;
string_dyn = malloc (sizeof (*string_dyn));
if (!string_dyn)
return NULL;
string_dyn->string = malloc (size_alloc);
if (!string_dyn->string)
{
free (string_dyn);
return NULL;
}
string_dyn->string[0] = '\0';
string_dyn->size_alloc = size_alloc;
string_dyn->size = 1;
return &(string_dyn->string);
}
/*
* Copies "new_string" into a dynamic string and replaces its current content
* (adjusts its size accordingly).
*
* The string pointer (*string) is updated with the new allocated string
* if the string had to be extended, or the same pointer if there was enough
* size to copy the new string.
*
* Returns:
* 1: OK
* 0: error
*/
int
string_dyn_copy (char **string, const char *new_string)
{
struct t_string_dyn *ptr_string_dyn;
char *string_realloc;
string_dyn_size_t length_new, new_size_alloc;
if (!string || !*string)
return 0;
ptr_string_dyn = (struct t_string_dyn *)string;
length_new = (new_string) ? strlen (new_string) : 0;
if (length_new + 1 > ptr_string_dyn->size_alloc)
{
/* compute new size_alloc for the string + add */
new_size_alloc = (ptr_string_dyn->size_alloc < 2) ?
2 : ptr_string_dyn->size_alloc + (ptr_string_dyn->size_alloc / 2);
if (new_size_alloc < length_new + 1)
new_size_alloc = length_new + 1;
string_realloc = realloc (ptr_string_dyn->string, new_size_alloc);
if (!string_realloc)
return 0;
ptr_string_dyn->string = string_realloc;
*string = string_realloc;
ptr_string_dyn->size_alloc = new_size_alloc;
}
/* copy "new_string" in "string" */
if (new_string)
memmove (ptr_string_dyn->string, new_string, length_new + 1);
else
ptr_string_dyn->string[0] = '\0';
ptr_string_dyn->size = length_new + 1;
return 1;
}
/*
* Concatenates a string to a dynamic string and adjusts its size accordingly.
*
* The string pointer (*string) is updated with the new allocated string
* if the string had to be extended, or the same pointer if there was enough
* size to concatenate the new string.
*
* Returns:
* 1: OK
* 0: error
*/
int
string_dyn_concat (char **string, const char *add)
{
struct t_string_dyn *ptr_string_dyn;
char *string_realloc;
string_dyn_size_t length_add, new_size_alloc, new_size;
if (!string || !*string)
return 0;
if (!add || !add[0])
return 1;
ptr_string_dyn = (struct t_string_dyn *)string;
length_add = strlen (add);
new_size = ptr_string_dyn->size + length_add;
if (new_size > ptr_string_dyn->size_alloc)
{
/* compute new size_alloc for the string + add */
new_size_alloc = (ptr_string_dyn->size_alloc < 2) ?
2 : ptr_string_dyn->size_alloc + (ptr_string_dyn->size_alloc / 2);
if (new_size_alloc < new_size)
new_size_alloc = new_size;
string_realloc = realloc (ptr_string_dyn->string, new_size_alloc);
if (!string_realloc)
{
free (ptr_string_dyn->string);
free (ptr_string_dyn);
return 0;
}
ptr_string_dyn->string = string_realloc;
*string = string_realloc;
ptr_string_dyn->size_alloc = new_size_alloc;
}
/* concatenate "add" after "string" */
memmove (ptr_string_dyn->string + ptr_string_dyn->size - 1,
add,
length_add + 1);
ptr_string_dyn->size = new_size;
return 1;
}
/*
* Frees a dynamic string.
*
* The argument "string" is a pointer on a string returned by function
* string_dyn_alloc or a string pointer modified by string_dyn_concat.
*
* If free_string == 1, the string itself is freed in the structure.
* Otherwise the pointer (*string) remains valid after this call, and
* the caller must manually free the string with a call to free().
*
* Returns the pointer to the string if "free_string" is 0 (string
* pointer is still valid), or NULL if "free_string" is 1 (string
* has been freed).
*/
char *
string_dyn_free (char **string, int free_string)
{
struct t_string_dyn *ptr_string_dyn;
char *ptr_string;
if (!string || !*string)
return NULL;
ptr_string_dyn = (struct t_string_dyn *)string;
if (free_string)
{
free (ptr_string_dyn->string);
ptr_string = NULL;
}
else
{
ptr_string = ptr_string_dyn->string;
}
free (ptr_string_dyn);
return ptr_string;
}
/*
* Frees all allocated data.
*/
void
string_end ()
{
if (string_hashtable_shared)
{
hashtable_free (string_hashtable_shared);
string_hashtable_shared = NULL;
}
}