pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file strutil.c
 *
 * \brief String utilities for PennMUSH.
 *
 *
 */

#include "config.h"

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <limits.h>
#include "copyrite.h"
#include "conf.h"
#include "case.h"
#include "ansi.h"
#include "pueblo.h"
#include "parse.h"
#include "externs.h"
#include "mymalloc.h"
#include "log.h"
#include "confmagic.h"

char *next_token(char *str, char sep);
int format_long(long val, char *buff, char **bp, int maxlen, int base);
static char *
mush_strndup(const char *src, size_t len, const char *check)
  __attribute_malloc__;

/* Duplicate the first len characters of s */
    static char *mush_strndup(const char *src, size_t len, const char *check)
{
  char *copy;
  size_t rlen = strlen(src);

  if (rlen < len)
    len = rlen;

  copy = mush_malloc(len + 1, check);
  if (copy) {
    memcpy(copy, src, len);
    copy[len] = '\0';
  }

  return copy;
}

/** Our version of strdup, with memory leak checking.
 * This should be used in preference to strdup, and in assocation
 * with mush_free().
 * \param s string to duplicate.
 * \param check label for memory checking.
 * \return newly allocated copy of s.
 */
char *
mush_strdup(const char *s, const char *check __attribute__ ((__unused__)))
{
  char *x;

#ifdef HAS_STRDUP
  x = strdup(s);
  if (x)
    add_check(check);
#else

  size_t len = strlen(s) + 1;
  x = mush_malloc(len, check);
  if (x)
    memcpy(x, s, len);
#endif
  return x;
}

/** Return the string chopped at lim characters.
 * \param str string to chop.
 * \param lim character at which to chop the string.
 * \return statically allocated buffer with chopped string.
 */
char *
chopstr(const char *str, size_t lim)
{
  static char tbuf1[BUFFER_LEN];
  if (strlen(str) <= lim)
    return (char *) str;
  strncpy(tbuf1, str, lim);
  tbuf1[lim] = '\0';
  return tbuf1;
}


#ifndef HAS_STRCASECMP
#ifndef WIN32
/** strcasecmp for systems without it.
 * \param s1 one string to compare.
 * \param s2 another string to compare.
 * \retval -1 s1 is less than s2.
 * \retval 0 s1 equals s2
 * \retval 1 s1 is greater than s2.
 */
int
strcasecmp(const char *s1, const char *s2)
{
  while (*s1 && *s2 && DOWNCASE(*s1) == DOWNCASE(*s2))
    s1++, s2++;

  return (DOWNCASE(*s1) - DOWNCASE(*s2));
}

/** strncasecmp for systems without it.
 * \param s1 one string to compare.
 * \param s2 another string to compare.
 * \param n number of characters to compare.
 * \retval -1 s1 is less than s2.
 * \retval 0 s1 equals s2
 * \retval 1 s1 is greater than s2.
 */
int
strncasecmp(const char *s1, const char *s2, size_t n)
{
  for (; 0 < n; ++s1, ++s2, --n)
    if (DOWNCASE(*s1) != DOWNCASE(*s2))
      return DOWNCASE(*s1) - DOWNCASE(*s2);
    else if (*s1 == 0)
      return 0;
  return 0;

}
#endif				/* !WIN32 */
#endif				/* !HAS_STRCASECMP */

/** Does string begin with prefix? 
 * This comparison is case-insensitive.
 * \param string to check.
 * \param prefix to check against.
 * \retval 1 string begins with prefix.
 * \retval 0 string does not begin with prefix.
 */
int
string_prefix(const char *RESTRICT string, const char *RESTRICT prefix)
{
  if (!string || !prefix)
    return 0;
  while (*string && *prefix && DOWNCASE(*string) == DOWNCASE(*prefix))
    string++, prefix++;
  return *prefix == '\0';
}

/** Match a substring at the start of a word in a string, case-insensitively.
 * \param src a string of words to match against.
 * \param sub a prefix to match against the start of a word in string.
 * \return pointer into src at the matched word, or NULL.
 */
const char *
string_match(const char *src, const char *sub)
{
  if (!src || !sub)
    return 0;

  if (*sub != '\0') {
    while (*src) {
      if (string_prefix(src, sub))
	return src;
      /* else scan to beginning of next word */
      while (*src && (isalpha((unsigned char) *src)
		      || isdigit((unsigned char) *src)))
	src++;
      while (*src && !isalpha((unsigned char) *src)
	     && !isdigit((unsigned char) *src))
	src++;
    }
  }
  return NULL;
}

/** Return an initial-cased version of a string in a static buffer.
 * \param s string to initial-case.
 * \return pointer to a static buffer containing the initial-cased version.
 */
char *
strinitial(const char *s)
{
  static char buf1[BUFFER_LEN];
  char *p;

  if (!s || !*s) {
    buf1[0] = '\0';
    return buf1;
  }
  strcpy(buf1, s);
  for (p = buf1; *p; p++)
    *p = DOWNCASE(*p);
  buf1[0] = UPCASE(buf1[0]);
  return buf1;
}

/** Return an uppercased version of a string in a static buffer.
 * \param s string to uppercase.
 * \return pointer to a static buffer containing the uppercased version.
 */
char *
strupper(const char *s)
{
  static char buf1[BUFFER_LEN];
  char *p;

  if (!s || !*s) {
    buf1[0] = '\0';
    return buf1;
  }
  strcpy(buf1, s);
  for (p = buf1; *p; p++)
    *p = UPCASE(*p);
  return buf1;
}

/** Return a lowercased version of a string in a static buffer.
 * \param s string to lowercase.
 * \return pointer to a static buffer containing the lowercased version.
 */
char *
strlower(const char *s)
{
  static char buf1[BUFFER_LEN];
  char *p;

  if (!s || !*s) {
    buf1[0] = '\0';
    return buf1;
  }
  strcpy(buf1, s);
  for (p = buf1; *p; p++)
    *p = DOWNCASE(*p);
  return buf1;
}

