view src/lib/strfuncs.c @ 8:2d8711a043a0 HEAD

added some extra asserts
author Timo Sirainen <tss@iki.fi>
date Thu, 22 Aug 2002 01:45:34 +0300
parents 1b34ec11fff8
children d493b9cc265e
line wrap: on
line source

/*
 strfuncs.c : String manipulation functions (note: LGPL, because the )

    Copyright (C) 2001-2002 Timo Sirainen

    printf_string_upper_bound() code is taken from GLIB:
    Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
    Modified by the GLib Team and others 1997-1999.


    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
  
    This library 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
    Library General Public License for more details.
  
    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the
    Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#include "lib.h"
#include "strfuncs.h"

#include <stdio.h>
#include <limits.h>
#include <ctype.h>

#define STRCONCAT_BUFSIZE 512

typedef void *(*ALLOC_FUNC)(Pool, unsigned int);

static void *tp_malloc(Pool pool __attr_unused__, unsigned int size)
{
        return t_malloc(size);
}

typedef union  _GDoubleIEEE754  GDoubleIEEE754;
#define G_IEEE754_DOUBLE_BIAS   (1023)
/* multiply with base2 exponent to get base10 exponent (nomal numbers) */
#define G_LOG_2_BASE_10         (0.30102999566398119521)
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
union _GDoubleIEEE754
{
  double v_double;
  struct {
    unsigned int mantissa_low : 32;
    unsigned int mantissa_high : 20;
    unsigned int biased_exponent : 11;
    unsigned int sign : 1;
  } mpn;
};
#elif G_BYTE_ORDER == G_BIG_ENDIAN
union _GDoubleIEEE754
{
  double v_double;
  struct {
    unsigned int sign : 1;
    unsigned int biased_exponent : 11;
    unsigned int mantissa_high : 20;
    unsigned int mantissa_low : 32;
  } mpn;
};
#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
#error unknown ENDIAN type
#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */

typedef struct
{
  unsigned int min_width;
  unsigned int precision;
  int alternate_format, zero_padding, adjust_left, locale_grouping;
  int add_space, add_sign, possible_sign, seen_precision;
  int mod_half, mod_long, mod_extra_long;
} PrintfArgSpec;

#if (SIZEOF_LONG > 4) || (SIZEOF_VOID_P > 4)
#  define HONOUR_LONGS 1
#else
#  define HONOUR_LONGS 0
#endif

