/*
#ident	"@(#)smail/src:RELEASE-3_2_0_120:string.c,v 1.70 2004/07/24 15:55:07 woods Exp"
 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * string.c:
 *	miscellaneous string operations
 *
 *	external functions:  strcmpic, strncmpic, strip, strcolon,
 *			     is_string_in_list, strerror, strsysexit,
 *			     str_c_quote, str_printf, str_printf_va, xprintf,
 *			     dprintf, c_dequote, c_atol, base62, read_line,
 *			     str_cat, str_ncat, ivaltol, ltoival, copy, rcopy,
 *			     make_lib_fn
 */

#include "defs.h"

#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "smailarch.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "log.h"
#include "smailstring.h"
#include "dys.h"
#include "extern.h"
#include "exitcodes.h"
#include "debug.h"
#include "smailport.h"

/* functions local to this file */
static char *bltoa __P((unsigned int, unsigned long));


/*
 * str2lower - convert a string to lower case....
 */
char *
str2lower(s)
    register char *s;
{
    register char *sp;

    for (sp = s; *sp; sp++) {
	*sp = tolower((int) *sp);
    }

    return s;
}

/*
 * strcmpic - case independent strcmp function
 *
 * XXX rename and make optional to *BSD strcasecmp(3)
 */
int
strcmpic(s1, s2)
    register char *s1, *s2;		/* strings to be compared */
{
    register unsigned int c1, c2;	/* temp */

    /* Make sure that strings exist */
    if ((s1 == NULL) || (s2 == NULL))
	return ((s1 == s2) ? 0 : ((s2 == NULL) ? 1 : -1));

    while (*s1 && *s2) {
	if ((c1 = tolower((int) *s1++)) != (c2 = tolower((int) *s2++))) {
	    return (c1 - c2);
	}
    }

    /*
     * one or both chars must be `\0'.  If only one is `\0', then
     * the other string is longer.
     */
    return ((int) ((*s1) - (*s2)));
}

/*
 * strncmpic - case independent strcmp function
 *
 * XXX rename and make optional to *BSD strncasecmp(3)
 */
int
strncmpic(s1, s2, n)
    register char *s1, *s2;		/* strings to compare */
    size_t n;				/* compare up to this many chars */
{
    register unsigned int c1, c2;	/* temp */
    register size_t cnt = n;		/* count of chars so far compared */

    while (*s1 && *s2 && cnt > 0) {
	if ((c1 = tolower((int) *s1++)) != (c2 = tolower((int) *s2++))) {
	    return (c1 - c2);
	}
	cnt--;				/* count this character */
    }

    /*
     * If we ran out of chars, then the string segments are equal, otherwise
     * one or both strings must have ended. In this case the subtraction
     * will show which one is shorter, if any.
     */
    return (cnt ? ((int) ((*s1) - (*s2))) : 0);
}


/*
 * strip - destructively strip quotes _and_ escaped characters
 *
 * Returns TRUE if any change was made to storage pointed to by addr.
 */
int
strip(addr)
    register char *addr;		/* strip this address */
{
    int was_stripped = FALSE;		/* TRUE if any stripping was done */
    register char *p = addr;		/* write pointer to addr */
    register int c;			/* read char in addr */

    while ((c = *addr++)) {
	if (c == '\\') {		/* skip to char after \ */
	    *p++ = *addr++;
	    was_stripped = TRUE;
	} else if (c == '"') {		/* don't copy quote char */
	    was_stripped = TRUE;
	} else {
	    *p++ = c;
	}
    }
    *p++ = '\0';			/* end of string */

    return was_stripped;
}

/*
 * strcolon - step through string parts separated by colons
 *
 * when called with a string, return a copy of the first part of
 * the string up to, but excluding the first `:'.  When called with
 * NULL return a copy of the next part of the previously passed string,
 * with each part separated by a colon `:'.
 *
 * return NULL if no more parts are left.
 *
 * strcolon is typically used in a loop on ':' separated names such as:
 *
 *	for (p = strcolon(names); p; p = strcolon((char *)NULL)) {
 *	    ... do something with the name p ...
 *	}
 *
 * the storage returned is reused by the next call, so if you wish to keep a
 * string around, you will need to copy it.
 */