/** Modify a string in-place to uppercase.
 * \param s string to uppercase.
 * \return s, now modified to be all uppercase.
 */
char *
upcasestr(char *s)
{
  char *p;
  for (p = s; p && *p; p++)
    *p = UPCASE(*p);
  return s;
}

/** Safely add an accented string to a buffer.
 * \param base base string to which accents are applied.
 * \param tmplate accent template string.
 * \param len length of base (and tmplate).
 * \param buff pointer to buffer to store accented string.
 * \param bp pointer to pointer to insertion point in buff.
 * \retval 1 failed to store entire string.
 * \retval 0 success.
 */
int
safe_accent(const char *RESTRICT base, const char *RESTRICT tmplate, size_t len,
	    char *buff, char **bp)
{
  /* base and tmplate must be the same length */
  size_t n;
  unsigned char c;

  for (n = 0; n < len; n++) {
    switch (base[n]) {
    case 'A':
      switch (tmplate[n]) {
      case '`':
	c = 192;
	break;
      case '\'':
	c = 193;
	break;
      case '^':
	c = 194;
	break;
      case '~':
	c = 195;
	break;
      case ':':
	c = 196;
	break;
      case 'o':
	c = 197;
	break;
      case 'e':
      case 'E':
	c = 198;
	break;
      default:
	c = 'A';
      }
      break;
    case 'a':
      switch (tmplate[n]) {
      case '`':
	c = 224;
	break;
      case '\'':
	c = 225;
	break;
      case '^':
	c = 226;
	break;
      case '~':
	c = 227;
	break;
      case ':':
	c = 228;
	break;
      case 'o':
	c = 229;
	break;
      case 'e':
      case 'E':
	c = 230;
	break;
      default:
	c = 'a';
      }
      break;
    case 'C':
      if (tmplate[n] == ',')
	c = 199;
      else
	c = 'C';
      break;
    case 'c':
      if (tmplate[n] == ',')
	c = 231;
      else
	c = 'c';
      break;
    case 'E':
      switch (tmplate[n]) {
      case '`':
	c = 200;
	break;
      case '\'':
	c = 201;
	break;
      case '^':
	c = 202;
	break;
      case ':':
	c = 203;
	break;
      default:
	c = 'E';
      }
      break;
    case 'e':
      switch (tmplate[n]) {
      case '`':
	c = 232;
	break;
      case '\'':
	c = 233;
	break;
      case '^':
	c = 234;
	break;
      case ':':
	c = 235;
	break;
      default:
	c = 'e';
      }
      break;
    case 'I':
      switch (tmplate[n]) {
      case '`':
	c = 204;
	break;
      case '\'':
	c = 205;
	break;
      case '^':
	c = 206;
	break;
      case ':':
	c = 207;
	break;
      default:
	c = 'I';
      }
      break;
    case 'i':
      switch (tmplate[n]) {
      case '`':
	c = 236;
	break;
      case '\'':
	c = 237;
	break;
      case '^':
	c = 238;
	break;
      case ':':
	c = 239;
	break;
      default:
	c = 'i';
      }
      break;
    case 'N':
      if (tmplate[n] == '~')
	c = 209;
      else
	c = 'N';
      break;
    case 'n':
      if (tmplate[n] == '~')
	c = 241;
      else
	c = 'n';
      break;
    case 'O':
      switch (tmplate[n]) {
      case '`':
	c = 210;
	break;
      case '\'':
	c = 211;
	break;
      case '^':
	c = 212;
	break;
      case '~':
	c = 213;
	break;
      case ':':
	c = 214;
	break;
      default:
	c = 'O';
      }
      break;
    case 'o':
      switch (tmplate[n]) {
      case '&':
	c = 240;
	break;
      case '`':
	c = 242;
	break;
      case '\'':
	c = 243;
	break;
      case '^':
	c = 244;
	break;
      case '~':
	c = 245;
	break;
      case ':':
	c = 246;
	break;
      default:
	c = 'o';
      }
      break;
    case 'U':
      switch (tmplate[n]) {
      case '`':
	c = 217;
	break;
      case '\'':
	c = 218;
	break;
      case '^':
	c = 219;
	break;
      case ':':
	c = 220;
	break;
      default:
	c = 'U';
      }
      break;
    case 'u':
      switch (tmplate[n]) {
      case '`':
	c = 249;
	break;
      case '\'':
	c = 250;
	break;
      case '^':
	c = 251;
	break;
      case ':':
	c = 252;
	break;
      default:
	c = 'u';
      }
      break;
    case 'Y':
      if (tmplate[n] == '\'')
	c = 221;
      else
	c = 'Y';
      break;
    case 'y':
      if (tmplate[n] == '\'')
	c = 253;
      else if (tmplate[n] == ':')
	c = 255;
      else
	c = 'y';
      break;
    case '?':
      if (tmplate[n] == 'u')
	c = 191;
      else
	c = '?';
      break;
    case '!':
      if (tmplate[n] == 'u')
	c = 161;
      else
	c = '!';
      break;
    case '<':
      if (tmplate[n] == '"')
	c = 171;
      else
	c = '<';
      break;
    case '>':
      if (tmplate[n] == '"')
	c = 187;
      else
	c = '>';
      break;
    case 's':
      if (tmplate[n] == 'B')
	c = 223;
      else
	c = 's';
      break;
    case 'p':
      if (tmplate[n] == '|')
	c = 254;
      else
	c = 'p';
      break;
    case 'P':
      if (tmplate[n] == '|')
	c = 222;
      else
	c = 'P';
      break;
    case 'D':
      if (tmplate[n] == '-')
	c = 208;
      else
	c = 'D';
      break;
    default:
      c = base[n];
    }
    if (isprint(c)) {
      if (safe_chr((char) c, buff, bp))
	return 1;
    } else {
      if (safe_chr(base[n], buff, bp))
	return 1;
    }
  }
  return 0;
}