unsigned int printf_string_upper_bound(const char *format, va_list args)
{
  int len = 1;

  if (!format)
    return len;

  while (*format)
    {
      register char c = *format++;

      if (c != '%')
        len += 1;
      else /* (c == '%') */
        {
          PrintfArgSpec spec;
          int seen_l = FALSE, conv_done = FALSE;
          unsigned int conv_len = 0;
          const char *spec_start = format;

          memset(&spec, 0, sizeof(spec));
          do
            {
              c = *format++;
              switch (c)
                {
                  GDoubleIEEE754 u_double;
                  unsigned int v_uint;
                  int v_int;
                  const char *v_string;

                  /* beware of positional parameters
                   */
                case '$':
                  i_warning (GNUC_PRETTY_FUNCTION
                             "(): unable to handle positional parameters (%%n$)");
                  len += 1024; /* try adding some safety padding */
                  break;

                  /* parse flags
                   */
                case '#':
                  spec.alternate_format = TRUE;
                  break;
                case '0':
                  spec.zero_padding = TRUE;
                  break;
                case '-':
                  spec.adjust_left = TRUE;
                  break;
                case ' ':
                  spec.add_space = TRUE;
                  break;
                case '+':
                  spec.add_sign = TRUE;
                  break;
                case '\'':
                  spec.locale_grouping = TRUE;
                  break;

                  /* parse output size specifications
                   */
                case '.':
                  spec.seen_precision = TRUE;
                  break;
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                  v_uint = c - '0';
                  c = *format;
                  while (c >= '0' && c <= '9')
                    {
                      format++;
                      v_uint = v_uint * 10 + c - '0';
                      c = *format;
                    }
                  if (spec.seen_precision)
                    spec.precision = I_MAX (spec.precision, v_uint);
                  else
                    spec.min_width = I_MAX (spec.min_width, v_uint);
                  break;
                case '*':
                  v_int = va_arg (args, int);
                  if (spec.seen_precision)
                    {
                      /* forget about negative precision */
                      if (v_int >= 0)
                        spec.precision = I_MAX ((int)spec.precision, v_int);
                    }
                  else
                    {
                      if (v_int < 0)
                        {
                          v_int = - v_int;
                          spec.adjust_left = TRUE;
                        }
                      spec.min_width = I_MAX ((int)spec.min_width, v_int);
                    }
                  break;

                  /* parse type modifiers
                   */
                case 'h':
                  spec.mod_half = TRUE;
                  break;
                case 'l':
                  if (!seen_l)
                    {
                      spec.mod_long = TRUE;
                      seen_l = TRUE;
                      break;
                    }
                  /* else, fall through */
                case 'L':
                case 'q':
                  spec.mod_long = TRUE;
                  spec.mod_extra_long = TRUE;
                  break;
                case 'z':
                case 'Z':
#if GLIB_SIZEOF_SIZE_T > 4
                  spec.mod_long = TRUE;
                  spec.mod_extra_long = TRUE;
#endif /* GLIB_SIZEOF_SIZE_T > 4 */
                  break;
                case 't':
#if GLIB_SIZEOF_PTRDIFF_T > 4
                  spec.mod_long = TRUE;
                  spec.mod_extra_long = TRUE;
#endif /* GLIB_SIZEOF_PTRDIFF_T > 4 */
                  break;
                case 'j':
#if GLIB_SIZEOF_INTMAX_T > 4
                  spec.mod_long = TRUE;
                  spec.mod_extra_long = TRUE;
#endif /* GLIB_SIZEOF_INTMAX_T > 4 */
                  break;

                  /* parse output conversions
                   */
                case '%':
                  conv_len += 1;
                  break;
                case 'O':
                case 'D':
                case 'I':
                case 'U':
                  /* some C libraries feature long variants for these as well? */
                  spec.mod_long = TRUE;
                  /* fall through */
                case 'o':
                  conv_len += 2;
                  /* fall through */
                case 'd':
                case 'i':
                  conv_len += 1; /* sign */
                  /* fall through */
                case 'u':
                  conv_len += 4;
                  /* fall through */
                case 'x':
                case 'X':
                  spec.possible_sign = TRUE;
                  conv_len += 10;
                  if (spec.mod_long && HONOUR_LONGS)
                    conv_len *= 2;
                  if (spec.mod_extra_long)
                    conv_len *= 2;
                  if (spec.mod_extra_long)
                    {
#ifdef G_HAVE_GINT64
                      (void) va_arg (args, gint64);
#else
                      (void) va_arg (args, long);
#endif
                    }
                  else if (spec.mod_long)
                    (void) va_arg (args, long);
                  else
                    (void) va_arg (args, int);
                  break;
                case 'A':
                case 'a':
                  /*          0x */
                  conv_len += 2;
                  /* fall through */
                case 'g':
                case 'G':
                case 'e':
                case 'E':
                case 'f':
                  spec.possible_sign = TRUE;
                  /*          n   .   dddddddddddddddddddddddd   E   +-  eeee */
                  conv_len += 1 + 1 + I_MAX (24, spec.precision) + 1 + 1 + 4;
                  if (spec.mod_extra_long)
                    i_warning (GNUC_PRETTY_FUNCTION
                               "(): unable to handle long double, collecting double only");
#ifdef HAVE_LONG_DOUBLE
#error need to implement special handling for long double
#endif
                  u_double.v_double = va_arg (args, double);
                  /* %f can expand up to all significant digits before '.' (308) */
                  if (c == 'f' &&
                      u_double.mpn.biased_exponent > 0 && u_double.mpn.biased_exponent < 2047)
                    {
                      int exp = u_double.mpn.biased_exponent;

                      exp -= G_IEEE754_DOUBLE_BIAS;
                      exp = exp * G_LOG_2_BASE_10 + 1;
                      conv_len += exp;
                    }
                  /* some printf() implementations require extra padding for rounding */
                  conv_len += 2;
                  /* we can't really handle locale specific grouping here */
                  if (spec.locale_grouping)
                    conv_len *= 2;
                  break;
                case 'C':
                  spec.mod_long = TRUE;
                  /* fall through */
                case 'c':
                  conv_len += spec.mod_long ? MB_LEN_MAX : 1;
                  (void) va_arg (args, int);
                  break;
                case 'S':
                  spec.mod_long = TRUE;
                  /* fall through */
                case 's':
                  v_string = va_arg (args, char*);
                  if (!v_string)
                    conv_len += 8; /* hold "(null)" */
                  else if (spec.seen_precision)
                    conv_len += spec.precision;
                  else
                    conv_len += strlen (v_string);
                  conv_done = TRUE;
                  if (spec.mod_long)
                    {
                      i_warning (GNUC_PRETTY_FUNCTION
                                 "(): unable to handle wide char strings");
                      len += 1024; /* try adding some safety padding */
                    }
                  break;
                case 'P': /* do we actually need this? */
                  /* fall through */
                case 'p':
                  spec.alternate_format = TRUE;
                  conv_len += 10;
                  if (HONOUR_LONGS)
                    conv_len *= 2;
                  /* fall through */
                case 'n':
                  conv_done = TRUE;
                  (void) va_arg (args, void*);
                  break;
                case 'm':
                  /* there's not much we can do to be clever */
                  v_string = strerror (errno);
                  v_uint = v_string ? strlen (v_string) : 0;
                  conv_len += I_MAX (256, v_uint);
                  break;

                  /* handle invalid cases
                   */
                case '\000':
                  /* no conversion specification, bad bad */
                  conv_len += format - spec_start;
                  break;
                default:
                  i_warning (GNUC_PRETTY_FUNCTION
                             "(): unable to handle `%c' while parsing format",
                             c);
                  break;
                }
              conv_done |= conv_len > 0;
            }
          while (!conv_done);
          /* handle width specifications */
          conv_len = I_MAX (conv_len, I_MAX (spec.precision, spec.min_width));
          /* handle flags */
          conv_len += spec.alternate_format ? 2 : 0;
          conv_len += (spec.add_space || spec.add_sign || spec.possible_sign);
          /* finally done */
          len += conv_len;
        } /* else (c == '%') */
    } /* while (*format) */

  return len;
}

