/*
** 1998-08-02 -	This module holds various utility functions sharing one common trait:
**		they all deal with character strings in one way or another.
*/

#include "gentoo.h"

#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "strutil.h"
#include "sizeutil.h"

/* ----------------------------------------------------------------------------------------- */

/* 1998-08-02 -	Copy at most <n> bytes from <src> to <dst>, including the terminating
**		'\0'-byte. If <src> is longer than <n-1>, it will be truncated with a
**		'\0'-byte at position n-1. Example: str_strncpy(b, "string", 1) would
**		put a '\0'-byte at b[0] and return. Basically, think of the <n> as a
**		limiter on the number of bytes at <dst> we're allowed to touch, and
**		then fit a string-copying idea on top of that. :^) Works for me, and a
**		lot better than the standard strncpy(), too.
*/
gchar * str_strncpy(gchar *dst, const gchar *src, gsize n)
{
	gchar	*ret = dst;

	if(dst && !src)
		*dst = '\0';
	else if(src && dst && n >= 1)
	{
		for(n--; n > 0 && *src != '\0'; n--)
			*dst++ = *src++;
		*dst = '\0';
	}

	return ret;
}

/* 1998-08-23 -	Concatenate <src> onto end of <dst>, but never write more than <n> bytes
**		total to <dst>. This is similar to strcat(3), but not identical. Returns
**		<dst>.
*/
gchar * str_strncat(gchar *dst, const gchar *src, gsize n)
{
	gchar	*orig = dst;

	if(n == 0)
		return NULL;
	n--;	/* Make sure we'll have room for the '\0'. */
	for(; *dst != '\0' && n > 0; dst++, n--)
		;
	for(; *src != '\0' && n > 0; n--)
		*(dst++) = *src++;
	*dst = '\0';

	return orig;
}