/** Define the args used in APPEND_TO_BUF */
#define APPEND_ARGS int len, blen, clen
/** Append a string c to the end of buff, starting at *bp.
 * This macro is used by the safe_XXX functions.
 */
#define APPEND_TO_BUF \
  /* Trivial cases */  \
  if (c[0] == '\0') \
    return 0; \
  /* The array is at least two characters long here */ \
  if (c[1] == '\0') \
    return safe_chr(c[0], buff, bp); \
  len = strlen(c); \
  blen = *bp - buff; \
  if (blen > (BUFFER_LEN - 1)) \
    return len; \
  if ((len + blen) <= (BUFFER_LEN - 1)) \
    clen = len; \
  else \
    clen = (BUFFER_LEN - 1) - blen; \
  memcpy(*bp, c, clen); \
  *bp += clen; \
  return len - clen


/** Safely store a formatted string into a buffer.
 * This is a better way to do safe_str(tprintf(fmt,...),buff,bp)
 * \param buff buffer to store formatted string.
 * \param bp pointer to pointer to insertion point in buff.
 * \param fmt format string.
 * \return number of characters left over, or 0 for success.
 */
int
safe_format(char *buff, char **bp, const char *RESTRICT fmt, ...)
{
  APPEND_ARGS;
#ifdef HAS_VSNPRINTF
  char c[BUFFER_LEN];
#else
  char c[BUFFER_LEN * 3];
#endif
  va_list args;

  va_start(args, fmt);

#ifdef HAS_VSNPRINTF
  vsnprintf(c, sizeof c, fmt, args);
#else
  vsprintf(c, fmt, args);
#endif
  c[BUFFER_LEN - 1] = '\0';
  va_end(args);

  APPEND_TO_BUF;
}

/** Safely store an integer into a buffer.
 * \param i integer to store.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \return 0 on success, non-zero on failure.
 */
int
safe_integer(int i, char *buff, char **bp)
{
  return format_long(i, buff, bp, BUFFER_LEN, 10);
}

/** Safely store an unsigned integer into a buffer.
 * \param i integer to store.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \return 0 on success, non-zero on failure.
 */
int
safe_uinteger(unsigned int i, char *buff, char **bp)
{
  return safe_str(unparse_uinteger(i), buff, bp);
}

/** Safely store an unsigned integer into a short buffer.
 * \param i integer to store.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \return 0 on success, non-zero on failure.
 */
int
safe_integer_sbuf(int i, char *buff, char **bp)
{
  return format_long(i, buff, bp, SBUF_LEN, 10);
}

/** Safely store a dbref into a buffer.
 * Don't store partial dbrefs.
 * \param d dbref to store.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \retval 0 success.
 * \retval 1 failure.
 */
int
safe_dbref(dbref d, char *buff, char **bp)
{
  char *saved = *bp;
  if (safe_chr('#', buff, bp)) {
    *bp = saved;
    return 1;
  }
  if (format_long(d, buff, bp, BUFFER_LEN, 10)) {
    *bp = saved;
    return 1;
  }
  return 0;
}


/** Safely store a number into a buffer.
 * \param n number to store.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \retval 0 success.
 * \retval 1 failure.
 */
int
safe_number(NVAL n, char *buff, char **bp)
{
  const char *c;
  APPEND_ARGS;
  c = unparse_number(n);
  APPEND_TO_BUF;
}

/** Safely store a string into a buffer.
 * \param c string to store.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \retval 0 success.
 * \retval 1 failure.
 */
int
safe_str(const char *c, char *buff, char **bp)
{
  APPEND_ARGS;
  if (!c || !*c)
    return 0;
  APPEND_TO_BUF;
}

/** Safely store a string into a buffer, quoting it if it contains a space.
 * \param c string to store.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \retval 0 success.
 * \retval 1 failure.
 */
int
safe_str_space(const char *c, char *buff, char **bp)
{
  APPEND_ARGS;
  char *saved = *bp;

  if (!c || !*c)
    return 0;

  if (strchr(c, ' ')) {
    if (safe_chr('"', buff, bp) || safe_str(c, buff, bp) ||
	safe_chr('"', buff, bp)) {
      *bp = saved;
      return 1;
    }
    return 0;
  } else {
    APPEND_TO_BUF;
  }
}


/** Safely store a string of known length into a buffer
 * This is an optimization of safe_str for when we know the string's length.
 * \param s string to store.
 * \param len length of s.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \retval 0 success.
 * \retval 1 failure.
 */
int
safe_strl(const char *s, int len, char *buff, char **bp)
{
  int blen, clen;

  if (!s || !*s)
    return 0;
  if (len == 1)
    return safe_chr(*s, buff, bp);

  blen = *bp - buff;
  if (blen > BUFFER_LEN - 2)
    return len;
  if ((len + blen) <= BUFFER_LEN - 2)
    clen = len;
  else
    clen = BUFFER_LEN - 2 - blen;
  memcpy(*bp, s, clen);
  *bp += clen;
  return len - clen;
}

/** Safely fill a string with a given character a given number of times.
 * \param x character to fill with.
 * \param n number of copies of character to fill in.
 * \param buff buffer to store into.
 * \param bp pointer to pointer to insertion point in buff.
 * \retval 0 success.
 * \retval 1 failure (filled to end of buffer, but more was requested).
 */
int
safe_fill(char x, size_t n, char *buff, char **bp)
{
  size_t blen;
  int ret = 0;

  if (n == 0)
    return 0;
  else if (n == 1)
    return safe_chr(x, buff, bp);

  if (n > BUFFER_LEN - 1)
    n = BUFFER_LEN - 1;

  blen = BUFFER_LEN - (*bp - buff) - 1;

  if (blen < n) {
    n = blen;
    ret = 1;
  }
  memset(*bp, x, n);
  *bp += n;

  return ret;
}

#undef APPEND_ARGS
#undef APPEND_TO_BUF

/* skip_space and seek_char are essentially right out of the 2.0 code */

/** Return a pointer to the next non-space character in a string, or NULL.
 * We return NULL if given a null string or a string with only spaces.
 * \param s string to search for non-spaces.
 * \return pointer to next non-space character in s.
 */
