/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc.  Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * 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; version 2
 * of the License.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * END COPYRIGHT BLOCK **/
/*
 * util.c: A hodge podge of utility functions and standard functions which 
 *         are unavailable on certain systems
 * 
 * Rob McCool
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>

#include <prtypes.h>

#ifdef XP_UNIX
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include "prthread.h"
#endif /* XP_UNIX */

#include "base/util.h"

#ifdef XP_UNIX
#include <sys/types.h>
#endif /* WIN32 */

#ifdef XP_WIN32
#define  VC_EXTRALEAN         //  Exclude rarely-used stuff from afxwin.h
#include <afxwin.h>      // MFC core and standard components`
#include <winnls.h>
#endif

#ifdef _WIN32
static char *win_char_converter(const char *instr, int bFromUTF8);
#else
#include <locale.h>
#endif
 
#ifndef _WIN32
#include <iconv.h>
#include <langinfo.h>   /* for nl_langinfo() */
#endif

#if defined(_HPUX_SOURCE)
#define UTIL_CHARSET_UTF8           "utf8"          /* HP/UX */
#else
#define UTIL_CHARSET_UTF8           "UTF-8"         /* all others */
#endif

#if defined(_HPUX_SOURCE)
#define UTIL_CHARSET_DEFAULT        "roma8"         /* HP/UX */
#elif defined(__GLIBC__)
#define UTIL_CHARSET_DEFAULT        "US-ASCII"      /* glibc, e.g. Linux */
#else
#define UTIL_CHARSET_DEFAULT        "646"           /* all others */
#endif

#ifdef __cplusplus
extern "C" {
#endif

static char *convertor( const char *src, int fromUtf8 );
#ifndef _WIN32
static const char *GetCurrentCharset(void);
#endif

#ifdef __cplusplus
}
#endif

/* ----------------------------- util_getline ----------------------------- */

#define LF 10
#define CR 13

NSAPI_PUBLIC int util_getline(filebuf_t *buf, int lineno, int maxlen, char *l) {
    int i, x;

    x = 0;
    while(1) {
        switch(i = (int)filebuf_getc(buf)) {
          case IO_EOF:
            l[x] = '\0';
            return 1;
          case LF:
            if(x && (l[x-1] == '\\')) {
                --x;
                continue;
            }
            l[x] = '\0';
            return 0;
          case IO_ERROR:
            util_sprintf(l, "I/O error reading file at line %d", lineno);
            return -1;
          case CR:
            continue;
          default:
            l[x] = (char) i;
            if(++x == maxlen) {
                util_sprintf(l, "line %d is too long", lineno);
                return -1;
            }
            break;
        }
    }
}


/* ---------------------------- util_can_exec ----------------------------- */

#ifdef XP_UNIX
NSAPI_PUBLIC int util_can_exec(struct stat *fi, uid_t uid, gid_t gid) 
{
    if(!uid)
       return 1;
    if((fi->st_mode & S_IXOTH) || 
       ((gid == fi->st_gid) && (fi->st_mode & S_IXGRP)) ||
       ((uid == fi->st_uid) && (fi->st_mode & S_IXUSR)))
        return 1;
    return 0;
}
#endif /* XP_UNIX */


/* --------------------------- util_env_create ---------------------------- */


NSAPI_PUBLIC char **util_env_create(char **env, int n, int *pos)
{
    int x;

    if(!env) {
        *pos = 0;
        return (char **) MALLOC((n + 1)*sizeof(char *));
    }
    else {
        for(x = 0; (env[x]); x++);
        env = (char **) REALLOC(env, (n + x + 1)*(sizeof(char *)));
        *pos = x;
        return env;
    }
}


/* ---------------------------- util_env_free ----------------------------- */


NSAPI_PUBLIC void util_env_free(char **env)
{
    register char **ep = env;

    for(ep = env; *ep; ep++)
        FREE(*ep);
    FREE(env);
}

/* ----------------------------- util_env_str ----------------------------- */


NSAPI_PUBLIC char *util_env_str(char *name, char *value) {
    char *t,*tp;

    t = (char *) MALLOC(strlen(name)+strlen(value)+2); /* 2: '=' and '\0' */

    for(tp=t; (*tp = *name); tp++,name++);
    for(*tp++ = '='; (*tp = *value); tp++,value++);
    return t;
}


