/*
 *	binkleyforce -- unix FTN mailer project
 *	
 *	Copyright (c) 1998-2000 Alexander Belkin, 2:5020/1398.11
 *	
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *	
 *	$Id: u_string.c,v 1.3 2001/08/31 08:54:47 kst Exp $
 */

#ifndef TEST
#include "includes.h"
#include "confread.h"
#include "logger.h"
#include "util.h"
#else
#include <stdio.h>
#include <stdarg.h>
#define TRUE     1
#define FALSE    0
#define bool     int
#define xmalloc  malloc
#define xrealloc realloc
#define ASSERT
#endif

/*
 * Maximum number of quoted-printable strings we will store together
 */
#define PRINTABLE_MAXITEMS 5

/*
 * Cyclic buffer for quoted-printable strings
 */
static char *printable_storage[PRINTABLE_MAXITEMS];

/*
 * Position of next entry to use in printable_storage[]
 */
static int printable_pos = -1;


/*****************************************************************************
 * Allocate memory for the copy of a string pointed by src
 *
 * Arguments:
 * 	src       pointer to the null-terminated string
 *
 * Return value:
 * 	Pointer to the resulting string (must be freed)
 */
char *xstrcpy(const char *src)
{
	char *tmp;

	if( !src )
		return NULL;
	
	tmp = xmalloc(strlen(src)+1);
	strcpy(tmp, src);
	
	return tmp;
}

/*****************************************************************************
 * Append string pointed by add to the string src, allocate memory for result
 *
 * Arguments:
 * 	src       pointer to the null-terminated string (will be freed)
 * 	add       this one will be appended to the src
 *
 * Return value:
 * 	Pointer to the resulting string (must be freed)
 */
char *xstrcat(char *src, const char *add)
{
	char *tmp;
	size_t size;

	if( !add || *add == '\0' )
		return src;
	
	size = (src ? strlen(src) : 0) + strlen(add);
	tmp  = (char*)xmalloc(size+1);
	
	if( src )
	{
		strcpy(tmp, src);
		free(src);
	} else
		*tmp = '\0';
	
	strcat(tmp, add);
	
	return tmp;
}

/*****************************************************************************
 * Copy a string to the buffer. Checks for the destination buffer overflow and
 * terminating '\0' character.
 *
 * Arguments:
 * 	dst       pointer to the destination buffer
 * 	src       string to copy from
 * 	len       destination buffer size
 *
 * Return value:
 * 	Pointer to the resulting string
 */
char *strnxcpy(char *dst, const char *src, size_t len)
{
	ASSERT(src != NULL && dst != NULL && len >= 0);
	
	dst[len - 1] = 0;
	
	return strncpy(dst, src, len - 1);
}

/*****************************************************************************
 * Add one string to the end of another. Checks for destination buffer
 * overflow and terminating '\0' character.
 *
 * Arguments:
 * 	dst       pointer to the destination buffer (append to the string
 * 	          stored in it)
 * 	src       append this string
 * 	len       destination buffer size
 *
 * Return value:
 * 	Pointer to the resulting string
 */
char *strnxcat(char *dst, const char *src, size_t len)
{
	int pos;
	
	ASSERT(src != NULL && dst != NULL && len >= 0);
	
	pos = strlen(dst);
	
	return strnxcpy(dst + pos, src, len - pos);
}

/*****************************************************************************
 * Get next substring from the string.
 *
 * Arguments:
 * 	str       pointer to a null-terminated string
 * 	next      points to the pointer where you want store address of
 * 	          the next substring for next calls
 * 	delim     delimiter characters, if NULL than the isspace() call used
 * 	          to check for the delimiter characters
 * 	quoted    ignore spaces in the quoted substrings
 *
 * Return value:
 * 	Pointer to the new substring or NULL if no more available
 */
char *string_token(char *str, char **next, const char *delim, int quoted)
{
	char *yield;
	char *p;
	
	ASSERT(next != NULL);
	
	yield = str ? str : *next;
	
	if( yield )
	{
		if( delim && *delim )
			while( *yield && strchr(delim, *yield) ) yield++;
		else
			while( isspace(*yield) ) yield++;
		
		if( *yield )
		{
			if( quoted && *yield == '"' )
			{
				p = ++yield;
				while( *p && *p != '"' ) p++;
			}
			else
			{
				p = yield;
				if( delim && *delim )
					while( *p && !strchr(delim, *p) ) p++;
				else
					while( *p && !isspace(*p) ) p++;
			}
			if( *p )
			{
				*p = '\0';
				*next = p+1;
			} else
				*next = NULL;
		} else
			yield = *next = NULL;
	}

	return yield;
}