char *
skip_space(const char *s)
{
  char *c = (char *) s;
  while (c && *c && isspace((unsigned char) *c))
    c++;
  return c;
}

/** Return a pointer to next char in s which matches c, or to the terminating
 * null at the end of s.
 * \param s string to search.
 * \param c character to search for.
 * \return pointer to next occurence of c or to the end of s.
 */
char *
seek_char(const char *s, char c)
{
  char *p = (char *) s;
  while (p && *p && (*p != c))
    p++;
  return p;
}

/** Unsigned char version of strlen.
 * \param s string.
 * \return length of s.
 */
int
u_strlen(const unsigned char *s)
{
  return strlen((const char *) s);
}

/** Unsigned char version of strcpy. Equally dangerous.
 * \param target destination for copy.
 * \param source string to copy.
 * \return pointer to copy.
 */
unsigned char *
u_strcpy(unsigned char *target, const unsigned char *source)
{
  return (unsigned char *) strcpy((char *) target, (const char *) source);
}

/** Search for all copies of old in string, and replace each with newbit.
 * The replaced string is returned, newly allocated.
 * \param old string to find.
 * \param newbit string to replace old with.
 * \param string string to search for old in.
 * \return allocated string with replacements performed.
 */
char *
replace_string(const char *RESTRICT old, const char *RESTRICT newbit,
	       const char *RESTRICT string)
{
  char *result, *r;
  size_t len, newlen;

  r = result = mush_malloc(BUFFER_LEN, "replace_string.buff");
  if (!result)
    mush_panic(T("Couldn't allocate memory in replace_string!"));

  len = strlen(old);
  newlen = strlen(newbit);

  while (*string) {
    char *s = strstr(string, old);
    if (s) {			/* Match found! */
      safe_strl(string, s - string, result, &r);
      safe_strl(newbit, newlen, result, &r);
      string = s + len;
    } else {
      safe_str(string, result, &r);
      break;
    }
  }
  *r = '\0';
  return result;
}

/** Standard replacer tokens for text and position */
const char *standard_tokens[2] = { "##", "#@" };

/* Replace two tokens in a string at once. All-around better than calling
 * replace_string() twice
 */
/** Search for all copies of two old strings, and replace each with a 
 * corresponding newbit.
 * The replaced string is returned, newly allocated.
 * \param old array of two strings to find.
 * \param newbits array of two strings to replace old with.
 * \param string string to search for old.
 * \return allocated string with replacements performed.
 */
char *
replace_string2(const char *old[2], const char *newbits[2],
		const char *RESTRICT string)
{
  char *result, *rp;
  char firsts[3] = { '\0', '\0', '\0' };
  size_t oldlens[2], newlens[2];

  if (!string)
    return NULL;

  rp = result = mush_malloc(BUFFER_LEN, "replace_string.buff");
  if (!result)
    mush_panic(T("Couldn't allocate memory in replace_string2!"));

  firsts[0] = old[0][0];
  firsts[1] = old[1][0];

  oldlens[0] = strlen(old[0]);
  oldlens[1] = strlen(old[1]);
  newlens[0] = strlen(newbits[0]);
  newlens[1] = strlen(newbits[1]);

  while (*string) {
    size_t skip = strcspn(string, firsts);
    if (skip) {
      safe_strl(string, skip, result, &rp);
      string += skip;
    }
    if (*string) {
      if (strncmp(string, old[0], oldlens[0]) == 0) {	/* Copy the first */
	safe_strl(newbits[0], newlens[0], result, &rp);
	string += oldlens[0];
      } else if (strncmp(string, old[1], oldlens[1]) == 0) {	/* The second */
	safe_strl(newbits[1], newlens[1], result, &rp);
	string += oldlens[1];
      } else {
	safe_chr(*string, result, &rp);
	string++;
      }
    }
  }

  *rp = '\0';
  return result;

}

/** Given a string and a separator, trim leading and trailing spaces
 * if the separator is a space. This destructively modifies the string.
 * \param str string to trim.
 * \param sep separator character.
 * \return pointer to (trimmed) string.
 */
char *
trim_space_sep(char *str, char sep)
{
  /* Trim leading and trailing spaces if we've got a space separator. */

  char *p;

  if (sep != ' ')
    return str;
  /* Skip leading spaces */
  str += strspn(str, " ");
  for (p = str; *p; p++) ;
  /* And trailing */
  for (p--; (*p == ' ') && (p > str); p--) ;
  p++;
  *p = '\0';
  return str;
}

/** Find the start of the next token in a string.
 * If the separator is a space, we magically skip multiple spaces.
 * \param str the string.
 * \param sep the token separator character.
 * \return pointer to start of next token in string.
 */
char *
next_token(char *str, char sep)
{
  /* move pointer to start of the next token */

  while (*str && (*str != sep))
    str++;
  if (!*str)
    return NULL;
  str++;
  if (sep == ' ') {
    while (*str == sep)
      str++;
  }
  return str;
}

/** Split out the next token from a string, destructively modifying it.
 * As usually, if the separator is a space, we skip multiple spaces.
 * The string's address is update to be past the token, and the token
 * is returned. This code from TinyMUSH 2.0.
 * \param sp pointer to string to split from.
 * \param sep token separator.
 * \return pointer to token, now null-terminated.
 */
char *
split_token(char **sp, char sep)
{
  char *str, *save;

  save = str = *sp;
  if (!str) {
    *sp = NULL;
    return NULL;
  }
  while (*str && (*str != sep))
    str++;
  if (*str) {
    *str++ = '\0';
    if (sep == ' ') {
      while (*str == sep)
	str++;
    }
  } else {
    str = NULL;
  }
  *sp = str;
  return save;
}

/** Count the number of tokens in a string.
 * \param str string to count.
 * \param sep token separator.
 * \return number of tokens in str.
 */
int
do_wordcount(char *str, char sep)
{
  int n;

  if (!*str)
    return 0;
  for (n = 0; str; str = next_token(str, sep), n++) ;
  return n;
}

/** A version of strlen that ignores ansi and HTML sequences.
 * \param p string to get length of.
 * \return length of string p, not including ansi/html sequences.
 */
int
ansi_strlen(const char *p)
{
  int i = 0;

  if (!p)
    return 0;

  while (*p) {
    if (*p == ESC_CHAR) {
      while ((*p) && (*p != 'm'))
	p++;
    } else if (*p == TAG_START) {
      while ((*p) && (*p != TAG_END))
	p++;
    } else {
      i++;
    }
    p++;
  }
  return i;
}

/** Returns the apparent length of a string, up to numchars visible 
 * characters. The apparent length skips over nonprinting ansi and
 * tags.
 * \param p string.
 * \param numchars maximum size to report.
 * \return apparent length of string.
 */
int
ansi_strnlen(const char *p, size_t numchars)
{
  int i = 0;

  if (!p)
    return 0;
  while (*p && numchars > 0) {
    if (*p == ESC_CHAR) {
      while ((*p) && (*p != 'm')) {
	p++;
	i++;
      }
    } else if (*p == TAG_START) {
      while ((*p) && (*p != TAG_END)) {
	p++;
	i++;
      }
    } else
      numchars--;
    i++;
    p++;
  }
  return i;
}

/** Given a string, a word, and a separator, remove first occurence
 * of the word from the string. Destructive.
 * \param list a string containing a separated list.
 * \param word a word to remove from the list.
 * \param sep the separator between list items.
 * \return pointer to static buffer containing list without first occurence
 * of word.
 */
char *
remove_word(char *list, char *word, char sep)
{
  char *sp;
  char *bp;
  static char buff[BUFFER_LEN];

  bp = buff;
  sp = split_token(&list, sep);
  if (!strcmp(sp, word)) {
    sp = split_token(&list, sep);
    safe_str(sp, buff, &bp);
  } else {
    safe_str(sp, buff, &bp);
    while (list && strcmp(sp = split_token(&list, sep), word)) {
      safe_chr(sep, buff, &bp);
      safe_str(sp, buff, &bp);
    }
  }
  while (list) {
    sp = split_token(&list, sep);
    safe_chr(sep, buff, &bp);
    safe_str(sp, buff, &bp);
  }
  *bp = '\0';
  return buff;
}

/** Return the next name in a list. A name may be a single word, or
 * a quoted string. This is used by things like page/list. The list's
 * pointer is advanced to the next name in the list.
 * \param head pointer to pointer to string of names.
 * \return pointer to static buffer containing next name.
 */
char *
next_in_list(const char **head)
{
  int paren = 0;
  static char buf[BUFFER_LEN];
  char *p = buf;

  while (**head == ' ')
    (*head)++;

  if (**head == '"') {
    (*head)++;
    paren = 1;
  }

  /* Copy it char by char until you hit a " or, if not in a
   * paren, a space
   */
  while (**head && (paren || (**head != ' ')) && (**head != '"')) {
    safe_chr(**head, buf, &p);
    (*head)++;
  }

  if (paren && **head)
    (*head)++;

  safe_chr('\0', buf, &p);
  return buf;

}

/** Strip all ansi and html markup from a string. As a side effect,
 * stores the length of the stripped string in a provided address.
 * NOTE! Length returned is length *including* the terminating NULL,
 * because we usually memcpy the result.
 * \param orig string to strip.
 * \param s_len address to store length of stripped string, if provided.
 * \return pointer to static buffer containing stripped string.
 */
char *
remove_markup(const char *orig, size_t * s_len)
{
  static char buff[BUFFER_LEN];
  char *bp = buff;
  const char *q;
  size_t len = 0;

  if (!orig) {
    if (s_len)
      *s_len = 0;
    return NULL;
  }

  for (q = orig; *q;) {
    switch (*q) {
    case ESC_CHAR:
      /* Skip over ansi */
      while (*q && *q++ != 'm') ;
      break;
    case TAG_START:
      /* Skip over HTML */
      while (*q && *q++ != TAG_END) ;
      break;
    default:
      safe_chr(*q++, buff, &bp);
      len++;
    }
  }
  *bp = '\0';
  if (s_len)
    *s_len = len + 1;
  return buff;
}


/** Safely append an int to a string. Returns a true value on failure.
 * This will someday take extra arguments for use with our version 
 * of snprintf. Please try not to use it.
 * maxlen = total length of string.
 * buf[maxlen - 1] = place where \0 will go.
 * buf[maxlen - 2] = last visible character.
 * \param val value to append.
 * \param buff string to append to.
 * \param bp pointer to pointer to insertion point in buff.
 * \param maxlen total length of string. 
 * \param base the base to render the number in.
 */
int
format_long(long val, char *buff, char **bp, int maxlen, int base)
{
  char stack[128];		/* Even a negative 64 bit number will only be 21
				   digits or so max. This should be plenty of
				   buffer room. */
  char *current;
  int size = 0, neg = 0;
  ldiv_t r;
  const char *digits = "0123456789abcdefghijklmnopqrstuvwxyz";

  /* Sanity checks */
  if (!bp || !buff || !*bp)
    return 1;
  if (*bp - buff >= maxlen - 1)
    return 1;

  if (base < 2)
    base = 2;
  else if (base > 36)
    base = 36;

  if (val < 0) {
    neg = 1;
    val = -val;
    if (val < 0) {
      /* -LONG_MIN == LONG_MIN on 2's complement systems. Take the
         easy way out since this value is rarely encountered. */
      switch (base) {
      case 10:
	return safe_format(buff, bp, "%ld", val);
      case 16:
	return safe_format(buff, bp, "%lx", val);
      case 8:
	return safe_format(buff, bp, "%lo", val);
      default:
	/* Weird base /and/ LONG_MIN. Fix someday. */
	return 0;
      }
    }

  }

  current = stack + sizeof(stack);

  /* Take the rightmost digit, and push it onto the stack, then
   * integer divide by 10 to get to the next digit. */
  r.quot = val;
  do {
    /* ldiv(x, y) does x/y and x%y at the same time (both of
     * which we need).
     */
    r = ldiv(r.quot, base);
    *(--current) = digits[r.rem];
  } while (r.quot);

  /* Add the negative sign if needed. */
  if (neg)
    *(--current) = '-';

  /* The above puts the number on the stack.  Now we need to put
   * it in the buffer.  If there's enough room, use Duff's Device
   * for speed, otherwise do it one char at a time.
   */

  size = stack + sizeof(stack) - current;

  /* if (size < (int) ((buff + maxlen - 1) - *bp)) { */
  if (((int) (*bp - buff)) + size < maxlen - 2) {
    switch (size % 8) {
    case 0:
      while (current < stack + sizeof(stack)) {
	*((*bp)++) = *(current++);
    case 7:
	*((*bp)++) = *(current++);
    case 6:
	*((*bp)++) = *(current++);
    case 5:
	*((*bp)++) = *(current++);
    case 4:
	*((*bp)++) = *(current++);
    case 3:
	*((*bp)++) = *(current++);
    case 2:
	*((*bp)++) = *(current++);
    case 1:
	*((*bp)++) = *(current++);
      }
    }
  } else {
    while (current < stack + sizeof(stack)) {
      if (*bp - buff >= maxlen - 1) {
	return 1;
      }
      *((*bp)++) = *(current++);
    }
  }

  return 0;
}

#if defined(HAS_STRXFRM) && !defined(WIN32)
/** A locale-sensitive strncmp.
 * \param s1 first string to compare.
 * \param s2 second string to compare.
 * \param t number of characters to compare.
 * \retval -1 s1 collates before s2.
 * \retval 0 s1 collates the same as s2.
 * \retval 1 s1 collates after s2.
 */
int
strncoll(const char *s1, const char *s2, size_t t)
{
  char *d1, *d2, *ns1, *ns2;
  int result;
  size_t s1_len, s2_len;

  ns1 = mush_malloc(t + 1, "string");
  ns2 = mush_malloc(t + 1, "string");
  memcpy(ns1, s1, t);
  ns1[t] = '\0';
  memcpy(ns2, s2, t);
  ns2[t] = '\0';
  s1_len = strxfrm(NULL, ns1, 0) + 1;
  s2_len = strxfrm(NULL, ns2, 0) + 1;

  d1 = mush_malloc(s1_len + 1, "string");
  d2 = mush_malloc(s2_len + 1, "string");
  (void) strxfrm(d1, ns1, s1_len);
  (void) strxfrm(d2, ns2, s2_len);
  result = strcmp(d1, d2);
  mush_free(ns1, "string");
  mush_free(ns2, "string");
  mush_free(d1, "string");
  mush_free(d2, "string");
  return result;
}

/** A locale-sensitive strcasecmp.
 * \param s1 first string to compare.
 * \param s2 second string to compare.
 * \retval -1 s1 collates before s2.
 * \retval 0 s1 collates the same as s2.
 * \retval 1 s1 collates after s2.
 */
int
strcasecoll(const char *s1, const char *s2)
{
  char *d1, *d2;
  int result;
  size_t s1_len, s2_len;

  s1_len = strxfrm(NULL, s1, 0) + 1;
  s2_len = strxfrm(NULL, s2, 0) + 1;

  d1 = mush_malloc(s1_len, "string");
  d2 = mush_malloc(s2_len, "string");
  (void) strxfrm(d1, strupper(s1), s1_len);
  (void) strxfrm(d2, strupper(s2), s2_len);
  result = strcmp(d1, d2);
  mush_free(d1, "string");
  mush_free(d2, "string");
  return result;
}

/** A locale-sensitive strncasecmp.
 * \param s1 first string to compare.
 * \param s2 second string to compare.
 * \param t number of characters to compare.
 * \retval -1 s1 collates before s2.
 * \retval 0 s1 collates the same as s2.
 * \retval 1 s1 collates after s2.
 */
int
strncasecoll(const char *s1, const char *s2, size_t t)
{
  char *d1, *d2, *ns1, *ns2;
  int result;
  size_t s1_len, s2_len;

  ns1 = mush_malloc(t + 1, "string");
  ns2 = mush_malloc(t + 1, "string");
  memcpy(ns1, s1, t);
  ns1[t] = '\0';
  memcpy(ns2, s2, t);
  ns2[t] = '\0';
  s1_len = strxfrm(NULL, ns1, 0) + 1;
  s2_len = strxfrm(NULL, ns2, 0) + 1;

  d1 = mush_malloc(s1_len, "string");
  d2 = mush_malloc(s2_len, "string");
  (void) strxfrm(d1, strupper(ns1), s1_len);
  (void) strxfrm(d2, strupper(ns2), s2_len);
  result = strcmp(d1, d2);
  mush_free(ns1, "string");
  mush_free(ns2, "string");
  mush_free(d1, "string");
  mush_free(d2, "string");
  return result;
}
#endif				/* HAS_STRXFRM && !WIN32 */

/** Return a string pointer past any ansi/html markup at the start.
 * \param p a string.
 * \return pointer to string after any initial ansi/html markup.
 */
char *
skip_leading_ansi(const char *p)
{
  if (!p)
    return NULL;
  while (*p == ESC_CHAR || *p == TAG_START) {
    if (*p == ESC_CHAR) {
      while (*p && *p != 'm')
	p++;
    } else {			/* TAG_START */
      while (*p && *p != TAG_END)
	p++;
    }
    if (*p)
      p++;
  }
  return (char *) p;

}

/** Convert a string into an ansi_string.
 * This takes a string that may contain ansi/html markup codes and
 * converts it to an ansi_string structure that separately stores
 * the plain string and the markup codes for each character.
 * \param src string to parse.
 * \return pointer to an ansi_string structure representing the src string.
 */