/* --------------------------- util_env_replace --------------------------- */


NSAPI_PUBLIC void util_env_replace(char **env, char *name, char *value)
{
    int x, y, z;
    char *i;

    for(x = 0; env[x]; x++) {
        i = strchr(env[x], '=');
        *i = '\0';
        if(!strcmp(env[x], name)) {
            y = strlen(env[x]);
            z = strlen(value);

            env[x] = (char *) REALLOC(env[x], y + z + 2);
            util_sprintf(&env[x][y], "=%s", value);
            return;
        }
        *i = '=';
    }
}


/* ---------------------------- util_env_find ----------------------------- */


NSAPI_PUBLIC char *util_env_find(char **env, char *name)
{
    char *i;
    int x, r;

    for(x = 0; env[x]; x++) {
        i = strchr(env[x], '=');
        *i = '\0';
        r = !strcmp(env[x], name);
        *i = '=';
        if(r)
            return i + 1;
    }
    return NULL;
}


/* ---------------------------- util_env_copy ----------------------------- */


NSAPI_PUBLIC char **util_env_copy(char **src, char **dst)
{
    char **src_ptr;
    int src_cnt;
    int index;

    if (!src)
        return NULL;

    for (src_cnt = 0, src_ptr = src; *src_ptr; src_ptr++, src_cnt++);

    if (!src_cnt)
        return NULL;

    dst = util_env_create(dst, src_cnt, &index);

    for (src_ptr = src, index=0; *src_ptr; index++, src_ptr++)
        dst[index] = STRDUP(*src_ptr);
    dst[index] = NULL;
    
    return dst;
}

/* ---------------------------- util_hostname ----------------------------- */


/*
 * MOVED TO NET.C TO AVOID INTERDEPENDENCIES
 */


/* --------------------------- util_chdir2path ---------------------------- */


NSAPI_PUBLIC int util_chdir2path(char *path) 
{  
	/* use FILE_PATHSEP to accomodate WIN32 */
    char *t = strrchr(path, FILE_PATHSEP);
    int ret;

    if(!t)
        return -1;

    *t = '\0';
#ifdef XP_UNIX
    ret = chdir(path);
#else /* WIN32 */
	ret = SetCurrentDirectory(path);
#endif /* XP_UNIX */

	/* use FILE_PATHSEP instead of chdir to accomodate WIN32 */
    *t = FILE_PATHSEP;

    return ret;
}


/* --------------------------- util_is_mozilla ---------------------------- */


NSAPI_PUBLIC int util_is_mozilla(char *ua, char *major, char *minor)
{
    if((!ua) || strncasecmp(ua, "Mozilla/", 8))
        return 0;

    /* Major version. I punted on supporting versions like 10.0 */
    if(ua[8] > major[0])
        return 1;
    else if((ua[8] < major[0]) || (ua[9] != '.'))
        return 0;

    /* Minor version. Support version numbers like 0.96 */
    if(ua[10] < minor[0])
        return 0;
    else if((ua[10] > minor[0]) || (!minor[1]))
        return 1;

    if((!isdigit(ua[11])) || (ua[11] < minor[1]))
        return 0;
    else
        return 1;
}


/* ----------------------------- util_is_url ------------------------------ */


#include <ctype.h>     /* isalpha */

NSAPI_PUBLIC int util_is_url(char *url)
{
    char *t = url;

    while(*t) {
        if(*t == ':')
            return 1;
        if(!isalpha(*t))
            return 0;
        ++t;
    }
    return 0;
}


/* --------------------------- util_later_than ---------------------------- */


int _mstr2num(char *str) {
    if(!strcasecmp(str, "Jan")) return 0;
    if(!strcasecmp(str, "Feb")) return 1;
    if(!strcasecmp(str, "Mar")) return 2;
    if(!strcasecmp(str, "Apr")) return 3;
    if(!strcasecmp(str, "May")) return 4;
    if(!strcasecmp(str, "Jun")) return 5;
    if(!strcasecmp(str, "Jul")) return 6;
    if(!strcasecmp(str, "Aug")) return 7;
    if(!strcasecmp(str, "Sep")) return 8;
    if(!strcasecmp(str, "Oct")) return 9;
    if(!strcasecmp(str, "Nov")) return 10;
    if(!strcasecmp(str, "Dec")) return 11;
    return -1;
}