static const char *fix_format_real(const char *fmt, const char *p)
{
	const char *errstr;
	char *buf;
	unsigned int pos, alloc, errlen;

	errstr = strerror(errno);
	errlen = strlen(errstr);

	pos = (unsigned int) (p-fmt);
	i_assert(pos < INT_MAX);

	alloc = pos + errlen + 128;
	buf = t_buffer_get(alloc);

	memcpy(buf, fmt, pos);

	while (*p != '\0') {
		if (*p == '%' && p[1] == 'm') {
			if (pos+errlen+1 > alloc) {
				alloc += errlen+1 + 128;
				buf = t_buffer_get(alloc);
			}

			memcpy(buf+pos, errstr, errlen);
			pos += errlen;
			p += 2;
		} else {
			/* p + \0 */
			if (pos+2 > alloc) {
				alloc += 128;
				buf = t_buffer_get(alloc);
			}

			buf[pos++] = *p;
			p++;
		}
	}

	buf[pos++] = '\0';
	t_buffer_alloc(pos);
	return buf;
}

/* replace %m with strerror() */
static const char *fix_format(const char *fmt)
{
	const char *p;

	for (p = fmt; *p != '\0'; p++) {
		if (*p == '%' && p[1] == 'm')
			return fix_format_real(fmt, p);
	}

	return fmt;
}

int i_snprintf(char *str, unsigned int max_chars, const char *format, ...)
{
#ifdef HAVE_VSNPRINTF
	va_list args;
	int ret;

	i_assert(str != NULL);
	i_assert(max_chars < INT_MAX);
	i_assert(format != NULL);

	va_start(args, format);
	ret = vsnprintf(str, max_chars, fix_format(format), args);
	va_end(args);

	if (ret < 0) {
		str[max_chars-1] = '\0';
		ret = strlen(str);
	}

	return ret;
#else
	char *buf;
	va_list args;
        int len;

	i_assert(str != NULL);
	i_assert(max_chars < INT_MAX);
	i_assert(format != NULL);

	va_start(args, format);
	format = fix_format(format);
	buf = t_buffer_get(printf_string_upper_bound(format, args));
	va_end(args);

	len = vsprintf(buf, format, args);
	if (len >= (int)max_chars)
		len = max_chars-1;

        memcpy(str, buf, len);
	str[len] = '\0';
	return len;
#endif
}

#define STRDUP_CORE(alloc_func, str) STMT_START { \
	void *mem;				\
	unsigned int len;			\
						\
	for (len = 0; (str)[len] != '\0'; )	\
		len++;				\
	len++;					\
	mem = alloc_func;			\
	memcpy(mem, str, sizeof(str[0])*len);	\
	return mem;				\
	} STMT_END

char *p_strdup(Pool pool, const char *str)
{
	if (str == NULL)
                return NULL;

        STRDUP_CORE(p_malloc(pool, len), str);
}