char *
strcolon(s)
    register char *s;			/* string or NULL */
{
    static char *next = NULL;		/* pointer to next ':' */
    static char *region = NULL;		/* region used to store result */
    static size_t alloc = 0;		/* alloc size of region */

    if (!s) {
	s = next;
	if (s == NULL) {
	    return NULL;
	}
    }
    next = strchr(s, ':');
    if (next) {
	register size_t len = next - s;

	if (len >= alloc) {
	    if (region == NULL) {
		region = xmalloc(alloc = len + 1);
	    } else {
		region = xrealloc(region, alloc = len + 1);
	    }
	}
	(void) memcpy(region, s, (size_t) (next - s));
	region[next - s] = '\0';
	next++;
	return region;
    }
    return s;
}

/*
 * is_string_in_list - return TRUE if string is in colon-separated list
 *
 * Case is not significant in comparisons.
 *
 * XXX might be nice to add ';' subfield handling ala match_ip()
 * (in which case this might best be moved to match.c)
 */
int
is_string_in_list(string, list)
    register char *string;		/* string to look for */
    char *list;				/* list of strings */
{
    register char *s;

    if (!string || !list) {
	return FALSE;
    }

    for (s = strcolon(list); s; s = strcolon((char *)NULL)) {
	if (EQIC(string, s)) {
	    return TRUE;
	}
    }

    return FALSE;
}

/*
 * is_number_in_list - return TRUE if number is in colon-separated list of
 *                     numbers, which may include ranges of numbers
 *
 * XXX should this function support negated items?  Some form of wildcard?
 *
 * XXX might be nice to add ';' subfield handling ala match_ip()
 * (in which case this might best be moved to match.c)
 */
int
is_number_in_list(number, list)
    unsigned int number;		/* number to look for */
    char *list;				/* list of numbers and number ranges */
{
    register char *s;

    if (!list) {
	return FALSE;
    }

    for (s = strcolon(list); s; s = strcolon((char *) NULL)) {
	char *range_sep = strchr(s, '-');

	/*
	 * Note: the way the check for valid ranges is done should avoid having
	 * to deal with atoi() returning a negative value....
	 */
	if (range_sep && (range_sep == s || !range_sep[1])) {
	    /* XXX broken range -- should return/log an error message! */
	    return FALSE;
	} else if (range_sep && range_sep != s && range_sep[1]) {
	    /* check if within range */
	    if ((unsigned int) atoi(s) <= number && number <= (unsigned int) atoi(range_sep + 1)) {
		return TRUE;
	    } else {
		return FALSE;
	    }
	} else if ((unsigned int) atoi(s) == number) {
	    return TRUE;
	}
    }

    return FALSE;
}

#if 0 /* not needed yet */
/*
 * is_user_in_list - return TRUE if username is in colon-separated list
 *
 * given a username value (which may be a C-style integer) and a colon
 * separated list of usernames, which may include C-style numbers, ranges of
 * numbers, and usernames, return TRUE if the given username is in the list,
 * else FALSE.
 */
int
is_user_in_list(user, list)
    register char *user;		/* user to look for */
    char *list;				/* list of users */
{
    register char *s;

    if (!user || !list) {
	return FALSE;
    }

    /* XXX convert user to user-ID */
    for (s = strcolon(list); s; s = strcolon((char *) NULL)) {
	/* XXX convert s to an ID and compare integer IDs */
	if (EQIC(user, s)) {
	    return TRUE;
	}
    }

    return FALSE;
}
#endif /* 0 */

#if 0 /* not needed yet */
/*
 * is_group_in_list - return TRUE if groupname is in colon-separated list
 *
 * given a groupname value (which may be a C-style integer) and a colon
 * separated list of groupnames, which may include C-style numbers, ranges of
 * numbers, and groupnames, return TRUE if the given groupname is in the list,
 * else FALSE.
 */
int
is_group_in_list(group, list)
    register char *group;		/* group to look for */
    char *list;				/* list of groups */
{
    register char *s;

    if (!group || !list) {
	return FALSE;
    }

    /* XXX convert group to group-ID */
    for (s = strcolon(list); s; s = strcolon((char *) NULL)) {
	/* XXX convert s to an ID and compare integer IDs */
	if (EQIC(group, s)) {
	    return TRUE;
	}
    }

    return FALSE;
}
#endif /* 0 */