/* 1998-09-15 -	Copy <src> to <dst>, using no more than <n> bytes total at <dst>. While
**		copying, convert each character into upper case.
*/
gchar * str_strntoupper(gchar *dst, gchar *src, gsize n)
{
	gchar	*orig = dst;

	if(n == 0)
		return NULL;
	n--;
	for(; (*dst++ = toupper((int) *src++)) != '\0';)
		;
	*dst = '\0';
	return orig;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-06 -	This function has a silly name. It is used to get a pointer to the
**		last occurance of the character <c> in the string <str>, ignoring case.
**		It returns such a pointer, or NULL if <c> doesn't appear in <str>. The
**		string being searched is not modified.
*/
const gchar * str_strcaserchr(const gchar *str, gchar c)
{
	const gchar	*ptr;
	gsize		len;

	if((len = strlen(str)) == 0)
		return NULL;

	for(c = toupper((int) c), ptr = str + len - 1; ptr >= str; ptr--)
		if(toupper((int) *ptr) == c)
			return ptr;

	return NULL;
}

/* 1998-08-02 -	Check if <string> ends with the suffix <suffix>. Note that this is fairly
**		general; it does not assume (or require) <suffix> to begin with a period
**		or any some such. If you want it to, include the period yourself. Returns
**		1 if there is indeed a suffix match, 0 otherwise.
** 1998-09-13 -	Now handles suffixes of the ".tar.gz" kind, where the initial letter to
**		match is NOT the last letter of its kind in the string. This made it a tad
**		slower, sure, but hey.
*/
gboolean str_has_suffix(const gchar *string, const gchar *suffix)
{
	gsize	len, sl;

	if(string == NULL || suffix == NULL)
		return FALSE;

	len = strlen(string);
	sl  = strlen(suffix);

	if(len < sl)		/* String shorter than suffix? */
		return FALSE;

	return str_strcasecmp(string + len - sl, suffix) == 0;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-08-30 -	Core glob->RE translator. Returns a pointer to a (dynamically allocated)
**		piece of memory holding the RE. When done with that, please free() it.
*/
const gchar * str_glob_to_re(const gchar *glob)
{
	GString		*re;
	gchar		here;
	const gchar	*ptr, *end, *ret = NULL;

	if((re = g_string_new(NULL)) != NULL)
	{
		for(ptr = glob; *ptr != '\0'; ptr++)
		{
			here = *ptr;
			if(here == '[' && (*(ptr + 1) != ']'))		/* Character set begins? */
			{
				if((end = strchr(ptr + 1, ']')) != NULL)
				{
					for(; ptr <= end; ptr++)
						g_string_append_c(re, *ptr);
				}
			}
			else
			{
				switch(here)
				{
					case '.':
						g_string_append(re, "\\.");
						break;
					case '*':
						g_string_append(re, ".*");
						break;
					case '+':
						g_string_append(re, "\\+");
						break;
					case '?':
						g_string_append_c(re, '.');
						break;
					default:
						g_string_append_c(re, here);
				}
			}
		}
		ret = re->str;
		g_string_free(re, FALSE);		/* Frees the GString, except for its buffer. */
	}
	return ret;
}

/* 1998-08-30 -	Translate a glob pattern into a System V8 regular expression. Thanks to the
**		magic of GStrings, it will look to the caller as if the string is simply
**		replaced by the translation. This code was moved from the good 'ol cmd_select
**		module, since I wanted glob->RE translation in other places, too (types).
**		This routine doesn't exactly fit in among the other, but since it deals with
**		strings, I thought it could live here at least for a while.
*/
void str_gstring_glob_to_re(GString *glob)
{
	const gchar	*re;

	if((re = str_glob_to_re(glob->str)) != NULL)
	{
		g_string_assign(glob, re);
		free((void *) re);
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-19 -	Convert the protection <mode> to a (more) human-readable form stored at <buf>.
**		Will not use more than <max> bytes of <buf>. Returns <buf>, or NULL on failure.
** 1999-01-05 -	Finally sat down and experimentally deduced the way GNU 'ls' formats its mode
**		strings, and did something similar here.
*/
gchar * str_mode_to_text(gchar *buf, mode_t mode, gsize max)
{
	gchar	*grp[] = { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx" };
	gint	u, g, o;

	if(max < 12)			/* A lazy size limitation. */
		return NULL;
	u = (mode & S_IRWXU) >> 6;
	g = (mode & S_IRWXG) >> 3;
	o = (mode & S_IRWXO);
	sprintf(buf, "-%s%s%s", grp[u], grp[g], grp[o]);

	/* Set the left-most character according to the file's intrinsic type. */
	if(S_ISLNK(mode))
		buf[0] = 'l';
	else if(S_ISDIR(mode))
		buf[0] = 'd';
	else if(S_ISBLK(mode))
		buf[0] = 'b';
	else if(S_ISCHR(mode))
		buf[0] = 'c';
	else if(S_ISFIFO(mode))
		buf[0] = 'f';		/* Um, these two (FIFO and socket) are */
	else if(S_ISSOCK(mode))		/* guessed... */
		buf[0] = 's';

	/* This is magic until you understand how it works. The trick seems to be that one
	** bit (e.g. "SETUID") is displayed on top of another bit (in this case user read)
	** by changing that character either to 'S' (if it was not set) or 's' (if set).
	** AFAIK, this is not documented anywhere (except perhaps in ls's source).
	*/
	if(mode & S_ISVTX)		/* Sticky bit set? This is not POSIX... */
		buf[9] = (buf[9] == '-') ? 'T' : 't';
	if(mode & S_ISGID)		/* Set GID bit set? */
		buf[6] = (buf[6] == '-') ? 'S' : 's';
	if(mode & S_ISUID)		/* Set UID bit set? */
		buf[3] = (buf[3] == '-') ? 'S' : 's';

	return buf;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-10-07 -	Scan a string from <def>, and put a pointer to a dynamically allocated version
**		of it into <str>. The string should be delimited by double quotes. Characters
**		(such as commas and whitespace) between strings is ignored. Returns a pointer
**		to the beginning of the next string (suitable for a repeat call), or NULL when
**		no more strings were found.
** 1999-02-24 -	Added support for backslash escaping. Might be useful when this routine is used
**		to scan strings which are then parsed by the command argument stuff. Or, you
**		could just use single quotes of course...
*/
char * str_scan_string(char *def, char **str)
{
	GString	*tmp;

	if(def == NULL || str == NULL)
		return NULL;

	while(*def && *def != '"')
		def++;

	if(*def == '"')			/* Beginning of string actually found? */
	{
		def++;
		if((tmp = g_string_new(NULL)) != NULL)
		{
			while(*def && *def != '"')
			{
				if(*def == '\\')
				{
					def++;
					if(*def == '\0')
						break;
				}
				g_string_append_c(tmp, *def++);
			}
			if(*def == '"')		/* Closing quote here, too? */
			{
				*str = tmp->str;
				g_string_free(tmp, FALSE);				
				return ++def;	/* Then return with an OK status. */
			}
			g_string_free(tmp, TRUE);
		}
	}
	return NULL;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-10-09 -	I'm very tired of the compatibility problems caused by the strcasecmp()
**		function when trying to make gentoo a bit more portable, so I reimplement
**		it myself.
*/
int str_strcasecmp(const char *s, const char *t)
{
	if(s == NULL || t == NULL)
		return 0;

	for(; toupper(*s) == toupper(*t); s++, t++)
		if(*s == '\0')
			return 0;
	return toupper(*s) - toupper(*t);
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-02-24 -	Compute the length and content of the first word at <str>. Knows about
**		quoting and backslash escapes. Stores word at <store>. Will typically be
**		run twice on the same input, since you can't know how much space is going
**		to be needed without running it once (with store == NULL).
**		Quoting rules:	A word can contain whitespace (space, tab) only if quoted.
**				Double (") and single (') quotes can both be used, and have
**				the same "power". One quotes the other, so "'" and '"' are
**				both legal 1-character words. To include the quote used for
**				a word IN the word, it must be backslash escaped: "\"" is
**				the 1-character word ".
**		Returns pointer to first character after word, or NULL if there are no more
**		words. Stores word length at <len> (if non-NULL).
*/
char * str_word_length(char *str, int *len, char *store)
{
	char	quote = 0, here;
	int	l = 0;

	if(str == NULL)
		return NULL;

	while(*str && isspace((int) *str))		/* Skip inter-word spaces. */
		str++;

	if(*str == '\0')
		return NULL;
	for(; *str && !(quote == 0 && isspace((int) *str)); l++)
	{
		if((here = *str) == '\\')	/* Backslash escapade? */
		{
			here = *++str;
			if(here == '\0')	/* At end of string? */
				break;
		}
		else if(here == '\'' || here == '"')
		{
			if(quote == 0 || quote == here)	/* Ignore "other" quote. */
			{
				if(quote == here)
					quote = 0;
				else
					quote = here;
				str++;
				l--;			/* Don't count the quote. */
				continue;		/* Avoid storing the quote. */
			}
		}
		if(store != NULL)
			*store++ = here;
		str++;
	}
	if(quote != '\0' && *str == quote)	/* Skip ending quote. */
		str++;
	if(len != NULL)
		*len = l;

	return str;
}

/* 1999-02-24 -	This takes a string intended as a shell command and splits it into a word-
**		vector as used by exec() functions. Think argv[]. Handles some quoting and
**		escaped characters, too. The returned vector will be NULL-terminated, and
**		can be freed by a single call to g_free().
*/
gchar ** str_split_args(const char *argstring)
{
	gint	wlen, wtotlen, wnum, i;
	gchar	**argv, *ptr = (char *) argstring, *store;

	for(wnum = wlen = wtotlen = 0; (ptr = str_word_length(ptr, &wlen, NULL)) != NULL; wnum++, wtotlen += wlen + 1)
		;

	if(wnum == 0)		/* Nothing found? */
		return NULL;

	argv  = g_malloc((wnum + 1) * sizeof *argv + wtotlen);
	store = (gchar *) argv + (wnum + 1) * sizeof *argv;
	for(ptr = (gchar *) argstring, i = 0; (ptr = str_word_length(ptr, &wlen, store)) != NULL; i++)
	{
		argv[i] = store;
		*(store + wlen) = '\0';
		store += (wlen + 1);
	}
	argv[i] = NULL;

	return argv;
}