/*****************************************************************************
 * Remove trailing CR and LF characters
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	Pointer to the string
 */
char *string_chomp(char *str)
{
	char *p;

	ASSERT(str != NULL);
	
	if( str && *str )
	{
		p = str + strlen(str + 1);
		if( *p == '\n' )
			*p-- = '\0';
		if( *p == '\r' && p >= str )
			*p = '\0';
	}
	
	return str;
}

/*****************************************************************************
 * Find the firtst occurance of substring into the string
 *
 * Arguments:
 * 	substr    pointer to the null-terminated substring
 * 	string    pointer to the null-terminated string
 *
 * Return value:
 * 	Pointer to the first occurrence of the substring in the string,
 * 	and NULL if substring is not found
 */
const char *string_casestr(const char *string, const char *substr)
{
	size_t subln = strlen(substr);
	size_t strln = strlen(string);
	
	while( *string && strln >= subln )
	{
		if( !strncasecmp(substr, string, subln) )
			return string;
		
		++string;
		--strln;
	}

	return NULL;
}

/*****************************************************************************
 * Find character in the string (It is like strchr(), but case insensitive)
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 * 	ch        character you want to find
 *
 * Return value:
 * 	Pointer to the first occurrence of the character in the string,
 * 	and NULL if character is not found
 */
const char *string_casechr(const char *str, int ch)
{
	ch = tolower(ch);
	
	while( *str )
	{
		if( tolower(*str) == ch )
			return str;

		++str;
	}

	return NULL;
}

/*****************************************************************************
 * Convert string to the upper case
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	Pointer to the string
 */
char *string_toupper(char *str)
{
	char *p;
	
	ASSERT(str != NULL);
	
	for( p = str; *p; p++ ) *p = toupper(*p);
	
	return(str);
}

/*****************************************************************************
 * Convert string to the lower case
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	Pointer to the string
 */
char *string_tolower(char *str)
{
	char *p;
	
	ASSERT(str != NULL);
	
	for( p = str; *p; p++ ) *p = tolower(*p);
	
	return(str);
}

/*****************************************************************************
 * Check wheather string doesn't contain lower case characters
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	Return TRUE if string conforms our requirements, and FALSE if not
 */
bool string_isupper(const char *str)
{
	const char *p;
	
	ASSERT(str != NULL);
	
	for( p = str; *p; p++ )
	{
		if( isalpha(*p) && islower(*p) ) break;
	}

	return (*p == '\0') ? TRUE : FALSE;
}

/*****************************************************************************
 * Check wheather string doesn't contain upper case characters
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	Return TRUE if string conforms our requirements, and FALSE if not
 */
bool string_islower(const char *str)
{
	const char *p;
	
	ASSERT(str != NULL);
	
	for( p = str; *p; p++ )
	{
		if( isalpha(*p) && isupper(*p) ) break;
	}
	
	return (*p == '\0') ? TRUE : FALSE;
}

/*****************************************************************************
 * Remove spaces at the end of string
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	Pointer to the string
 */
char *string_trimright(char *str)
{
	char *p;

	ASSERT(str != NULL);

	if( str && *str )
	{
		p = str + strlen(str+1);
		while( p >= str && isspace(*p) ) *p-- = '\0';
	}
	
	return str;
}

/*****************************************************************************
 * Remove spaces at the begining of string
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	pointer to the string
 */
char *string_trimleft(char *str)
{
	char *p;
	
	ASSERT(str != NULL);

	if( str && *str )
	{
		p = str;
		while( isspace(*p) ) p++;
		if( p > str )
			memmove(str, p, strlen(p)+1);
	}
	
	return str;
}

/*****************************************************************************
 * Remove white spaces at the begining and at the end of string
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	pointer to the string
 */