/*
 * quote - quote the string pointed to by value, IFF required.
 *
 * If string requires quoting, then quote it using str_c_quote(), else return
 * a copy of the original string.
 *
 * A string "requires quoting" if it starts with a non-alphanumeric, or
 * contains any characters other than alphanumerics, periods, hyphens,
 * underscores, or "@" chars.
 *
 * If a string is already quoted then it is assumed all the right escape
 * sequences have been inserted as well.
 *
 * The storage pointed to by the return value is reused on subsequent calls.
 *
 * XXX this syntax handling is bogus -- we should have various syntax options
 * and perhaps factor out the "needs quote" and "is quoted" checks so that we
 * can safely quote identifiers (i.e. with all whitespace escaped), variable
 * values (as lists or not, etc.), as well as addresses.
 */
char * 
quote(value, dospaces, is_list)
    register char *value;
    int dospaces;
    int is_list;
{
    static struct str wkstr;
    static int initialised = FALSE;
    int quote_needed;
    register char *ptr;
    register int chr;

    if (!value) {
	return NULL;			/* callers expect NULL == NULL */
    }
    if (!initialised) {
	STR_INIT(&wkstr);
	initialised = TRUE;
    } else {
	STR_CLEAR(&wkstr);
	STR_CHECK(&wkstr);
    }
    /* empty strings are just empty quotes... */
    if (!*value) {
	STR_CAT(&wkstr, "\"\"");
	return STR(&wkstr);
    }

    /* Check if quoting is necessary */
    quote_needed = FALSE;
    for (ptr = value; (chr = *ptr); ptr++) {
	if (!((isalnum((int) chr)) ||
	      (ptr != value && ((chr == '_') ||
				(chr == '-') ||
				(chr == '.') ||
				(chr == '@'))))) {
	    quote_needed = TRUE;
	}
    }

    /*
     * Is it already quoted?
     *
     * Note we do this after the check above because we have to find the end of
     * the string anyway and doing the check is less expensive than walking the
     * string twice, especially since most strings we see will not already be
     * quoted.
     */
    if (*value == '"' && *(ptr - 1) == '"' && *(ptr - 2) != '\\') {
	/* XXX should check that all escaping was done properly */
	STR_CAT(&wkstr, value);
	return STR(&wkstr);
    }
  
    if (quote_needed) {
	STR_NEXT(&wkstr, '"');		/* Leading quotes */
    }
    str_c_quote(&wkstr, value, dospaces, is_list);
    if (quote_needed) {
	STR_NEXT(&wkstr, '"');		/* Trailing quotes */
    }
    STR_NEXT(&wkstr, '\0');		/* Trailing NUL */

    return STR(&wkstr);
}


/*
 * str_c_quote - return in the already initialized struct string pointed to by
 * wkstr a copy of value after escaping any backslashes or double quote
 * characters with a backslash, quoting non-space whitespace with with C-style
 * backslash character constants, and turning unprintable characters into octal
 * escapes.
 *
 * Also escapes spaces as '\s' if dospaces is TRUE.
 *
 * NOTE:  This function does not have an orthogonal API to that c_dequote()
 * despite its name.  This function operates on a whole string at a time,
 * whereas c_dequote() only processes a single escape sequence.
 */
void
str_c_quote(wkstr, value, dospaces, is_list)
    struct str *wkstr;
    register char *value;
    int dospaces;
    int is_list;
{
    int chr;
    int last_chr = '\0';
    int in_curly = 0;

    STR_CHECK(wkstr);

    while ((chr = *value++)) {
	switch (chr) {
	case ' ':
	    if (dospaces) {
		STR_NEXT(wkstr, '\\');
		STR_NEXT(wkstr, 's');
	    } else {
		    STR_NEXT(wkstr, chr);
	    }
	    break;
	case '{':
	    in_curly++;
	    STR_NEXT(wkstr, chr);
	    break;
	case '}':
	    in_curly--;
	    STR_NEXT(wkstr, chr);
	    break;
	case ':':
	    if (is_list && !in_curly && last_chr != '\\' && (*value != ':')) {
		STR_NEXT(wkstr, '\\');
		STR_NEXT(wkstr, '\n');
		STR_NEXT(wkstr, '\t');
	    }
	    STR_NEXT(wkstr, ':');
	    break;
	case '\\':
	case '"':
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, chr);
	    break;
	case BELL:
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, 'a');
	    break;
	case '\b':
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, 'b');
	    break;
	case '\f':
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, 'f');
	    break;
	case '\n':
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, 'n');
	    break;
	case '\r':
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, 'r');
	    break;
	case '\t':
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, 't');
	    break;
	case '\v':
	    STR_NEXT(wkstr, '\\');
	    STR_NEXT(wkstr, 'v');
	    break;
	default:
	    if (isprint((int) chr) && isascii((int) chr)) {
		STR_NEXT(wkstr, chr);
	    } else {
		STR_NEXT(wkstr, '\\');
		STR_NEXT(wkstr, (unsigned char) (((unsigned int) (unsigned char) chr >> 6) & 03) + '0');
		STR_NEXT(wkstr, (unsigned char) (((unsigned int) (unsigned char) chr >> 3) & 07) + '0');
		STR_NEXT(wkstr,                                                 (chr       & 07) + '0');
	    }
	    break;
	}
	last_chr = chr;
    }
}