ansi_string *
parse_ansi_string(const char *src)
{
  ansi_string *data;
  char *y, *current = NULL;
  size_t p = 0;

  if (!src)
    return NULL;

  data = mush_malloc(sizeof *data, "ansi_string");
  if (!data)
    return NULL;

  data->len = ansi_strlen(src);

  while (*src) {
    y = skip_leading_ansi(src);
    if (y != src) {
      if (current)
	mush_free(current, "markup_codes");
      current = mush_strndup(src, y - src, "markup_codes");
      src = y;
    }
    if (current)
      data->codes[p] = mush_strdup(current, "markup_codes");
    else
      data->codes[p] = NULL;
    data->text[p] = *src;
    if (*src)
      src++;
    p++;
  }
  data->text[p] = '\0';

  while (p <= data->len) {
    data->codes[p] = NULL;
    p++;
  }

  if (current)
    mush_free(current, "markup_codes");

  return data;
}


/** Fill up an ansi_string with codes so that when a code starts it
 * applies to all the following characters until there's a new code.
 * \param as pointer to an ansi_string to populate codes in.
 */
void
populate_codes(ansi_string *as)
{
  size_t p;
  char *current = NULL;

  if (!as)
    return;

  for (p = 0; p < as->len; p++)
    if (as->codes[p]) {
      if (current)
	mush_free(current, "markup_codes");
      current = mush_strdup(as->codes[p], "markup_codes");
    } else {
      if (!current)
	current = mush_strdup(ANSI_NORMAL, "markup_codes");
      as->codes[p] = mush_strdup(current, "markup_codes");
    }
  if (current)
    mush_free(current, "markup_codes");
}

/** Strip out codes from an ansi_string, leaving in only the codes where
 * they change.
 * \param as pointer to an ansi_string.
 */
void
depopulate_codes(ansi_string *as)
{
  size_t p, m;
  int normal = 1;

  if (!as)
    return;

  for (p = 0; p <= as->len; p++) {
    if (as->codes[p]) {
      if (normal) {
	if (strcmp(as->codes[p], ANSI_NORMAL) == 0) {
	  mush_free(as->codes[p], "markup_codes");
	  as->codes[p] = NULL;
	  continue;
	} else {
	  normal = 0;
	}
      }

      m = p;
      for (p++; p < as->len; p++) {
	if (as->codes[p] && strcmp(as->codes[p], as->codes[m]) == 0) {
	  mush_free(as->codes[p], "markup_codes");
	  as->codes[p] = NULL;
	} else {
	  p--;
	  break;
	}
      }
    }
  }
}

static int is_ansi_code(const char *s);
static int is_start_html_code(const char *s) __attribute__ ((__unused__));
static int is_end_html_code(const char *s);
/** Is s a string that signifies the end of ANSI codes? */
#define is_end_ansi_code(s) (!strcmp((s),ANSI_NORMAL))


static int
is_ansi_code(const char *s)
{
  return s && *s == ESC_CHAR;
}

static int
is_start_html_code(const char *s)
{
  return s && *s == TAG_START && *(s + 1) != '/';
}

static int
is_end_html_code(const char *s)
{
  return s && *s == TAG_START && *(s + 1) == '/';
}

/** Free an ansi_string.
 * \param as pointer to ansi_string to free.
 */
void
free_ansi_string(ansi_string *as)
{
  int p;

  if (!as)
    return;
  for (p = as->len; p >= 0; p--) {
    if (as->codes[p])
      mush_free(as->codes[p], "markup_codes");
  }
  mush_free(as, "ansi_string");
}

/** Safely append an ansi_string into a buffer as a real string.
 * \param as pointer to ansi_string to append.
 * \param start position in as to start copying from.
 * \param len length in characters to copy from as.
 * \param buff buffer to insert into.
 * \param bp pointer to pointer to insertion point of buff.
 * \retval 0 success.
 * \retval 1 failure.
 */
int
safe_ansi_string(ansi_string *as, size_t start, size_t len, char *buff,
		 char **bp)
{
  int p, q;
  int in_ansi = 0;
  int in_html = 0;

  if (!as)
    return 1;

  depopulate_codes(as);

  if (start > as->len || len == 0 || as->len == 0)
    return safe_str("", buff, bp);

  /* Find the starting codes by working our way backward until we
   * reach some opening codes, and then working our way back from there
   * until we hit a non-opening code or non-code 
   */
  p = start;
  while ((p >= 0) && (as->codes[p] == NULL))
    p--;
  /* p is now either <0 or pointing to a code */
  if ((p >= 0) && !is_end_html_code(as->codes[p]) &&
      !is_end_ansi_code(as->codes[p])) {
    /* p is now pointing to a starting code */
    q = p;
    while ((q >= 0) && as->codes[q] && !is_end_html_code(as->codes[q]) &&
	   !is_end_ansi_code(as->codes[q])) {
      if (is_ansi_code(as->codes[q]))
	in_ansi = 1;
      else if (is_start_html_code(as->codes[q]))
	in_html++;
      q--;
    }
    /* p is now pointing to the first starting code, and we know if we're
     * in ansi, html, or both. We also know how many html tags have been
     * opened.
     */
  }

  /* Copy the text. The right thing to do now would be to have a stack
   * of open html tags and clear in_html once all of the tags have
   * been closed. We don't quite do that, alas.
   */
  for (p = (int) start; p < (int) (start + len) && p < (int) as->len; p++) {
    if (as->codes[p]) {
      if (safe_str(as->codes[p], buff, bp))
	return 1;
      if (is_end_ansi_code(as->codes[p]))
	in_ansi = 0;
      else if (is_ansi_code(as->codes[p]))
	in_ansi = 1;
      if (is_end_html_code(as->codes[p]))
	in_html--;
      else if (is_start_html_code(as->codes[p]))
	in_html++;
    }
    if (safe_chr(as->text[p], buff, bp))
      return 1;
  }

  /* Output (only) closing codes if needed. */
  while (p <= (int) as->len) {
    if (!in_ansi && !in_html)
      break;
    if (as->codes[p]) {
      if (is_end_ansi_code(as->codes[p])) {
	in_ansi = 0;
	if (safe_str(as->codes[p], buff, bp))
	  return 1;
      } else if (is_end_html_code(as->codes[p])) {
	in_html--;
	if (safe_str(as->codes[p], buff, bp))
	  return 1;
      }
    }
    p++;
  }
  if (in_ansi)
    safe_str(ANSI_NORMAL, buff, bp);
  return 0;
}