int _time_compare(struct tm *lms, char *ims, int later_than_op)
{
    int y = 0, mnum = 0, d = 0, h = 0, m = 0, s = 0, x;
    char t[128];

    /* Supported formats start with weekday (which we don't care about) */
    /* The sizeof(t) is to avoid buffer overflow with t */
    if((!(ims = strchr(ims,' '))) || (strlen(ims) > (sizeof(t) - 2)))
        return 0;

    while(*ims && isspace(*ims)) ++ims;
    if((!(*ims)) || (strlen(ims) < 2))
        return 0;

    /* Standard HTTP (RFC 850) starts with dd-mon-yy */
    if(ims[2] == '-') {
        /* Warning - hardcoded 128 is sizeof(t) - scanf is not security conscious */
        sscanf(ims, "%128s %d:%d:%d", t, &h, &m, &s);
        t[sizeof(t)-1] = 0;
        if(strlen(t) < 6)
            return 0;
        t[2] = '\0';
        t[6] = '\0';
        d = atoi(t);
        mnum = _mstr2num(&t[3]);
        x = atoi(&t[7]);
        /* Postpone wraparound until 2070 */
        y = x + (x < 70 ? 2000 : 1900);
    }
    /* The ctime format starts with a month name */
    else if(isalpha(*ims)) {
        /* Warning - hardcoded 128 is sizeof(t) - scanf is not security conscious */
        sscanf(ims,"%128s %d %d:%d:%d %*s %d", t, &d, &h, &m, &s, &y);
        t[sizeof(t)-1] = 0;
        mnum = _mstr2num(t);
    }
    /* RFC 822 */
    else {
        /* Warning - hardcoded 128 is sizeof(t) - scanf is not security conscious */
        sscanf(ims, "%d %128s %d %d:%d:%d", &d, t, &y, &h, &m, &s);
        t[sizeof(t)-1] = 0;
        mnum = _mstr2num(t);
    }

    if (later_than_op) {
	if( (x = (1900 + lms->tm_year) - y) )
	    return x < 0;

	if(mnum == -1)
	    return 0;

	/* XXXMB - this will fail if you check if december 31 1996 is later
	 * than january 1 1997
	 */
	if((x = lms->tm_mon - mnum) || (x = lms->tm_mday - d) || 
	   (x = lms->tm_hour - h) || (x = lms->tm_min - m) || 
	   (x = lms->tm_sec - s))
	  return x < 0;

	return 1;
    }
    else {
	return (mnum != -1 &&
		1900 + lms->tm_year == y    &&
		lms->tm_mon         == mnum &&
		lms->tm_mday        == d    &&
		lms->tm_hour        == h    &&
		lms->tm_min         == m    &&
		lms->tm_sec         == s);
    }
}


/* Returns 0 if lms later than ims
 * Returns 1 if equal
 * Returns 1 if ims later than lms
 */
NSAPI_PUBLIC int util_later_than(struct tm *lms, char *ims)
{
    return _time_compare(lms, ims, 1);
}


NSAPI_PUBLIC int util_time_equal(struct tm *lms, char *ims)
{
    return _time_compare(lms, ims, 0);
}

/* util_str_time_equal()
 *
 * Function to compare if two time strings are equal
 *
 * Acceptible date formats:
 *      Saturday, 17-Feb-96 19:41:34 GMT        <RFC850>
 *      Sat, 17 Mar 1996 19:41:34 GMT           <RFC1123>
 *
 * Argument t1 MUST be RFC1123 format.
 *
 * Note- it is not the intention of this routine to *always* match
 *       There are cases where we would return != when the strings might
 *       be equal (especially with case).  The converse should not be true.
 *
 * Return 0 if equal, -1 if not equal.
 */
