/** * \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; }