/*
 * c_dequote - translate a \escape as C would within a string or char literal
 * with the addition that '\s' is a space character.
 *
 * Returns a pointer to the next character to fetch from p and the actual
 * character value in the integer storage pointed to by val.
 *
 * NOTE:  This function is not orthogonal to str_c_quote() despite its name as
 * it operates only on one character at a time.
 */
char *
c_dequote(p, val)
    register char *p;			/* start point in string */
    register int *val;			/* put result char here */
{
    register int c;			/* current character */
    int cnt;

    switch (c = *p++) {
    case '\0':
	*val = '\0';
	return p - 1;
    case 'a':
    case 'g':				/* non-standard BELL */
	*val = BELL;
	return p;
    case 'b':
	*val = '\b';
	return p;
    case 'e':
	*val = '\033';
	return p;
    case 'f':
	*val = '\f';
	return p;
    case 'n':
	*val = '\n';
	return p;
    case 'r':
	*val = '\r';
	return p;
    case 's':
	*val = ' ';
	return p;
    case 't':
	*val = '\t';
	return p;
    case 'v':
	*val = '\v';
	return p;
    case 'x':
    case 'X':
	/*
	 * x or X followed by up to three hex digits form a char
	 */
	cnt = 3;
	*val = 0;
	while (cnt && (isdigit((int) (c = *p++)) ||
		       (c >= 'A' && c <= 'F') ||
		       (c >= 'a' && c <= 'f')))
	{
	    *val = (*val * 16) +
		(isdigit((int) c)? c - '0': isupper((int) c)? c - 'A': c - 'a');
	}
	return p - 1;
    case '0': case '1': case '2': case '3':
    case '4': case '5': case '6': case '7':
	/* handle the normal, octal, case of chars specified numerically */
	cnt = 2;			/* two more digits, three total */
	*val = c - '0';
	while (cnt && (c = *p++) >= '0' && c <= '7') {
	    *val = (*val * 8) + c - '0';
	}
	return p - 1;
    default:
	*val = c;
	return p;
    }
}


/*
 * str_printf - highly simplified printf to a dynamic string
 * str_printf_va - ditto, but taking a va_list parameter
 *
 * note that of the standard expansion controls we only support %s, %d, %ld,
 * %o, %lo, %x, %lx and %c.
 *
 * However we also support:
 *
 * %z which inserts a null byte (and %N for backward compatibility)
 *
 * %S is akin to %.*s where a maximum length parameter is given before the
 * %string, but the length parameter is of type size_t, not int.
 *
 * %v and %V which are like %s and %S but also turn unprintable characters into
 * C-style quotes using str_c_quote(..., dospaces=FALSE, ...).
 *
 * %q and %Q are like %v and %S but use str_c_quote(..., dospaces=TRUE, ...).
 *
 * Zero padding and field adjustment, etc. are not supported.
 *
 * XXX implement "%a" for producing RFC 821 compliant addresses.
 */
#ifdef __STDC__
void
str_printf(struct str *sp, char *fmt, ...)
#else
/*VARARGS*/
void
str_printf(sp, fmt, va_alist)
    struct str *sp;			/* append to this string */
    char *fmt;				/* printf-style format string  */
    va_dcl
#endif
{
    va_list ap;				/* placeholder for varargs */

    VA_START(ap, fmt);
    str_printf_va(sp, fmt, ap);
    va_end(ap);
}