const char *t_strdup(const char *str)
{
	if (str == NULL)
                return NULL;

        STRDUP_CORE(t_malloc(len), str);
}

int *p_intarrdup(Pool pool, const int *arr)
{
	if (arr == NULL)
                return NULL;

        STRDUP_CORE(p_malloc(pool, sizeof(int) * len), arr);
}

const int *t_intarrdup(const int *arr)
{
	if (arr == NULL)
                return NULL;

        STRDUP_CORE(t_malloc(sizeof(int) * len), arr);
}

#define STRDUP_EMPTY_CORE(alloc_func, str) STMT_START { \
	if ((str) == NULL || (str)[0] == '\0')	\
                return NULL;			\
						\
	STRDUP_CORE(alloc_func, str);		\
	} STMT_END


char *p_strdup_empty(Pool pool, const char *str)
{
        STRDUP_EMPTY_CORE(p_malloc(pool, len), str);
}

const char *t_strdup_empty(const char *str)
{
        STRDUP_EMPTY_CORE(t_malloc(len), str);
}

char *p_strdup_until(Pool pool, const char *start, const char *end)
{
	unsigned int size;
	char *mem;

	i_assert(start <= end);

	size = (unsigned int) (end-start);
	i_assert(size < UINT_MAX);

	mem = p_malloc(pool, size + 1);
	memcpy(mem, start, size);
	return mem;
}

const char *t_strdup_until(const char *start, const char *end)
{
	unsigned int size;
	char *mem;

	i_assert(start <= end);

	size = (unsigned int) (end-start);
	i_assert(size < UINT_MAX);

	mem = t_malloc(size + 1);
	memcpy(mem, start, size);
	mem[size] = '\0';
	return mem;
}

static inline char *
strndup_core(const char *str, unsigned int max_chars,
	     ALLOC_FUNC alloc, Pool pool)
{
	char *mem;
	unsigned int len;

	i_assert(max_chars < INT_MAX);

	if (str == NULL)
		return NULL;

	len = 0;
	while (str[len] != '\0' && len < max_chars)
		len++;

	mem = alloc(pool, len+1);
	memcpy(mem, str, len);
	mem[len] = '\0';
	return mem;
}

char *p_strndup(Pool pool, const char *str, unsigned int max_chars)
{
        return strndup_core(str, max_chars, pool->malloc, pool);
}

const char *t_strndup(const char *str, unsigned int max_chars)
{
        return strndup_core(str, max_chars, tp_malloc, NULL);
}

char *p_strdup_printf(Pool pool, const char *format, ...)
{
	va_list args;
        char *ret;

	va_start(args, format);
        ret = p_strdup_vprintf(pool, format, args);
	va_end(args);

	return ret;
}

const char *t_strdup_printf(const char *format, ...)
{
	va_list args;
        const char *ret;

	va_start(args, format);
        ret = t_strdup_vprintf(format, args);
	va_end(args);

	return ret;
}

static inline char *
strdup_vprintf_core(const char *format, va_list args,
		    ALLOC_FUNC alloc_func, Pool pool)
{
        va_list temp_args;
        char *ret;

	if (format == NULL)
		return NULL;
	format = fix_format(format);

	VA_COPY(temp_args, args);

        ret = alloc_func(pool, printf_string_upper_bound(format, args));
	vsprintf(ret, format, args);

	va_end(temp_args);

        return ret;
}

char *p_strdup_vprintf(Pool pool, const char *format, va_list args)
{
        return strdup_vprintf_core(format, args, pool->malloc, pool);
}

const char *t_strdup_vprintf(const char *format, va_list args)
{
        return strdup_vprintf_core(format, args, tp_malloc, NULL);
}

void p_strdup_replace(Pool pool, char **dest, const char *str)
{
	p_free(pool, *dest);
        *dest = p_strdup(pool, str);
}

const char *temp_strconcat(const char *str1, va_list args,
			   unsigned int *ret_len)
{
	const char *str;
        char *temp;
	unsigned int full_len, len, bufsize;

	if (str1 == NULL)
		return NULL;

        /* put str1 to buffer */
        len = strlen(str1);
	bufsize = len <= STRCONCAT_BUFSIZE ? STRCONCAT_BUFSIZE :
		nearest_power(len+1);
        temp = t_buffer_get(bufsize);

	memcpy(temp, str1, len);
	full_len = len;

        /* put rest of the strings to buffer */
	while ((str = va_arg(args, char *)) != NULL) {
		len = strlen(str);
		if (len == 0)
			continue;

		if (bufsize < full_len+len+1) {
			bufsize = nearest_power(bufsize+len+1);
			temp = t_buffer_reget(temp, bufsize);
		}

                memcpy(temp+full_len, str, len);
		full_len += len;
	}

	temp[full_len] = '\0';
        *ret_len = full_len+1;
        return temp;
}