char *string_trimboth(char *str)
{
	char *p;
	
	ASSERT(str != NULL);

	if( str && *str )
	{
		/* Remove leading spaces */
		p = str;
		while( isspace(*p) ) p++;
		if( p > str )
			memmove(str, p, strlen(p)+1);
		
		/* Remove trailing spaces */
		p = str + strlen(str+1);
		while( p >= str && isspace(*p) ) *p-- = '\0';
	}
	
	return str;
}

/*****************************************************************************
 * Quote all unprintable characters in the buffer (e.g. "\xff\xfe")
 *
 * Arguments:
 * 	buffer    pointer to the buffer to process
 * 	buflen    buffer size
 *
 * Return value:
 * 	Pointer to the quoted-printable string (must be freed)
 */
char *string_printable_buffer(const char *buffer, size_t buflen)
{
	char *dest, *p;
	const char *bp;
	int nonprintcount = 0;
	size_t pos = 0;
	
	for( bp = buffer, pos = 0; pos < buflen; bp++, pos++ )
	{
		if( !isprint((unsigned char)*bp) )
			nonprintcount++;
	}
	
	dest = xmalloc(buflen + nonprintcount * 4 + 1);
	
	if( nonprintcount == 0 )
	{
		memcpy(dest, buffer, buflen);
		dest[buflen] = '\0';
	}
	else
	{
		p = dest;
		for( pos = 0; pos < buflen; buffer++, pos++ )
		{
			if( !isprint((unsigned char)*buffer) )
			{
				sprintf(p, "\\x%02x", (unsigned char)*buffer);
				p += 4;
			} else
				*p++ = *buffer;
		}
		*p = '\0';
	}
	
	return dest;
}

/*****************************************************************************
 * Quote all unprintable characters in the string (e.g. "\xff\xfe")
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 *
 * Return value:
 * 	Pointer to the quoted-printable string. Memory allocated for the
 * 	resulting string will be freed automatically. So don't use more
 * 	than last PRINTABLE_MAXITEMS pointers returned by this function.
 */
const char *string_printable(const char *str)
{
	char *p;
	const char *q;
	int nonprintcount = 0;
	size_t pos = 0;
	size_t len = 0;
	
	if( printable_pos == -1 )
	{
		memset(printable_storage, '\0', sizeof(printable_storage));
		printable_pos = 0;
	}
	
	if( str == NULL ) return "(null)";

	len = strlen(str);
	
	for( q = str, pos = 0; pos < len; q++, pos++ )
	{
		if( iscntrl((unsigned char)*q) || !isprint((unsigned char)*q) )
			nonprintcount++;
	}
	
	if( !nonprintcount )
		return str;
	
	if( printable_pos >= PRINTABLE_MAXITEMS )
		printable_pos = 0;
	
	if( printable_storage[printable_pos] )
		free(printable_storage[printable_pos]);
	
	p = printable_storage[printable_pos] = xmalloc(len + nonprintcount * 4 + 1);
	
	for( pos = 0; pos < len; str++, pos++ )
	{
		if( iscntrl((unsigned char)*str) || !isprint((unsigned char)*str) )
		{
			sprintf(p, "\\x%02x", (unsigned char)*str);
			p += 4;
		} else
			*p++ = *str;
	}
	*p = '\0';
	
	return printable_storage[printable_pos++];
}

/*****************************************************************************
 * Replace all chracters 'oldchar' by the 'newchar'
 *
 * Arguments:
 * 	str       pointer to the null-terminated string
 * 	oldchar   old chracter
 * 	newchar   character to put istead of 'oldchar'
 *
 * Return value:
 * 	Pointer to the string
 */
char *string_replchar(char *str, char oldchar, char newchar)
{
	char *p = str;
	
	while( *p )
	{
		if( *p == oldchar ) *p = newchar;
		++p;
	}
	
	return str;
}

/*****************************************************************************
 * Devide string on substrings and put pointers to them into the
 * dest[] array.
 *
 * Arguments:
 * 	dest      destination array where we should put substrings
 * 	items     number of entries in dest[]
 * 	str       string to parse (will be destroyed)
 * 	separator separator character
 *
 * Return value:
 * 	Number of substrings in the dest[]
 */