#define MINIMUM_LENGTH  18
#define RFC1123_DAY      5
#define RFC1123_MONTH    8
#define RFC1123_YEAR     12
#define RFC1123_HOUR     17
#define RFC1123_MINUTE   20
#define RFC1123_SECOND   23
NSAPI_PUBLIC int util_str_time_equal(char *t1, char *t2)
{
    int index;

    /* skip over leading whitespace... */
    while(*t1 && isspace(*t1)) ++t1;
    while(*t2 && isspace(*t2)) ++t2;

    /* Check weekday */
    if ( (t1[0] != t2[0]) || (t1[1] != t2[1]) )
        return -1;

    /* Skip to date */
    while(*t2 && !isspace(*t2)) ++t2;
    t2++;

    /* skip if not strings not long enough */
    if ( (strlen(t1) < MINIMUM_LENGTH) || (strlen(t2) < MINIMUM_LENGTH) )
        return -1;

    if ( (t1[RFC1123_DAY] != t2[0]) || (t1[RFC1123_DAY+1] != t2[1]) )
        return -1;

    /* Skip to the month */
    t2 += 3;

    if ( (t1[RFC1123_MONTH] != t2[0]) || (t1[RFC1123_MONTH+1] != t2[1]) ||
        (t1[RFC1123_MONTH+2] != t2[2]) )
        return -1;

    /* Skip to year */
    t2 += 4;

    if ( (t1[RFC1123_YEAR] != t2[0]) ) {
        /* Assume t2 is RFC 850 format */
        if ( (t1[RFC1123_YEAR+2] != t2[0]) || (t1[RFC1123_YEAR+3] != t2[1]) )
            return -1;

        /* skip to hour */
        t2 += 3;
    } else {
        /* Assume t2 is RFC 1123 format */
        if ( (t1[RFC1123_YEAR+1] != t2[1]) || (t1[RFC1123_YEAR+2] != t2[2]) ||
            (t1[RFC1123_YEAR+3] != t2[3]) )
            return -1;

        /* skip to hour */
        t2 += 5;
    }

    /* check date */
    for (index=0; index<8; index++) {
        if ( t1[RFC1123_HOUR+index] != t2[index] )
            return -1;
    }

    /* Ignore timezone */

    return 0;
}


/* ---------------------------- util_uri_parse ---------------------------- */

NSAPI_PUBLIC void util_uri_parse(char *uri)  
{
    int spos = 0, tpos = 0;
    int l = strlen(uri);

    while(uri[spos])  {
        if(uri[spos] == '/')  {
            if((spos != l) && (uri[spos+1] == '.'))  {
                if(uri[spos+2] == '/')
                    spos += 2;
                else
                    if((spos <= (l-3)) && 
                       (uri[spos+2] == '.') && (uri[spos+3] == '/'))  {
                        spos += 3;
                        while((tpos > 0) && (uri[--tpos] != '/'))    
                            uri[tpos] = '\0';
                    }  else
                        uri[tpos++] = uri[spos++];
            }  else  {
                if(uri[spos+1] != '/')
                    uri[tpos++] = uri[spos++];
                else 
                    spos++;
            }
        }  else
            uri[tpos++] = uri[spos++];
    }
    uri[tpos] = '\0';
}


/* -------------------- util_uri_unescape_and_normalize -------------------- */

#ifdef XP_WIN32
/* The server calls this function to unescape the URI and also normalize 
 * the uri.  Normalizing the uri converts all "\" characters in the URI
 * and pathinfo portion to "/".  Does not touch "\" in query strings.
 */
void util_uri_unescape_and_normalize(char *s)
{
    char *t, *u;

    for(t = s, u = s; *t; ++t, ++u) {
        if((*t == '%') && t[1] && t[2]) {
            *u = ((t[1] >= 'A' ? ((t[1] & 0xdf) - 'A')+10 : (t[1] - '0'))*16) +
                  (t[2] >= 'A' ? ((t[2] & 0xdf) - 'A')+10 : (t[2] - '0'));
            t += 2;
        }
        else
            if(u != t)
                *u = *t;
        if (*u == '\\')		/* normalize */
            *u = '/';
    }
    *u = *t;
}
#endif /* XP_WIN32 */

/* -------------------------- util_uri_unescape --------------------------- */

NSAPI_PUBLIC void util_uri_unescape(char *s)
{
    char *t, *u;

    for(t = s, u = s; *t; ++t, ++u) {
        if((*t == '%') && t[1] && t[2]) {
            *u = ((t[1] >= 'A' ? ((t[1] & 0xdf) - 'A')+10 : (t[1] - '0'))*16) +
                  (t[2] >= 'A' ? ((t[2] & 0xdf) - 'A')+10 : (t[2] - '0'));
            t += 2;
        }
        else
            if(u != t)
                *u = *t;
    }
    *u = *t;
}


/* --------------------------- util_uri_escape ---------------------------- */