char *p_strconcat(Pool pool, const char *str1, ...)
{
	va_list args;
        const char *temp;
	char *ret;
        unsigned int len;

	va_start(args, str1);

	temp = temp_strconcat(str1, args, &len);
	if (temp == NULL)
		ret = NULL;
	else {
		ret = p_malloc(pool, len);
		memcpy(ret, temp, len);
	}

	va_end(args);
        return ret;
}

const char *t_strconcat(const char *str1, ...)
{
	va_list args;
	const char *ret;
        unsigned int len;

	va_start(args, str1);

	ret = temp_strconcat(str1, args, &len);
	if (ret != NULL)
		t_buffer_alloc(len);

	va_end(args);
        return ret;
}

const char *t_strcut(const char *str, char cutchar)
{
	const char *p;

	for (p = str; *p != '\0'; p++) {
		if (*p == cutchar)
                        return t_strdup_until(str, p);
	}

        return str;
}

int is_numeric(const char *str, char end_char)
{
	if (*str == '\0' || *str == end_char)
		return FALSE;

	while (*str != '\0' && *str != end_char) {
		if (!i_isdigit(*str))
			return FALSE;
		str++;
	}

	return TRUE;
}

char *str_ucase(char *str)
{
	char *p;

	for (p = str; *p != '\0'; p++)
		*p = i_toupper(*p);
        return str;
}

char *str_lcase(char *str)
{
	char *p;

	for (p = str; *p != '\0'; p++)
		*p = i_tolower(*p);
        return str;
}

char *i_strtoken(char **str, char delim)
{
	char *ret;

	if (*str == NULL || **str == '\0')
                return NULL;

	ret = *str;
	while (**str != '\0') {
		if (**str == delim) {
			**str = '\0';
                        (*str)++;
                        break;
		}
                (*str)++;
	}
        return ret;
}

void string_remove_escapes(char *str)
{
	char *dest;

	for (dest = str; *str != '\0'; str++) {
		if (*str != '\\' || str[1] == '\0')
			*dest++ = *str;
	}

	*dest = '\0';
}

int strarray_length(char *const array[])
{
	int len;

	len = 0;
	while (*array) {
		len++;
                array++;
	}
        return len;
}

int strarray_find(char *const array[], const char *item)
{
	int index;

	i_assert(item != NULL);

	for (index = 0; *array != NULL; index++, array++) {
		if (strcasecmp(*array, item) == 0)
			return index;
	}

	return -1;
}

char *const *t_strsplit(const char *data, const char *separators)
{
        const char **array;
	char *str;
        int alloc_len, len;

        i_assert(*separators != '\0');

	str = (char *) t_strdup(data);

        alloc_len = 20;
        array = t_buffer_get(sizeof(const char *) * alloc_len);

	array[0] = str; len = 1;
	while (*str != '\0') {
		if (strchr(separators, *str) != NULL) {
			/* separator found */
			if (len+1 >= alloc_len) {
                                alloc_len *= 2;
				array = t_buffer_reget(array,
						       sizeof(const char *) *
						       alloc_len);
			}

                        *str = '\0';
			array[len++] = str+1;
		}

                str++;
	}
        array[len] = NULL;

	t_buffer_alloc(sizeof(const char *) * (len+1));
        return (char *const *) array;
}

const char *t_strjoin_replace(char *const args[], char separator,
			      int replacearg, const char *replacedata)
{
        const char *arg;
        char *data;
	unsigned int alloc_len, arg_len, full_len;
	int i;

	if (args[0] == NULL)
                return NULL;

        alloc_len = 512; full_len = 0;
	data = t_buffer_get(alloc_len);
	for (i = 0; args[i] != NULL; i++) {
		arg = i == replacearg ? replacedata : args[i];
		arg_len = strlen(arg);

		if (full_len + arg_len+1 >= alloc_len) {
			alloc_len = nearest_power(full_len + arg_len+1);
                        data = t_buffer_reget(data, alloc_len);
		}

		memcpy(data+full_len, arg, arg_len);
                full_len += arg_len;

                data[full_len++] = separator;
	}
        data[full_len-1] = '\0';

        t_buffer_alloc(full_len);
        return data;
}