void
str_printf_va(sp, fmt, ap)
    register struct str *sp;		/* append to this string */
    register char *fmt;			/* printf-style format string */
    va_list ap;				/* placeholder for varargs */
{
    register int c;			/* current char in fmt */
    register int islong;                /* to handle %ld */
    unsigned long int un;		/* temp */
    long int n;				/* temp */
    char *s;				/* temp */
    size_t len;				/* for %S */
    char *ofmt = fmt;			/* original format string */

    /*
     * loop on the format string copying into the string sp
     */
    while ((c = *fmt++)) {
	if (c != '%') {
	    STR_NEXT(sp, c);
	} else {
	    if ((islong = (*fmt == 'l'))) {
		fmt++;
	    }
	    switch (c = *fmt++) {
	    case '\0':
		STR_NEXT(sp, '%');
		--fmt;
		break;
	    case 'v':
	    case 'q':
	    case 's':
		if ((s = va_arg(ap, char *))) {
		    if (c == 'v' || c == 'q') {
			str_c_quote(sp, s, ((c == 'q') ? TRUE : FALSE), FALSE);
		    } else {
			STR_CAT(sp, s);
		    }
		} else {
		    STR_CAT(sp, "(null)");
		}
		break;
	    case 'V':
	    case 'Q':
	    case 'S':			/* like printf("%.*s", ...), but param is size_t */
		len = va_arg(ap, size_t);
		s = va_arg(ap, char *);
		if (s) {
		    if (c == 'V' || c == 'Q') {
			char *cp = strncpy(xmalloc(len + 1), s, len);

			cp[len] = '\0';
			str_c_quote(sp, cp, ((c == 'Q') ? TRUE : FALSE), FALSE);
			xfree(cp);
		    } else {
			STR_NCAT(sp, s, len);
		    }
		} else {
		    STR_CAT(sp, "(empty)");
		}
		break;
	    case 'c':
		STR_NEXT(sp, va_arg(ap, int));
		break;
	    case 'o':
		un = islong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int);
		STR_CAT(sp, bltoa(8, un));
		break;
	    case 'x':
		un = islong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int);
		STR_CAT(sp, bltoa(16, un));
		break;
	    case 'u':
		un = islong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int);
		STR_CAT(sp, bltoa(10, un));
		break;
	    case 'd':
		n = islong ? va_arg(ap, long) : va_arg(ap, int);
		if (n < 0) {
		    STR_NEXT(sp, '-');
		    n = -n;
		}
		STR_CAT(sp, bltoa(10, (unsigned long) n));
		break;
	    case 'N':			/* how to insert a nul byte */
	    case 'z':
		STR_NEXT(sp, '\0');
		break;				/* FIXME: ???? */
	    case '%':
		STR_NEXT(sp, '%');
		break;
	    default:
		/* This is not terribly help */
		write_log(WRITE_LOG_PANIC, "str_printf_va(): invalid format specifier: '%%%c' in '%v'", c, ofmt);
		STR_NEXT(sp, '%');
		STR_NEXT(sp, c);
		/*
		 * too bad we don't know how many bytes to skip on the stack
		 * for this -- we're will probably dump core very soon if there
		 * are any more format specifiers, especially any that will
		 * dereference pointers, assuming this wasn't one....
		 */
		break;
	    }
	}
    }

    /*
     * add a trailing null, but make sure the next character will
     * overwrite it.
     */

    STR_NEXT(sp, '\0');
    STR_PREV(sp);
}

/*
 * bltoa - convert long integer to string representation in given base
 *
 * standard bug about pointing to static data.
 */
static char *
bltoa(base, n)
    register unsigned base;		/* base for conversion */
    register unsigned long n;		/* number to convert */
{
    static char buf[BITS_PER_INT + 1];	/* plenty big */
    register char *p = buf + sizeof(buf); /* start at end and move backward */
    register int i;

    *--p = '\0';			/* terminate string */
    if (n == 0) {
	/* special case, 0 is just "0" */
	*--p = '0';
	return p;
    }
    /* get more significant digits until no more are required */
    while (n > 0) {
	/* allow for bases up to 16 */
	i = (int) (n % base);
	n /= base;
	*--p = "0123456789abcdef"[i];
    }
    return p;
}

/*
 * xprintf - str_print to a region, returning pointer to xmalloc()ed region
 * that should be xfree()ed when finished with....
 */