NSAPI_PUBLIC char *util_uri_escape(char *od, char *s)
{
    char *d;

    if(!od)
        od = (char *) MALLOC((strlen(s)*3) + 1);
    d = od;

    while(*s) {
        if(strchr("% ?#:+&*\"<>\r\n", *s)) {
            sprintf(d, "%%%2x", *s);
            ++s; d += 3;
        }
        else
            *d++ = *s++;
    }
    *d = '\0';
    return od;
}


/* --------------------------- util_url_escape ---------------------------- */


NSAPI_PUBLIC char *util_url_escape(char *od, char *s)
{
    char *d;

    if(!od)
        od = (char *) MALLOC((strlen(s)*3) + 1);
    d = od;

    while(*s) {
        if(strchr("% +*\"<>\r\n", *s)) {
            sprintf(d, "%%%.2x", *s);
            ++s; d += 3;
        }
        else
            *d++ = *s++;
    }
    *d = '\0';
    return od;
}


/* ------------------------- util_mime_separator -------------------------- */


NSAPI_PUBLIC int util_mime_separator(char *sep)
{
    srand(time(NULL));
    return util_sprintf(sep, "%c%c--%d%d%d", CR, LF, rand(), rand(), rand());
}


/* ------------------------------ util_itoa ------------------------------- */


/* 
 * Assumption: Reversing the digits will be faster in the general case 
 * than doing a log10 or some nasty trick to find the # of digits.
 */

NSAPI_PUBLIC int util_itoa(int i, char *a)
{
    register int x, y, p;
    register char c;
    int negative;

    negative = 0;
    if(i < 0) {
        *a++ = '-';
        negative = 1;
        i = -i;
    }
    p = 0;
    while(i > 9) {
        a[p++] = (i%10) + '0';
        i /= 10;
    }
    a[p++] = i + '0';

    if(p > 1) {
        for(x = 0, y = p - 1; x < y; ++x, --y) {
            c = a[x];
            a[x] = a[y];
            a[y] = c;
        }
    }
    a[p] = '\0';
    return p + negative;
}


/* ----------------------------- util_sprintf ----------------------------- */


#include "prprf.h"

/* 
   XXXrobm the NSPR interfaces don't allow me to just pass in a buffer 
   without a size
 */
#define UTIL_PRF_MAXSIZE 1048576

NSAPI_PUBLIC int util_vsnprintf(char *s, int n, register const char *fmt, 
                                va_list args)
{
    return PR_vsnprintf(s, n, fmt, args);
}

NSAPI_PUBLIC int util_snprintf(char *s, int n, const char *fmt, ...)
{
    int rc;
    va_list args;
    va_start(args, fmt);
    rc = PR_vsnprintf(s, n, fmt, args);
    va_end(args);
    return rc;
}

NSAPI_PUBLIC int util_vsprintf(char *s, register const char *fmt, va_list args)
{
    return PR_vsnprintf(s, UTIL_PRF_MAXSIZE, fmt, args);
}

NSAPI_PUBLIC int util_sprintf(char *s, const char *fmt, ...)
{
    int rc;
    va_list args;
    va_start(args, fmt);
    rc = PR_vsnprintf(s, UTIL_PRF_MAXSIZE, fmt, args);
    va_end(args);
    return rc;
}

/* ---------------------------- util_sh_escape ---------------------------- */


NSAPI_PUBLIC char *util_sh_escape(char *s)
{
    char *ns = (char *) MALLOC(strlen(s) * 2 + 1);   /* worst case */
    register char *t, *u;

    for(t = s, u = ns; *t; ++t, ++u) {
        if(strchr("&;`'\"|*?~<>^()[]{}$\\ #!", *t))
            *u++ = '\\';
        *u = *t;
    }
    *u = '\0';
    return ns;
}

/* --------------------------- util_strcasecmp ---------------------------- */


#ifdef NEED_STRCASECMP
/* These are stolen from mcom/lib/xp */
NSAPI_PUBLIC 
int util_strcasecmp(CASECMPARG_T char *one, CASECMPARG_T char *two)
{
    CASECMPARG_T char *pA;
    CASECMPARG_T char *pB;

    for(pA=one, pB=two; *pA && *pB; pA++, pB++)
      {
        int tmp = tolower(*pA) - tolower(*pB);
        if (tmp)
            return tmp;
      }
    if (*pA)
        return 1;
    if (*pB)
        return -1;
    return 0;
}
#endif /* NEED_STRCASECMP */