/** Safely append an ansi_string into a buffer as a real string,
 * with extra copying of starting tags (for wrap()/align()).
 * \param as pointer to ansi_string to append.
 * \param start position in as to start copying from.
 * \param len length in characters to copy from as.
 * \param buff buffer to insert into.
 * \param bp pointer to pointer to insertion point of buff.
 * \retval 0 success.
 * \retval 1 failure.
 */
int
safe_ansi_string2(ansi_string *as, size_t start, size_t len, char *buff,
		  char **bp)
{
  int p, q;
  int in_ansi = 0;
  int in_html = 0;

  if (!as)
    return 1;

  depopulate_codes(as);

  if (start > as->len || len == 0 || as->len == 0)
    return safe_str("", buff, bp);

  /* Find the starting codes by working our way backward until we
   * reach some opening codes, and then working our way back from there
   * until we hit a non-opening code or non-code 
   */
  p = start;
  while ((p >= 0) && (as->codes[p] == NULL))
    p--;
  /* p is now either <0 or pointing to a code */
  if ((p >= 0) && !is_end_html_code(as->codes[p]) &&
      !is_end_ansi_code(as->codes[p])) {
    /* p is now pointing to a starting code */
    q = p;
    while ((q >= 0) && as->codes[q] && !is_end_html_code(as->codes[q]) &&
	   !is_end_ansi_code(as->codes[q])) {
      if (is_ansi_code(as->codes[q]))
	in_ansi = 1;
      else if (is_start_html_code(as->codes[q]))
	in_html++;
      q--;
    }
    /* p is now pointing to the first starting code, and we know if we're
     * in ansi, html, or both. We also know how many html tags have been
     * opened.
     */
    /* Except there's this one problem - Now we know it, we weren't
     * doing anything with it.
     */
    for (q = q + 1; q <= p; q++) {
      if (safe_str(as->codes[q], buff, bp))
	return 1;
    }
  }

  /* Copy the text. The right thing to do now would be to have a stack
   * of open html tags and clear in_html once all of the tags have
   * been closed. We don't quite do that, alas.
   */
  for (p = (int) start; p < (int) (start + len) && p < (int) as->len; p++) {
    if (as->codes[p]) {
      if (safe_str(as->codes[p], buff, bp))
	return 1;
      if (is_end_ansi_code(as->codes[p]))
	in_ansi = 0;
      else if (is_ansi_code(as->codes[p]))
	in_ansi = 1;
      if (is_end_html_code(as->codes[p]))
	in_html--;
      else if (is_start_html_code(as->codes[p]))
	in_html++;
    }
    if (safe_chr(as->text[p], buff, bp))
      return 1;
  }

  /* Output (only) closing codes if needed. */
  while (p <= (int) as->len) {
    if (!in_ansi && !in_html)
      break;
    if (as->codes[p]) {
      if (is_end_ansi_code(as->codes[p])) {
	in_ansi = 0;
	if (safe_str(as->codes[p], buff, bp))
	  return 1;
      } else if (is_end_html_code(as->codes[p])) {
	in_html--;
	if (safe_str(as->codes[p], buff, bp))
	  return 1;
      }
    }
    p++;
  }
  if (in_ansi)
    safe_str(ANSI_NORMAL, buff, bp);
  return 0;
}


/** Safely append a list item to a buffer, possibly with punctuation
 * and conjunctions.
 * Given the current item number in a list, whether it's the last item
 * in the list, the list's output separator, a conjunction,
 * and a punctuation mark to use between items, store the appropriate
 * inter-item stuff into the given buffer safely.
 * \param cur_num current item number of the item to append.
 * \param done 1 if this is the final item.
 * \param delim string to insert after most items (comma).
 * \param conjoin string to insert before last time ("and").
 * \param space output delimiter.
 * \param buff buffer to append to.
 * \param bp pointer to pointer to insertion point in buff.
 */
void
safe_itemizer(int cur_num, int done, const char *delim, const char *conjoin,
	      const char *space, char *buff, char **bp)
{
  /* We don't do anything if it's the first one */
  if (cur_num == 1)
    return;
  /* Are we done? */
  if (done) {
    /* if so, we need a [<delim>]<space><conj> */
    if (cur_num >= 3)
      safe_str(delim, buff, bp);
    safe_str(space, buff, bp);
    safe_str(conjoin, buff, bp);
  } else {
    /* if not, we need just <delim> */
    safe_str(delim, buff, bp);
  }
  /* And then we need another <space> */
  safe_str(space, buff, bp);

}

/** Return a stringified time in a static buffer
 * Just like ctime() except without the trailing newlines.
 * \param t the time to format.
 * \param utc true if the time should be displayed in UTC, 0 for local time zone.
 * \return a pointer to a static buffer with the stringified time.
 */
char *
show_time(time_t t, int utc)
{
  struct tm *when;

  if (utc)
    when = gmtime(&t);
  else
    when = localtime(&t);

  return show_tm(when);
}

/** Return a stringified time in a static buffer
 * Just like asctime() except without the trailing newlines.
 * \param when the time to format.
 * \return a pointer to a static buffer with the stringified time.
 */
char *
show_tm(struct tm *when)
{
  static char buffer[BUFFER_LEN];
  int p;

  if (!when)
    return NULL;

  strcpy(buffer, asctime(when));

  p = strlen(buffer) - 1;
  if (buffer[p] == '\n')
    buffer[p] = '\0';

  if (buffer[8] == ' ')
    buffer[8] = '0';

  return buffer;
}