#ifdef __STDC__
char *
xprintf(char *fmt, ...)
#else
/*VARARGS1*/
char *
xprintf(fmt, va_alist)
    char *fmt;				/* str_printf-style format string */
    va_dcl
#endif
{
    struct str str;
    va_list ap;

    STR_INIT(&str);
    VA_START(ap, fmt);
    str_printf_va(&str, fmt, ap);
    va_end(ap);
    STR_NEXT(&str, '\0');
    STR_DONE(&str);

    return STR(&str);
}

/*
 * dprintf - debugging printf to a file
 *
 * This allows the DEBUGx() macros to explicitly follow the convention that
 * printing a NULL string pointer displays "(null)".  The System V
 * printf() does not follow this convention, and thus cannot be used.
 */
#ifdef __STDC__
int
dprintf(FILE *file, char *fmt, ...)
#else
/* VARARGS2 */	/* XXX using two fixed args with va_list is potential trouble.... */
int
dprintf(file, fmt, va_alist)
    FILE *file;
    char *fmt;
    va_dcl
#endif
{
    static struct str str;
    static int inited = FALSE;
    va_list ap;

    if (! inited) {
	inited = TRUE;
	STR_INIT(&str);
    } else {
	STR_CHECK(&str);
	STR_CLEAR(&str);
    }
    VA_START(ap, fmt);
    str_printf_va(&str, fmt, ap);
    va_end(ap);
    STR_NEXT(&str, '\0');

    return fputs(STR(&str), file);
}

/*
 * c_atol - convert an ascii string to a long, C-style.
 *
 * Handle the forms  [+-]?0[0-7]* [+-]?0x[0-9a-f]* and [+-]?[1-9][0-9]*.
 * an optional suffix of one of the letters:  k, K, or M is allowed, as
 * a multplier by 1024 or 1048576.  If the string given is malformed,
 * error will be set to an error message.
 *
 * WARNING!!!  Does not detect overflow!
 */
long
c_atol(input, error)
    register char *input;		/* input string */
    char **error;			/* return error message here */
{
    char *input_string = input;
    long val = 0;
    int sign = 1;
    int digval;
    int base = 10;

    /* handle a leading sign character */
    if (*input == '-') {
	sign = -1;
	input++;
    } else if (*input == '+') {
	input++;
    }

    /* what is the base */
    if (*input == '0') {
	input++;
	base = 8;
	if (*input == 'x' || *input == 'X') {
	    base = 16;
	    input++;
	}
    }

    /* decode the number */
    while (*input) {
	if (isdigit((int) *input)) {
	    digval = *input++ - '0';
	} else {
	    if (islower((int) *input)) {
		digval = *input++ - 'a' + 10;
	    } else if (isupper((int) *input)) {
		digval = *input++ - 'A' + 10;
	    } else {
		break;
	    }
	}
	if (digval >= base) {
	    --input;
	    break;
	}
	val = val*base + digval;
    }

    /* set the correct sign */
    val *= sign;

    /* is there an optional multiplier? */
    if (*input == 'k' || *input == 'K') {
	input++;
	val *= 1024;
    }
    if (input && *input == 'M') {
	input++;
	val *= 1024 * 1024;
    }

    /* there should be nothing left of the input string at this point */
    if (error && *input) {
	*error = xprintf("invalid number: %s", input_string);
    } else if (error)
	*error = NULL;
    return val;
}

/*
 * base62 - convert a long integer into ASCII base 62
 *
 * uses upper and lowercase letters, plus numbers.
 * always returns a string of exactly 6 characters, plus a null byte.
 *
 * returns a static area which is reused for each call.
 *
 * XXX should be merged into bltoa()
 */
char *
base62(val)
    unsigned long val;
{
    static char base62_chars[] =
	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    static char buf[7];
    register char *p = buf + sizeof(buf) - 1;

    *p = '\0';
    while (p > buf) {
	*--p = base62_chars[val % 62];
	val /= 62;
    }

    return buf;
}

/*
 * read_line - read a line from an input stream and return a pointer to it
 *
 * returns a storage area which may be reused for subsequent calls.
 *
 * returns NULL after EOF has been reached and nothing more can be read.
 */