#ifdef NEED_STRNCASECMP
NSAPI_PUBLIC 
int util_strncasecmp(CASECMPARG_T char *one, CASECMPARG_T char *two, int n)
{
    CASECMPARG_T char *pA;
    CASECMPARG_T char *pB;

    for(pA=one, pB=two;; pA++, pB++)
      {
        int tmp;
        if (pA == one+n)
            return 0;
        if (!(*pA && *pB))
            return *pA - *pB;
        tmp = tolower(*pA) - tolower(*pB);
        if (tmp)
            return tmp;
      }
}
#endif /* NEED_STRNCASECMP */


#ifdef XP_UNIX
/*
 * Local Thread Safe version of waitpid.  This prevents the process
 * from blocking in the system call.
 */
NSAPI_PUBLIC pid_t 
util_waitpid(pid_t pid, int *statptr, int options)
{
    pid_t rv;

    for(rv = 0; !rv; PR_Sleep(500)) {
	rv = waitpid(pid, statptr, options | WNOHANG);
	if (rv == -1) {
	    if (errno == EINTR) {
		rv = 0; /* sleep and try again */
	    } else {
//		ereport(LOG_WARN, "waitpid failed for pid %d:%s", pid, system_errmsg());
	    ;
	    }
	}
    }
    return rv;
}
#endif

/*
 * Various reentrant routines by mikep.  See util.h and systems.h
 */

/*
 * These are only necessary if we turn on interrupts in NSPR
 */