int string_parse(char **dest, int items, char *str, int separator)
{
	int count = 0;
	char *p = str;
	
	dest[count++] = str;
	
	while( *p && count < items )
	{
		if( *((unsigned char *)p) == separator )
		{
			*p++ = '\0';
			dest[count++] = p;
		} else
			++p;
	}

	return count;
}

/*****************************************************************************
 * Devide string on substrings separated by white-space characters and put
 * pointers to them into the dest[] array. Also it will ignore spaces in
 * quoted strings (quote characters will be removed)
 *
 * Arguments:
 * 	dest      destination array where we should put substrings
 * 	items     number of entries in dest[]
 * 	str       string to parse (will be destroyed)
 *
 * Return value:
 * 	Number of substrings in the dest[]
 */
int string_parse_regular(char **dest, int items, char *str)
{
	int count = 0;
	char *p = str;
	
	while( *p )
	{
		while( *p && isspace(*p) )
			++p;
		
		if( *((unsigned char *)p) == '"' && *++p )
		{
			dest[count++] = p;
			while( *p && *((unsigned char *)p) != '"' ) p++;
		}
		else if( *p )
		{
			dest[count++] = p;
			while( *p && !isspace(*p) ) p++;
		}
		
		if( *p && count < items )
			*p++ = '\0';
		else
			break;
	}

	return count;
}

/*****************************************************************************
 * Replace all substrings 'find' by the 'repl' in the string 'str'
 *
 * Arguments:
 * 	str       null-terminated string to process (will be unchanged)
 * 	find      substring to find
 * 	repl      replace substring
 *
 * Return value:
 * 	Pointer to the new string (must be freed)
 */
char *string_translate(const char *str, const char *find, const char *repl)
{
	size_t sz_find = strlen(find);
	size_t sz_repl = strlen(repl);
	size_t sz_dest = strlen(str);
	char *dest, *p;
	
	p = dest = xstrcpy(str);
	
	if( !sz_find ) return dest;
	
	while( *p )
	{
		if( memcmp(p, find, sz_find) == 0 )
		{
			size_t offset = p - dest;
			size_t newsize = sz_dest + (sz_repl - sz_find);
			
			if( newsize > sz_dest )
				dest = xrealloc(dest, newsize+1);
			
			if( sz_repl > sz_find )
				memmove(dest + offset + (sz_repl - sz_find), dest + offset, sz_dest - offset + 1);
			else if( sz_repl < sz_find )
				memmove(dest + offset, dest + offset + (sz_find - sz_repl), sz_dest - offset + 1);
			
			memcpy(dest + offset, repl, sz_repl);
			
			sz_dest = newsize;
			p = dest + offset + sz_repl;
		} else
			++p;
	}
	
	return dest;
}

/*****************************************************************************
 * Convert size into human readable format (e.g. 100,256Kb or 20.7Mb)
 *
 * Arguments:
 * 	buffer    pointer to the buffer for resulting string
 * 	size      size in bytes that you want to print
 *
 * Return value:
 * 	Pointer to the string
 */
char *string_humansize(char *buffer, size_t size)
{
	if( size < 1024 )
		sprintf(buffer, "%ldb", (long)size);
	else if( size < 100*1024 )
		sprintf(buffer, "%.1fK", ((double)size)/1024.0);
	else if( size < 1000*1024 )
		sprintf(buffer, "%ldK", (long)(size/1024));
	else if( size < 100*1024*1024 )
		sprintf(buffer, "%.1fM", ((double)size)/(1024.0*1024.0));
	else if( size < 1000*1024*1024 )
		sprintf(buffer, "%ldM", (long)(size/(1024*1024)));
	else
		sprintf(buffer, "%.1fG", ((double)size)/(1024.0*1024.0*1024.0));

	return string_replchar(buffer, ',', '.');
}

/*****************************************************************************
 * Interpret escape sequence (\xNN, \NNN, \r, \n, etc.)
 *
 * Arguments:
 * 	pptr      points a pointer to the first character after '\'
 *
 * Return value:
 * 	Value of the escaped character
 */