char *
read_line(f)
    register FILE *f;
{
    static int inited = FALSE;
    static struct str line;
    register int c;

    if (! inited) {
	inited = TRUE;
	STR_INIT(&line);
    } else {
	STR_CHECK(&line);
	STR_CLEAR(&line);
    }
    while ((c = getc(f)) != EOF) {
	STR_NEXT(&line, c);
	if (c == '\n') {
	    break;
	}
    }
    if (STR_LEN(&line) == 0) {
	return NULL;
    }
    STR_NEXT(&line, '\0');
    return STR(&line);
}

/*
 * str_cat - concatenate a C string onto the end of a dynamic string (thus
 *	     NUL-terminating it), but leave the NUL un-accounted for in the
 *	     dynamic string's current length so that futher str_cat(),
 *	     str_ncat(), or STR_NEXT(), etc. operations simply overwrite it.
 *
 * str_ncat - concatenate a C string with a given length onto the
 *	      end of a dynamic string
 *
 * Grow the storage region as necessary.
 */

void
str_cat(sp, s)
    register struct str *sp;
    const char *s;
{
    str_ncat(sp, s, (size_t) strlen(s) + 1);
    STR_PREV(sp);
}

void
str_ncat(sp, s, n)
    register struct str *sp;
    const char *s;
    size_t n;
{
    if (STR_LEN(sp) + n > STR_ALLOCSZ(sp)) {
	/*
	 * need to expand the region:
	 * bump up to a sufficiently large region which is a multiple
	 * of STR_BUMP (except for a pointer).  Allow at least 10 free
	 * chars in the region, for future expansion.
	 *
	 * XXX why the removal of a pointer's worth?
	 */
	STR_ALLOCSZ(sp) = ((STR_LEN(sp) + n + STR_BUMP + 10) & (size_t) (~(STR_BUMP - 1))) - sizeof(char *);
	STR(sp) = xrealloc(STR(sp), STR_ALLOCSZ(sp));
    }
    /* copy string to the end of the region, copy any NUL byte(s) too */
    (void) memcpy(STR(sp) + STR_LEN(sp), s, n);
    STR_LEN(sp) += n;
}

/*
 * ivaltol - convert time interval string into a long integer
 *
 * Take a string defining an interval and convert it into seconds.  An
 * interval is the sum of seconds desribed by terms formed from a
 * decimal integer and a suffix.  The suffix gives a multiplier for
 * the term and can be one of 'y' (years), 'w' (weeks), 'd' (days),
 * 'h' (hours), 'm' (minutes) or 's' (seconds).  If the last term does
 * not have a suffix, 's' is assumed.  For example, an interval string
 * of "1h10m3s" would represent one hour, ten minutes and 30 seconds
 * and would be converted to the equivalent number of seconds.
 *
 * A negative integer is returned for illegal intervals.
 *
 * Interval multipliers may be upper- or lower-case and may be spelled out in
 * full although only the first character is used.
 *
 * No whitespace is allowed.
 *
 * Note:  interval values are often only useful if they fit within an
 *	  unsigned integer, so callers of this routine should do range
 *	  checking to make sure the value returned is not too large.
 *
 * XXX does not validate format or detect errors.
 *
 * XXX Once returned a long int, thus the name
 */
time_t
ivaltol(s)
    register char *s;			/* string containing queue interval */
{
    long ret = 0;			/* return value */
    long cur = 0;			/* value of current part */

    while (*s) {
	switch (*s++) {
	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
	    if (cur >= ((LONG_MAX - ret) / 10)) {
		return -1L;
	    }
	    cur = cur * 10 + s[-1] - '0';
	    break;

	case 'y':			/* years = 365.24 days */
	case 'Y':
	    if (cur >= ((LONG_MAX - ret) / ((60*60*24*365)+1))) {
		return -1L;
	    }
	    ret += (cur * 60*60*24*365) + (cur * 60*60*24*24)/100;
	    cur = 0;
	    while (isalpha(*s)) {
		s++;
	    }
	    break;

	case 'w':			/* weeks */
	case 'W':
	    if (cur >= ((LONG_MAX - ret) / (60*60*24*7))) {
		return -1L;
	    }
	    ret += cur * 60*60*24*7;
	    cur = 0;
	    while (isalpha(*s)) {
		s++;
	    }
	    break;

	case 'd':			/* days */
	case 'D':
	    if (cur >= ((LONG_MAX - ret) / (60*60*24))) {
		return -1L;
	    }
	    ret += cur * 60*60*24;
	    cur = 0;
	    while (isalpha(*s)) {
		s++;
	    }
	    break;

	case 'h':			/* hours */
	case 'H':
	    if (cur >= ((LONG_MAX - ret) / (60*60))) {
		return -1L;
	    }
	    ret += cur * 60*60;
	    cur = 0;
	    while (isalpha(*s)) {
		s++;
	    }
	    break;

	case 'm':			/* minutes */
	case 'M':
	    if (cur >= ((LONG_MAX - ret) / 60)) {
		return -1L;
	    }
	    ret += cur * 60;
	    cur = 0;
	    while (isalpha(*s)) {
		s++;
	    }
	    break;

	case 's':			/* seconds */
	case 'S':
	    if (cur > (LONG_MAX - ret)) {
		return -1L;
	    }
	    ret += cur;
	    cur = 0;
	    while (isalpha(*s)) {
		s++;
	    }
	    break;

	default:
	    return -1L;			/* illegal modifier */
	}
    }
    if ((ret + cur) > LONG_MAX) {
	return -1L;
    }

    return (ret + cur);			/* all done */
}