#ifdef NEED_RELOCKS
#include "crit.h"
#define RE_LOCK(name) \
    static CRITICAL name##_crit = 0; \
    if (name##_crit == 0) name##_crit = crit_init(); \
    crit_enter(name##_crit)

#define RE_UNLOCK(name)  crit_exit(name##_crit)

#else
#define RE_LOCK(name) /* nada */
#define RE_UNLOCK(name) /* nil */
#endif


NSAPI_PUBLIC char *
util_strtok(register char *s, 
			register const char *delim, 
			register char **lasts)
{
#ifdef HAVE_STRTOK_R
    return strtok_r(s, delim, lasts);
#else
	/*
 	 * THIS IS THE THREAD SAFE VERSION OF strtok captured from
	 * public NetBSD. Note that no locks are needed
	 */
    register char *spanp;
    register int c, sc;
    char *tok;

    if (s == NULL && (s = *lasts) == NULL)
        return (NULL);

    /*
     * Skip (span) leading delimiters (s += strspn(s, delim), 
	 * sort of).
     */

cont:
    c = *s++;
    for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
        if (c == sc)
            goto cont;
    }

    if (c == 0) {       /* no non-delimiter characters */
        *lasts = NULL;
        return (NULL);
    }
    tok = s - 1;

    /*
     * Scan token (scan for delimiters: s += strcspn(s, delim), 
	 * sort of).
     * Note that delim must have one NUL; we stop if we see that, too.
     */
    for (;;) {
        c = *s++;
        spanp = (char *)delim;
        do {
            if ((sc = *spanp++) == c) {
                if (c == 0)
                    s = NULL;
                else
                    s[-1] = 0;
                *lasts = s;
                return (tok);
            }
        } while (sc != 0);
    }
    /* NOTREACHED */
#endif /* no strtok_r */
}

#ifndef XP_WIN32
NSAPI_PUBLIC struct passwd *
util_getpwnam(const char *name, struct passwd *result, char *buffer, 
	int buflen)
{
#ifdef HAVE_PW_R

#ifdef AIX
  #ifdef AIX4_3
    return ((int)getpwnam_r(name, result, buffer, buflen, &result) == 0 ? result : NULL);
  #else
    return ((int)getpwnam_r(name, result, buffer, buflen) == 0 ? result : NULL);
  #endif
#else
    return getpwnam_r(name, result, buffer, buflen);
#endif /* AIX */

#else
    char *lastp;
    struct passwd *r;
    RE_LOCK(pw);
    r = getpwnam(name);
    if (!r) 
	return r;

    result->pw_gid = r->pw_gid;
    result->pw_uid = r->pw_uid;
    /* Hope this buffer is long enough */
    if (buffer)
        util_snprintf(buffer, buflen, "%s:%s:%d:%d:%s:%s:%s", r->pw_name, r->pw_passwd,
		r->pw_uid, r->pw_gid, r->pw_gecos, r->pw_dir, r->pw_shell);
    RE_UNLOCK(pw);

    result->pw_name = util_strtok(buffer, ":", &lastp);
    result->pw_passwd = util_strtok(NULL, ":", &lastp);
    (void) util_strtok(NULL, ":", &lastp);
    (void) util_strtok(NULL, ":", &lastp);
    result->pw_gecos = util_strtok(NULL, ":", &lastp);
    result->pw_dir = util_strtok(NULL, ":", &lastp);
    result->pw_shell = util_strtok(NULL, ":", &lastp);
    return result;
#endif
}
#endif

NSAPI_PUBLIC struct tm *
util_localtime(const time_t *clock, struct tm *res)
{
#ifdef HAVE_TIME_R
    return localtime_r(clock, res);
#else
    struct tm *rv;
    time_t zero = 0x7fffffff;

    RE_LOCK(localtime);
    RE_UNLOCK(localtime);
    rv = localtime(clock);
    if (!rv)
        rv = localtime(&zero);
    if (rv)
        *res = *rv;
    else
        return NULL;
    return res;
#endif
}


NSAPI_PUBLIC char *
util_ctime(const time_t *clock, char *buf, int buflen)
{
/* 
 * From cgi-src/restore.c refering to XP_WIN32:
 * 	MLM - gross, but it works, better now FLC
 */
#if !defined(HAVE_TIME_R) || defined(XP_WIN32)
    RE_LOCK(ctime);
    strncpy(buf, ctime(clock), buflen);
    buf[buflen - 1] = '\0';
    RE_UNLOCK(ctime);
    return buf;
#elif HAVE_TIME_R == 2
    return ctime_r(clock, buf);
#else /* HAVE_TIME_R == 3 */
    return ctime_r(clock, buf, buflen);
#endif
}

NSAPI_PUBLIC struct tm *
util_gmtime(const time_t *clock, struct tm *res)
{
#ifdef HAVE_TIME_R
    return gmtime_r(clock, res);
#else
    struct tm *rv;
    time_t zero = 0x7fffffff;

    RE_LOCK(gmtime);
    rv = gmtime(clock);
    RE_UNLOCK(gmtime);
    if (!rv)
        rv = gmtime(&zero);
    if (rv)
        *res = *rv;
    else 
        return NULL;
    
    return res;
#endif
}

NSAPI_PUBLIC char *
util_asctime(const struct tm *tm, char *buf, int buflen)
{
#if HAVE_TIME_R == 2
    return asctime_r(tm, buf);
#elif HAVE_TIME_R == 3
    return asctime_r(tm, buf, buflen);
#else
    RE_LOCK(asctime);
    strncpy(buf, asctime(tm), buflen);
    buf[buflen - 1] = '\0';
    RE_UNLOCK(asctime);
    return buf;
#endif
}

NSAPI_PUBLIC char *
util_strerror(int errnum, char *msg, size_t buflen)
{
#ifdef HAVE_STRERROR_R
    /* More IBM real-genius */
    return (strerror_r(errnum, msg, buflen) == 0) ? msg : NULL;
#else
    /* RE_LOCK(strerror); I don't think this is worth the trouble */
    (void)strncpy(msg, strerror(errnum), buflen);
    msg[buflen - 1] = '\0';
    return msg;
    /* RE_UNLOCK(strerror); */
#endif
}

/* returns a malloc'd string */
static const char *
GetCurrentCharset(void)
{
    static char *locale = NULL;
    const char  *charset;

    if ( NULL == locale ) {
        locale = setlocale(LC_CTYPE, "");       /* need to call this once */
    }

    charset = nl_langinfo( CODESET );
    if ( NULL == charset || '\0' == *charset ) {
        charset = UTIL_CHARSET_DEFAULT;
    }
    return strdup( charset );
}

#ifdef _WIN32
static char *
convertor( const char *src, int fromUtf8 )
{
    return win_char_converter( src, fromUtf8 );
}
#else /* _WIN32 */

static char *
convertor( const char *src, int fromUtf8 )
{
    const char   *src_charset = NULL;
    iconv_t             convdesc;
    char                *outbuf, *curoutbuf;
    size_t              inbytesleft, outbytesleft;

    src_charset = GetCurrentCharset();

    if (0 == strcasecmp(src_charset, UTIL_CHARSET_UTF8))
    {
        outbuf = strdup(src);
        if ( NULL ==  outbuf )
        {
            perror( "convert_to_utf8 - strdup" );
        }
        return outbuf;
    }

    /* Get a converter */
    if (fromUtf8) {
        convdesc = iconv_open( src_charset, UTIL_CHARSET_UTF8 );
    } else {
        convdesc = iconv_open( UTIL_CHARSET_UTF8, src_charset );
    }
    if ( (iconv_t)-1 == convdesc ) {
        if ( errno == EINVAL ) {
            fprintf( stderr, "%s: conversion from %s to %s is not supported\n",
                "convertor", src_charset, UTIL_CHARSET_UTF8 );
        } else {
            perror( src_charset );
        }
        return NULL;
    }

    /* Allocate room for the UTF-8 equivalent (maximum expansion = 6 times) */
/* XXX is that correct? */
    inbytesleft = strlen( src );
    outbytesleft = 6 * inbytesleft + 1;
    if ( NULL == ( outbuf = (char *)malloc( outbytesleft ))) {
        perror( "convert_to_utf8 - malloc" );
        iconv_close( convdesc );
        return NULL;
    }

    curoutbuf = outbuf;
    /*
     * Three steps for a good conversion:
     * 1) Insert the initial shift sequence if any.
     * 2) Convert our characters.
     * 3) Insert the closing shift sequence, if any.
     */
    if ( (size_t)-1 == iconv( convdesc, NULL, NULL,
        &curoutbuf, &outbytesleft ) /* initial shift seq. */
#ifdef LINUX
         || (size_t)-1 == iconv( convdesc, (char **)&src, &inbytesleft,
        &curoutbuf, &outbytesleft )     /* convert our chars. */
#else
        || (size_t)-1 == iconv( convdesc, &src, &inbytesleft,
        &curoutbuf, &outbytesleft )     /* convert our chars. */
#endif
        || (size_t)-1 == iconv( convdesc, NULL, NULL,
        &curoutbuf, &outbytesleft )) { /* closing shift seq. */
            perror( "convert_to_utf8 - iconv" );
            iconv_close( convdesc );
            return NULL;
    }
    *curoutbuf = '\0';  /* zero-terminate the resulting string */

    return outbuf;
}

#endif /* else _WIN32 */

#ifdef _WIN32
/* returns a malloc'd string */
static char *
win_char_converter(const char *instr, int bFromUTF8)
{
    char  *outstr = NULL;
    int    inlen, wclen, outlen;
    LPWSTR wcstr;
         
    if (instr == NULL)
            return NULL;

    if ((inlen = strlen(instr)) <= 0)
            return NULL;
 
    /* output never becomes longer than input,   XXXmcs: really true?
    ** thus we don't have to ask for the length
    */
    wcstr = (LPWSTR) malloc( sizeof( WCHAR ) * (inlen+1) );
    if (!wcstr)
        return NULL;
 
    wclen = MultiByteToWideChar(bFromUTF8 ? CP_UTF8 : CP_ACP, 0, instr,
                                 inlen, wcstr, inlen);
    outlen = WideCharToMultiByte(bFromUTF8 ? CP_ACP : CP_UTF8, 0, wcstr,
                                  wclen, NULL, 0, NULL, NULL);
 
    if (outlen > 0) {
        outstr = (char *) malloc(outlen + 2);
        outlen = WideCharToMultiByte(bFromUTF8 ? CP_ACP : CP_UTF8, 0, wcstr,
                                      wclen, outstr, outlen, NULL, NULL);
        if (outlen > 0)
            *(outstr+outlen) = _T('\0');
        else
            return NULL;
    }
    free( wcstr );
    return outstr;
}
#endif /* _WIN32 */

#ifdef __cplusplus
extern "C" {
#endif

NSAPI_PUBLIC char *
util_local_to_utf8(const char *src)
{
    char *utf8;
    if ( src == NULL ) {                /* trivial case # 1 */
        utf8 = NULL;
    } else if ( *src == '\0' ) {        /* trivial case # 2 */
        utf8 = strdup( "" );
    } else {
        utf8 = convertor( src, 0 );  /* the real deal */
        if ( utf8 == NULL ) {
            utf8 = strdup ( src );
        }
    }

    return utf8;
}

#ifdef __cplusplus
}
#endif