int string_get_escape(char **pptr)
{
	int ch = 0;
	static const char *hexdigits = "0123456789abcdef";
	char *p = *pptr;
	
	if( *p == 'x' )
	{
		const char *q;
		
		if( (q = string_casechr(hexdigits, *(++p))) )
		{
			ch = q - hexdigits;
			if( (q = string_casechr(hexdigits, *(++p))) )
			{
				ch = ch * 16 + (q - hexdigits);
			}
		}
	}
	else if( *p == '0' || *p == '1' || *p == '2' || *p == '3' )
	{
		ch = *(p++) - '0';
		if( isdigit(*p) && *p != '8' && *p != '9' )
		{
			ch = ch * 8 + (*(p++) - '0');
			if( isdigit(*p) && *p != '8' && *p != '9' )
			{
				ch = ch * 8 + (*p - '0');
			}
		}
	}
	else switch(*p) {
	case 'a':
		ch = '\a'; break;
	case 'b':
		ch = '\b'; break;
	case 'f':
		ch = '\f'; break;
	case 'n':
		ch = '\n'; break;
	case 'r':
		ch = '\r'; break;
	case 't':
		ch = '\t'; break;
	case '\\':
		ch = '\\'; break;
	default:
		ch = *p;
	}

	*pptr = p + 1;

	return (ch > 0) ? (ch & 0xff) : -1;
}

int string_dequote(char *dst, char *src)
{
	char *d = dst; 
	char *s = src;
	int ch;
	
	while( *s )
	{
		if( s[0] == '\\' && s[1] )
		{
			++s;
			ch = string_get_escape(&s);
			if( ch != -1 )
				*d++ = ch;
		}
		else
			*d++ = *s++;
	}

	*d = '\0';

	return 0;
}

char *string_concat(const char *str, ...)
{
	va_list args;
	size_t yield_len;
	char  *yield_ptr;
	char  *yield;
	char  *p;
	
	/*
	 * Calculate total length of yielding string
	 */
	yield_len = strlen(str);
	va_start(args, str);
	while( (p = va_arg(args, char *)) )
		yield_len += strlen(p);
	va_end(args);
	
	yield = xmalloc(yield_len + 1);
	strncpy(yield, str, yield_len);
	yield[yield_len] = '\0';
	yield_ptr = yield + strlen(yield);
	
	va_start(args, str);
	while( (p = va_arg(args, char *)) )
	{
		strcpy(yield_ptr, p);
		yield_ptr += strlen(p);
	}
	va_end(args);
	
	return yield;
}

void string_bin_to_hex(char *string, const char *binptr, int binlen)
{
	static const char *hexdigits = "0123456789abcdef";
	int i;

	for( i = 0; i < binlen; i++ )
	{
		*string++ = hexdigits[(*binptr >> 4) & 0x0f];
		*string++ = hexdigits[(*binptr     ) & 0x0f];
		++binptr;
	}
	*string = '\0';
}

int string_hex_to_bin(char *binptr, const char *string)
{
	static const char *hexdigits = "0123456789abcdef";
	int len = (int)strlen(string);
	int i, val;
	const char *p;
	char *dest = binptr;

	for( i = 0; 2*i < len; i++ )
	{
		if( (p = string_casechr(hexdigits, *(string++))) )
		{
			val = (int)(p - hexdigits);
			if( (p = string_casechr(hexdigits, *(string++))) )
			{
				val = val * 16 + (int)(p - hexdigits);
				*dest++ = (unsigned char)(val & 0xff);
			}
			else
				return 0;
		}
		else
			return 0;
	}

	return (int)(dest - binptr);
}

bool string_is_empty(const char *string)
{
	const char *p;

	for( p = string; *p; p++ )
		if( !isspace((int)(unsigned char)*p) )
			return FALSE;

	return TRUE;
}

#ifdef TEST
int main(void)
{
	char *hexstr = "0406ff2354124e6a9b2f6ed6ff00411287";
	char binbuf[64];
	int binlen;
	char newhex[64];
	
	/*
	 * string_hex_to_bin() and string_bin_to_hex()
	 */
	printf("*** Checking string_hex_to_bin() and string_bin_to_hex()\n");
	printf("Original string  : \"%s\"\n", hexstr);
	binlen = string_hex_to_bin(binbuf, hexstr);
	string_bin_to_hex(newhex, binbuf, binlen);
	printf("Resulting string : \"%s\"\n", newhex);
	if( strcmp(hexstr, newhex) )
		printf("<<< ERROR!!! >>>\n");
	
	return 0;
}
#endif /* TEST */