/*
 * ltoival - convert long integer into a more human-friendly time interval string
 *
 * Break down an interval into weeks/days/hours/minutes/seconds,
 * expressing it as it might have been input to ivaltol.
 *
 * XXX once accepted a long int, thus its name....
 */
char *
ltoival(n)
    time_t n;
{
    static char ret[BUFSIZ];		/* XXX for 64-bit: "292471208677yearsNNweeksNNdaysNNhoursNNminutesNNseconds" */
    static char *units[] = {		/* subinterval names */
	"second",
	"minute",
	"hour",
	"day",
	"week"
    };
    long vals[(sizeof(units) / sizeof(*units))];/* values of each subinterval */
    int i;					/* will go negative */

    if (n < 0) {
	return NULL;
    }
    if (n == 0) {
	return "0";
    }

    vals[0] = (n % 60);				/* 's'econd(s) */
    n /= 60;
    vals[1] = (n % 60);				/* 'm'minute(s) */
    n /= 60;
    vals[2] = (n % 24);				/* 'h'hour(s) */
    n /= 24;
    vals[3] = (n % 7);				/* 'd'day(s) */
    n /= 7;
    vals[4] = n;				/* 'w'eek(s) */

    ret[0] = '\0';
    for (i = (int) (sizeof(units) / sizeof(*units)) - 1; i >= 0; --i) {
	if (vals[i]) {
	    sprintf(ret + strlen(ret), "%ld%s%s", vals[i], units[i], (vals[i] > 1) ? "s" : "");
	}
    }

    return ret;
}

/*
 * copy - copy a NUL-terminated string
 */

char *
copy(s)
    char *s;
{
    return rcopy(s, s + strlen(s));
}

/*
 * rcopy - copy a string from start to end+1, creating a NUL-terminated result.
 */
char *
rcopy(s, e)
    char *s, *e;
{
    char *p;
    size_t n;

    n = (e - s);
    p = xmalloc(n + 1);
    (void) memcpy(p, s, n);
    p[n] = '\0';
    return p;
}

/*
 * make_lib_fn - build a filename relative to the smail lib directory
 *
 * If the filename begins with '/' or is "-" return a verbatim copy of it.
 *
 * Return NULL if smail_lib_dir is undefined, or if the given
 * filename is NULL.
 */
char *
make_lib_fn(fn)
    char *fn;				/* filename to expand */
{
    if (fn == NULL) {
	return NULL;
    }
    if (fn[0] == '/' || (fn[0] == '-' && fn[1] == '\0')) {
	return COPY_STRING(fn);
    }
    if (smail_lib_dir == NULL) {
	return NULL;
    }
    return xprintf("%s/%s", smail_lib_dir, fn);
}


/*
 * make_util_fn - build a filename relative to the smail util directory
 *
 * If the filename begins with '/' or is "-" return a verbatim copy of it.
 *
 * Return NULL if smail_util_dir is undefined, or if the given
 * filename is NULL.
 */
char *
make_util_fn(fn)
    char *fn;				/* filename to expand */
{
    if (fn == NULL) {
	return NULL;
    }
    if (fn[0] == '/' || (fn[0] == '-' && fn[1] == '\0')) {
	return COPY_STRING(fn);
    }
    if (smail_util_dir == NULL) {
	return NULL;
    }
    return xprintf("%s/%s", smail_util_dir, fn);
}
/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */
