summaryrefslogtreecommitdiff
path: root/pith/string.c
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/string.c
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'pith/string.c')
-rw-r--r--pith/string.c2862
1 files changed, 2862 insertions, 0 deletions
diff --git a/pith/string.c b/pith/string.c
new file mode 100644
index 00000000..84717c3e
--- /dev/null
+++ b/pith/string.c
@@ -0,0 +1,2862 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: string.c 910 2008-01-14 22:28:38Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2008 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+/*======================================================================
+ string.c
+ Misc extra and useful string functions
+ - rplstr replace a substring with another string
+ - sqzspaces Squeeze out the extra blanks in a string
+ - sqznewlines Squeeze out \n and \r.
+ - removing_trailing_white_space
+ - short_str Replace part of string with ... for display
+ - removing_leading_white_space
+ Remove leading or trailing white space
+ - removing_double_quotes
+ Remove surrounding double quotes
+ - strclean
+ both of above plus convert to lower case
+ - skip_white_space
+ return pointer to first non-white-space char
+ - skip_to_white_space
+ return pointer to first white-space char
+ - srchstr Search a string for first occurrence of a sub string
+ - srchrstr Search a string for last occurrence of a sub string
+ - strindex Replacement for strchr/index
+ - strrindex Replacement for strrchr/rindex
+ - sstrncpy Copy one string onto another, advancing dest'n pointer
+ - istrncpy Copy n chars between bufs, making ctrl chars harmless
+ - month_abbrev Return three letter abbreviations for months
+ - month_num Calculate month number from month/year string
+ - cannon_date Formalize format of a some what formatted date
+ - repeat_char Returns a string n chars long
+ - fold Inserts newlines for folding at whitespace.
+ - byte_string Format number of bytes with Kb, Mb, Gb or bytes
+ - enth-string Format number i.e. 1: 1st, 983: 983rd....
+ - string_to_cstring Convert string to C-style constant string with \'s
+ - cstring_to_hexstring Convert cstring to hex string
+ - cstring_to_string Convert C-style string to string
+ - add_backslash_escapes Escape / and \ with \
+ - remove_backslash_escapes Undo the \ escaping, and stop string at /.
+
+ ====*/
+
+#include "../pith/headers.h"
+#include "../pith/string.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/escapes.h"
+#include "../pith/util.h"
+
+
+void char_to_octal_triple(int, char *);
+char *dollar_escape_dollars(char *);
+time_t date_to_local_time_t(char *date);
+
+
+
+/*----------------------------------------------------------------------
+ Replace dl characters in one string with another given string
+
+ args: os -- pointer into output string
+ oslen -- size of output string starting at os
+ dl -- the number of character to delete starting at os
+ is -- The string to insert
+
+ Result: returns pointer in originl string to end of string just inserted
+ ---*/
+char *
+rplstr(char *os, size_t oslen, int dl, char *is)
+{
+ char *x1, *x2, *x3;
+ int diff;
+
+ if(os == NULL)
+ return(NULL);
+
+ x1 = os + strlen(os);
+
+ /* can't delete more characters than there are */
+ if(dl > x1 - os)
+ dl = x1 - os;
+
+ x2 = is;
+ if(is != NULL)
+ x2 = is + strlen(is);
+
+ diff = (x2 - is) - dl;
+
+ if(diff < 0){ /* String shrinks */
+ x3 = os;
+ if(is != NULL)
+ for(x2 = is; *x2; *x3++ = *x2++) /* copy new string in */
+ ;
+
+ for(x2 = x3 - diff; *x2; *x3++ = *x2++) /* shift for delete */
+ ;
+
+ *x3 = *x2; /* the null */
+ }
+ else{ /* String grows */
+ /* make room for insert */
+ x3 = x1 + diff;
+ if(x3 >= os + oslen) /* just protecting ourselves */
+ x3 = os + oslen - 1;
+
+ for(; x3 >= os + (x2 - is); *x3-- = *x1--); /* shift*/
+ ;
+
+ if(is != NULL)
+ for(x1 = os, x2 = is; *x2 ; *x1++ = *x2++)
+ ;
+
+ while(*x3) x3++;
+ }
+
+ os[oslen-1] = '\0';
+
+ return(x3);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Squeeze out blanks
+ ----------------------------------------------------------------------*/
+void
+sqzspaces(char *string)
+{
+ char *p = string;
+
+ while((*string = *p++) != '\0') /* while something to copy */
+ if(!isspace((unsigned char)*string)) /* only really copy if non-blank */
+ string++;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Squeeze out CR's and LF's
+ ----------------------------------------------------------------------*/
+void
+sqznewlines(char *string)
+{
+ char *p = string;
+
+ while((*string = *p++) != '\0') /* while something to copy */
+ if(*string != '\r' && *string != '\n') /* only copy if non-newline */
+ string++;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Remove leading white space from a string in place
+
+ Args: string -- string to remove space from
+ ----*/
+void
+removing_leading_white_space(char *string)
+{
+ register char *p;
+
+ if(!string || !*string || !isspace(*string))
+ return;
+
+ for(p = string; *p; p++) /* find the first non-blank */
+ if(!isspace((unsigned char) *p)){
+ while((*string++ = *p++) != '\0') /* copy back from there... */
+ ;
+
+ return;
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Remove trailing white space from a string in place
+
+ Args: string -- string to remove space from
+ ----*/
+void
+removing_trailing_white_space(char *string)
+{
+ char *p = NULL;
+
+ if(!string || !*string)
+ return;
+
+ for(; *string; string++) /* remember start of whitespace */
+ p = (!isspace((unsigned char)*string)) ? NULL : (!p) ? string : p;
+
+ if(p) /* if whitespace, blast it */
+ *p = '\0';
+}
+
+
+void
+removing_leading_and_trailing_white_space(char *string)
+{
+ register char *p, *q = NULL;
+
+ if(!string || !*string)
+ return;
+
+ for(p = string; *p; p++) /* find the first non-blank */
+ if(!isspace((unsigned char)*p)){
+ if(p == string){ /* don't have to copy in this case */
+ for(; *string; string++)
+ q = (!isspace((unsigned char)*string)) ? NULL : (!q) ? string : q;
+ }
+ else{
+ for(; (*string = *p++) != '\0'; string++)
+ q = (!isspace((unsigned char)*string)) ? NULL : (!q) ? string : q;
+ }
+
+ if(q)
+ *q = '\0';
+
+ return;
+ }
+
+ if(*string != '\0')
+ *string = '\0';
+}
+
+
+/*----------------------------------------------------------------------
+ Remove one set of double quotes surrounding string in place
+ Returns 1 if quotes were removed
+
+ Args: string -- string to remove quotes from
+ ----*/
+int
+removing_double_quotes(char *string)
+{
+ register char *p;
+ int ret = 0;
+
+ if(string && string[0] == '"' && string[1] != '\0'){
+ p = string + strlen(string) - 1;
+ if(*p == '"'){
+ ret++;
+ *p = '\0';
+ for(p = string; *p; p++)
+ *p = *(p+1);
+ }
+ }
+
+ return(ret);
+}
+
+
+
+/*----------------------------------------------------------------------
+ return a pointer to first non-whitespace char in string
+
+ Args: string -- string to scan
+ ----*/
+char *
+skip_white_space(char *string)
+{
+ while(*string && isspace((unsigned char) *string))
+ string++;
+
+ return(string);
+}
+
+
+
+/*----------------------------------------------------------------------
+ return a pointer to first whitespace char in string
+
+ Args: string -- string to scan
+ ----*/
+char *
+skip_to_white_space(char *string)
+{
+ while(*string && !isspace((unsigned char) *string))
+ string++;
+
+ return(string);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Remove quotes from a string in place
+
+ Args: string -- string to remove quotes from
+ Rreturns: string passed us, but with quotes gone
+ ----*/
+char *
+removing_quotes(char *string)
+{
+ char *p, *q;
+
+ if(*(p = q = string) == '\"'){
+ q++;
+ do
+ if(*q == '\"' || *q == '\\')
+ q++;
+ while((*p++ = *q++) != '\0');
+ }
+
+ return(string);
+}
+
+
+
+/*---------------------------------------------------
+ Remove leading whitespace, trailing whitespace and convert
+ to lowercase
+
+ Args: s, -- The string to clean
+
+ Result: the cleaned string
+ ----*/
+char *
+strclean(char *string)
+{
+ char *s = string, *sc = NULL, *p = NULL;
+
+ for(; *s; s++){ /* single pass */
+ if(!isspace((unsigned char)*s)){
+ p = NULL; /* not start of blanks */
+ if(!sc) /* first non-blank? */
+ sc = string; /* start copying */
+ }
+ else if(!p) /* it's OK if sc == NULL */
+ p = sc; /* start of blanks? */
+
+ if(sc) /* if copying, copy */
+ *sc++ = isupper((unsigned char)(*s))
+ ? (unsigned char)tolower((unsigned char)(*s))
+ : (unsigned char)(*s);
+ }
+
+ if(p) /* if ending blanks */
+ *p = '\0'; /* tie off beginning */
+ else if(!sc) /* never saw a non-blank */
+ *string = '\0'; /* so tie whole thing off */
+
+ return(string);
+}
+
+
+/*
+ * Returns a pointer to a short version of the string.
+ * If src is not longer than wid, pointer points to src.
+ * If longer than wid, a version which is wid long is made in
+ * buf and the pointer points there.
+ *
+ * Wid refers to UTF-8 screen width, not strlen width.
+ *
+ * Args src -- The string to be shortened
+ * buf -- A place to put the short version
+ * wid -- Desired width of shortened string
+ * where -- Where should the dots be in the shortened string. Can be
+ * FrontDots, MidDots, EndDots.
+ *
+ * FrontDots ...stuvwxyz
+ * EndDots abcdefgh...
+ * MidDots abcd...wxyz
+ */
+char *
+short_str(char *src, char *buf, size_t buflen, int wid, WhereDots where)
+{
+ char *ans;
+ unsigned alen, first, second;
+
+ if(wid <= 0){
+ ans = buf;
+ if(buflen > 0)
+ buf[0] = '\0';
+ }
+ else if((alen = utf8_width(src)) <= wid)
+ ans = src;
+ else{
+ ans = buf;
+ if(wid < 5){
+ if(buflen > wid){
+ strncpy(buf, "....", buflen);
+ buf[wid] = '\0';
+ }
+ }
+ else{
+ char *q;
+ unsigned got_width;
+
+ /*
+ * first == length of preellipsis text
+ * second == length of postellipsis text
+ */
+ if(where == FrontDots){
+ first = 0;
+ second = wid - 3;
+ }
+ else if(where == MidDots){
+ first = (wid - 3)/2;
+ second = wid - 3 - first;
+ }
+ else if(where == EndDots){
+ first = wid - 3;
+ second = 0;
+ }
+
+ q = buf;
+ if(first){
+ q += utf8_to_width(q, src, buflen, first, &got_width);
+ if(got_width != first){
+ if(second)
+ second++;
+ else
+ while(got_width < first && buflen-(q-buf) > 0)
+ *q++ = '.';
+ }
+ }
+
+ if(buflen - (q-buf) > 3){
+ strncpy(q, "...", buflen - (q-buf));
+ buf[buflen-1] = '\0';
+ q += strlen(q);
+ }
+
+ if(second){
+ char *p;
+
+ p = utf8_count_back_width(src, src+strlen(src), second, &got_width);
+ if(buflen - (q-buf) > strlen(p)){
+ strncpy(q, p, buflen - (q-buf));
+ buf[buflen-1] = '\0';
+ q += strlen(q);
+ }
+ }
+
+ if(buflen - (q-buf) > 0)
+ *q = '\0';
+
+ buf[buflen-1] = '\0';
+ }
+ }
+
+ return(ans);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Search one string for another
+
+ Args: haystack -- The string to search in, the larger string
+ needle -- The string to search for, the smaller string
+
+ Search for first occurrence of needle in the haystack, and return a pointer
+ into the string haystack when it is found. The text we are dealing with is
+ UTF-8. We'd like the search to be case-independent but we're not sure what
+ that means for UTF-8. We're not even sure what matching means. We're not going
+ to worry about composed characters and canonical forms and anything like that
+ for now. Instead, we'll do the case-independent thing for ascii but exact
+ equality for the rest of the character space.
+ ----*/
+char *
+srchstr(char *haystack, char *needle)
+{
+ char *p, *q;
+
+#define CMPNOCASE(x, y) (((isascii((unsigned char) (x)) && isupper((unsigned char) (x))) \
+ ? tolower((unsigned char) (x)) \
+ : (unsigned char) (x)) \
+ == ((isascii((unsigned char) (y)) && isupper((unsigned char) (y))) \
+ ? tolower((unsigned char) (y)) \
+ : (unsigned char) (y)))
+
+ if(needle && haystack)
+ for(; *haystack; haystack++)
+ for(p = needle, q = haystack; ; p++, q++){
+ if(!*p)
+ return(haystack); /* winner! */
+ else if(!*q)
+ return(NULL); /* len(needle) > len(haystack)! */
+ else if(*p != *q && !CMPNOCASE(*p, *q))
+ break;
+ }
+
+ return(NULL);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Search one string for another, from right
+
+ Args: is -- The string to search in, the larger string
+ ss -- The string to search for, the smaller string
+
+ Search for last occurrence of ss in the is, and return a pointer
+ into the string is when it is found. The search is case indepedent.
+ ----*/
+
+char *
+srchrstr(register char *is, register char *ss)
+{
+ register char *sx, *sy;
+ char *ss_store, *rv;
+ char *begin_is;
+ char temp[251];
+
+ if(is == NULL || ss == NULL)
+ return(NULL);
+
+ if(strlen(ss) > sizeof(temp) - 2)
+ ss_store = (char *)fs_get(strlen(ss) + 1);
+ else
+ ss_store = temp;
+
+ for(sx = ss, sy = ss_store; *sx != '\0' ; sx++, sy++)
+ *sy = isupper((unsigned char)(*sx))
+ ? (unsigned char)tolower((unsigned char)(*sx))
+ : (unsigned char)(*sx);
+ *sy = *sx;
+
+ begin_is = is;
+ is = is + strlen(is) - strlen(ss_store);
+ rv = NULL;
+ while(is >= begin_is){
+ for(sx = is, sy = ss_store;
+ ((*sx == *sy)
+ || ((isupper((unsigned char)(*sx))
+ ? (unsigned char)tolower((unsigned char)(*sx))
+ : (unsigned char)(*sx)) == (unsigned char)(*sy))) && *sy;
+ sx++, sy++)
+ ;
+
+ if(!*sy){
+ rv = is;
+ break;
+ }
+
+ is--;
+ }
+
+ if(ss_store != temp)
+ fs_give((void **)&ss_store);
+
+ return(rv);
+}
+
+
+
+/*----------------------------------------------------------------------
+ A replacement for strchr or index ...
+
+ Returns a pointer to the first occurrence of the character
+ 'ch' in the specified string or NULL if it doesn't occur
+
+ ....so we don't have to worry if it's there or not. We bring our own.
+If we really care about efficiency and think the local one is more
+efficient the local one can be used, but most of the things that take
+a long time are in the c-client and not in pine.
+ ----*/
+char *
+strindex(char *buffer, int ch)
+{
+ do
+ if(*buffer == ch)
+ return(buffer);
+ while (*buffer++ != '\0');
+
+ return(NULL);
+}
+
+
+/* Returns a pointer to the last occurrence of the character
+ * 'ch' in the specified string or NULL if it doesn't occur
+ */
+char *
+strrindex(char *buffer, int ch)
+{
+ char *address = NULL;
+
+ do
+ if(*buffer == ch)
+ address = buffer;
+ while (*buffer++ != '\0');
+ return(address);
+}
+
+
+/*----------------------------------------------------------------------
+ copy at most n chars of the UTF-8 source string onto the destination string
+ returning pointer to start of destination and converting any undisplayable
+ characters to harmless character equivalents.
+ ----*/
+char *
+iutf8ncpy(char *d, char *s, int n)
+{
+ register int i;
+
+ if(!d || !s)
+ return(NULL);
+
+ /*
+ * BUG: this needs to get improved to actually count the
+ * character "cell" positions. For now, at least don't break
+ * a multi-byte character.
+ */
+ for(i = 0; i < n && (d[i] = *s) != '\0'; s++, i++)
+ if((unsigned char)(*s) < 0x80 && FILTER_THIS(*s)){
+ if(i+1 < n){
+ d[i] = '^';
+ d[++i] = (*s == 0x7f) ? '?' : *s + '@';
+ }
+ else{
+ d[i] = '\0';
+ break; /* don't fit */
+ }
+ }
+ else if(*s & 0x80){
+ /* multi-byte character */
+ if((*s & 0xE0) == 0xC0){
+ if(i+1 < n){
+ if(((d[++i] = *++s) & 0xC0) != 0x80){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* too long */
+ }
+ }
+ else if((*s & 0xF0) == 0xE0){
+ if(i+2 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else if((*s & 0xF8) == 0xF0){
+ if(i+3 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else if((*s & 0xFC) == 0xF8){
+ if(i+4 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else if((*s & 0xFE) == 0xFC){
+ if(i+5 < n){
+ if(!(((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80
+ && ((d[++i] = *++s) & 0xC0) == 0x80)){
+ d[i] = '\0';
+ break; /* bogus utf-8 */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* won't fit */
+ }
+ }
+ else{
+ d[i] = '\0';
+ break; /* don't fit */
+ }
+ }
+
+ return(d);
+}
+
+
+/*----------------------------------------------------------------------
+ copy at most n chars of the source string onto the destination string
+ returning pointer to start of destination and converting any undisplayable
+ characters to harmless character equivalents.
+ ----*/
+char *
+istrncpy(char *d, char *s, int n)
+{
+ char *rv = d;
+ unsigned char c;
+
+ if(!d || !s)
+ return(NULL);
+
+ do
+ if(*s && (unsigned char)(*s) < 0x80 && FILTER_THIS(*s)
+ && !(*(s+1) && *s == ESCAPE && match_escapes(s+1))){
+ if(n-- > 0){
+ c = (unsigned char) *s;
+ *d++ = c >= 0x80 ? '~' : '^';
+
+ if(n-- > 0){
+ s++;
+ *d = (c == 0x7f) ? '?' : (c & 0x1f) + '@';
+ }
+ }
+ }
+ else{
+ if(n-- > 0)
+ *d = *s++;
+ }
+ while(n > 0 && *d++);
+
+ return(rv);
+}
+
+
+char *xdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL};
+
+char *
+month_abbrev(int month_num)
+{
+ static char *xmonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
+ if(month_num < 1 || month_num > 12)
+ return("xxx");
+ return(xmonths[month_num - 1]);
+}
+
+char *
+month_abbrev_locale(int month_num)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(month_num < 1 || month_num > 12)
+ return("xxx");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = 107;
+ tm.tm_mon = month_num-1;
+ our_strftime(buf, sizeof(buf), "%b", &tm);
+
+ /*
+ * If it is all digits, then use the English
+ * words instead. Look for
+ * "<digit>"
+ * "<digit><digit>" or
+ * "<space><digit>"
+ */
+ if((buf[0] && !(buf[0] & 0x80)
+ && isdigit((unsigned char)buf[0]) && !buf[1])
+ ||
+ (buf[0] && !(buf[0] & 0x80)
+ && (isdigit((unsigned char)buf[0]) || buf[0] == ' ')
+ && buf[1] && !(buf[1] & 0x80)
+ && isdigit((unsigned char)buf[1]) && !buf[2]))
+ return(month_abbrev(month_num));
+
+ /*
+ * If buf[0] is a digit then assume that there should be a leading
+ * space if it leads off with a single digit.
+ */
+ if(buf[0] && !(buf[0] & 0x80) && isdigit((unsigned char) buf[0])
+ && !(buf[1] && !(buf[1] & 0x80) && isdigit((unsigned char) buf[1]))){
+ char *p;
+
+ /* insert space at start of buf */
+ p = buf+strlen(buf) + 1;
+ if(p > buf + sizeof(buf) - 1)
+ p = buf + sizeof(buf) - 1;
+
+ for(; p > buf; p--)
+ *p = *(p-1);
+
+ buf[0] = ' ';
+ }
+
+ return(buf);
+ }
+ }
+ else
+ return(month_abbrev(month_num));
+#else /* DISABLE_LOCALE_DATES */
+ return(month_abbrev(month_num));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+char *
+month_name(int month_num)
+{
+ static char *months[] = {"January", "February", "March", "April",
+ "May", "June", "July", "August", "September", "October",
+ "November", "December", NULL};
+ if(month_num < 1 || month_num > 12)
+ return("");
+ return(months[month_num - 1]);
+}
+
+char *
+month_name_locale(int month_num)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(month_num < 1 || month_num > 12)
+ return("");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = 107;
+ tm.tm_mon = month_num-1;
+ our_strftime(buf, sizeof(buf), "%B", &tm);
+ return(buf);
+ }
+ }
+ else
+ return(month_name(month_num));
+#else /* DISABLE_LOCALE_DATES */
+ return(month_name(month_num));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+
+char *
+day_abbrev(int day_of_week)
+{
+ if(day_of_week < 0 || day_of_week > 6)
+ return("???");
+ return(xdays[day_of_week]);
+}
+
+char *
+day_abbrev_locale(int day_of_week)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(day_of_week < 0 || day_of_week > 6)
+ return("???");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_wday = day_of_week;
+ our_strftime(buf, sizeof(buf), "%a", &tm);
+ return(buf);
+ }
+ }
+ else
+ return(day_abbrev(day_of_week));
+#else /* DISABLE_LOCALE_DATES */
+ return(day_abbrev(day_of_week));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+char *
+day_name(int day_of_week)
+{
+ static char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday", NULL};
+ if(day_of_week < 0 || day_of_week > 6)
+ return("");
+ return(days[day_of_week]);
+}
+
+char *
+day_name_locale(int day_of_week)
+{
+#ifndef DISABLE_LOCALE_DATES
+ if(F_OFF(F_DISABLE_INDEX_LOCALE_DATES, ps_global)){
+ if(day_of_week < 0 || day_of_week > 6)
+ return("");
+ else{
+ static char buf[20];
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_wday = day_of_week;
+ our_strftime(buf, sizeof(buf), "%A", &tm);
+ return(buf);
+ }
+ }
+ else
+ return(day_name(day_of_week));
+#else /* DISABLE_LOCALE_DATES */
+ return(day_name(day_of_week));
+#endif /* DISABLE_LOCALE_DATES */
+}
+
+
+size_t
+our_strftime(char *dst, size_t dst_size, char *format, struct tm *tm)
+{
+#ifdef _WINDOWS
+ LPTSTR lptbuf, lptformat;
+ char *u;
+
+ lptbuf = (LPTSTR) fs_get(dst_size * sizeof(TCHAR));
+ lptbuf[0] = '\0';
+ lptformat = utf8_to_lptstr((LPSTR) format);
+
+ _tcsftime(lptbuf, dst_size, lptformat, tm);
+ u = lptstr_to_utf8(lptbuf);
+ if(u){
+ strncpy(dst, u, dst_size);
+ dst[dst_size-1] = '\0';
+ fs_give((void **) &u);
+ }
+
+ return(strlen(dst));
+#else
+ return(strftime(dst, dst_size, format, tm));
+#endif
+}
+
+
+/*----------------------------------------------------------------------
+ Return month number of month named in string
+
+ Args: s -- string with 3 letter month abbreviation of form mmm-yyyy
+
+ Result: Returns month number with January, year 1900, 2000... being 0;
+ -1 if no month/year is matched
+ ----*/
+int
+month_num(char *s)
+{
+ int month = -1, year;
+ int i;
+
+ if(F_ON(F_PRUNE_USES_ISO,ps_global)){
+ char save, *p;
+ char digmon[3];
+
+ if(s && strlen(s) > 4 && s[4] == '-'){
+ save = s[4];
+ s[4] = '\0';
+ year = atoi(s);
+ s[4] = save;
+ if(year == 0)
+ return(-1);
+
+ p = s + 5;
+ for(i = 0; i < 12; i++){
+ digmon[0] = ((i+1) < 10) ? '0' : '1';
+ digmon[1] = '0' + (i+1) % 10;
+ digmon[2] = '\0';
+ if(strcmp(digmon, p) == 0)
+ break;
+ }
+
+ if(i == 12)
+ return(-1);
+
+ month = year * 12 + i;
+ }
+ }
+ else{
+ if(s && strlen(s) > 3 && s[3] == '-'){
+ for(i = 0; i < 12; i++){
+ if(struncmp(month_abbrev(i+1), s, 3) == 0)
+ break;
+ }
+
+ if(i == 12)
+ return(-1);
+
+ year = atoi(s + 4);
+ if(year == 0)
+ return(-1);
+
+ month = year * 12 + i;
+ }
+ }
+
+ return(month);
+}
+
+
+/*
+ * Structure containing all knowledge of symbolic time zones.
+ * To add support for a given time zone, add it here, but make sure
+ * the zone name is in upper case.
+ */
+static struct {
+ char *zone;
+ short len,
+ hour_offset,
+ min_offset;
+} known_zones[] = {
+ {"PST", 3, -8, 0}, /* Pacific Standard */
+ {"PDT", 3, -7, 0}, /* Pacific Daylight */
+ {"MST", 3, -7, 0}, /* Mountain Standard */
+ {"MDT", 3, -6, 0}, /* Mountain Daylight */
+ {"CST", 3, -6, 0}, /* Central Standard */
+ {"CDT", 3, -5, 0}, /* Central Daylight */
+ {"EST", 3, -5, 0}, /* Eastern Standard */
+ {"EDT", 3, -4, 0}, /* Eastern Daylight */
+ {"JST", 3, 9, 0}, /* Japan Standard */
+ {"GMT", 3, 0, 0}, /* Universal Time */
+ {"UT", 2, 0, 0}, /* Universal Time */
+#ifdef IST_MEANS_ISREAL
+ {"IST", 3, 2, 0}, /* Israel Standard */
+#else
+#ifdef IST_MEANS_INDIA
+ {"IST", 3, 5, 30}, /* India Standard */
+#endif
+#endif
+ {NULL, 0, 0},
+};
+
+/*----------------------------------------------------------------------
+ Parse date in or near RFC-822 format into the date structure
+
+Args: given_date -- The input string to parse
+ d -- Pointer to a struct date to place the result in
+
+Returns nothing
+
+The following date fomrats are accepted:
+ WKDAY DD MM YY HH:MM:SS ZZ
+ DD MM YY HH:MM:SS ZZ
+ WKDAY DD MM HH:MM:SS YY ZZ
+ DD MM HH:MM:SS YY ZZ
+ DD MM WKDAY HH:MM:SS YY ZZ
+ DD MM WKDAY YY MM HH:MM:SS ZZ
+
+All leading, intervening and trailing spaces tabs and commas are ignored.
+The prefered formats are the first or second ones. If a field is unparsable
+it's value is left as -1.
+
+ ----*/
+void
+parse_date(char *given_date, struct date *d)
+{
+ char *p, **i, *q;
+ int month, n;
+
+ d->sec = -1;
+ d->minute= -1;
+ d->hour = -1;
+ d->day = -1;
+ d->month = -1;
+ d->year = -1;
+ d->wkday = -1;
+ d->hours_off_gmt = -1;
+ d->min_off_gmt = -1;
+
+ if(given_date == NULL)
+ return;
+
+ p = given_date;
+ while(*p && isspace((unsigned char)*p))
+ p++;
+
+ /* Start with weekday? */
+ if((q=strchr(p, ',')) != NULL){
+
+ if(q - p == 3){
+ *q = '\0';
+ for(i = xdays; *i != NULL; i++)
+ if(strucmp(p, *i) == 0) /* Match first 3 letters */
+ break;
+
+ *q = ',';
+
+ if(*i != NULL) {
+ /* Started with week day */
+ d->wkday = i - xdays;
+ }
+ }
+
+ p = q+1;
+ while(*p && isspace((unsigned char)*p))
+ p++;
+ }
+ else if((q=strchr(p, ' ')) != NULL && q - p == 3){
+ *q = '\0';
+ for(i = xdays; *i != NULL; i++)
+ if(strucmp(p, *i) == 0) /* Match first 3 letters */
+ break;
+
+ *q = ' ';
+
+ if(*i != NULL) {
+ /* Started with week day */
+ d->wkday = i - xdays;
+ p = q+1;
+ while(*p && isspace((unsigned char)*p))
+ p++;
+ }
+ }
+
+ if(isdigit((unsigned char)*p)) {
+ d->day = atoi(p);
+ while(*p && isdigit((unsigned char)*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace((unsigned char)*p)))
+ p++;
+ }
+ for(month = 1; month <= 12; month++)
+ if(struncmp(p, month_abbrev(month), 3) == 0)
+ break;
+ if(month < 13) {
+ d->month = month;
+
+ }
+ /* Move over month, (or whatever is there) */
+ while(*p && !isspace((unsigned char)*p) && *p != ',' && *p != '-')
+ p++;
+ while(*p && (isspace((unsigned char)*p) || *p == ',' || *p == '-'))
+ p++;
+
+ /* Check again for day */
+ if(isdigit((unsigned char)*p) && d->day == -1) {
+ d->day = atoi(p);
+ while(*p && isdigit((unsigned char)*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace((unsigned char)*p)))
+ p++;
+ }
+
+ /*-- Check for time --*/
+ for(q = p; *q && isdigit((unsigned char)*q); q++);
+ if(*q == ':') {
+ /* It's the time (out of place) */
+ d->hour = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ d->minute = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ d->sec = atoi(p);
+ while(*p && !isspace((unsigned char)*p))
+ p++;
+ }
+ }
+ while(*p && isspace((unsigned char)*p))
+ p++;
+ }
+
+
+ /* Get the year 0-49 is 2000-2049; 50-100 is 1950-1999 and
+ 101-9999 is 101-9999 */
+ if(isdigit((unsigned char)*p)) {
+ d->year = atoi(p);
+ if(d->year < 50)
+ d->year += 2000;
+ else if(d->year < 100)
+ d->year += 1900;
+ while(*p && isdigit((unsigned char)*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace((unsigned char)*p)))
+ p++;
+ } else {
+ /* Something wierd, skip it and try to resynch */
+ while(*p && !isspace((unsigned char)*p) && *p != ',' && *p != '-')
+ p++;
+ while(*p && (isspace((unsigned char)*p) || *p == ',' || *p == '-'))
+ p++;
+ }
+
+ /*-- Now get hours minutes, seconds and ignore tenths --*/
+ for(q = p; *q && isdigit((unsigned char)*q); q++);
+ if(*q == ':' && d->hour == -1) {
+ d->hour = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ d->minute = atoi(p);
+ while(*p && *p != ':' && !isspace((unsigned char)*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ d->sec = atoi(p);
+ while(*p && !isspace((unsigned char)*p))
+ p++;
+ }
+ }
+ }
+ while(*p && isspace((unsigned char)*p))
+ p++;
+
+
+ /*-- The time zone --*/
+ d->hours_off_gmt = 0;
+ d->min_off_gmt = 0;
+ if(*p) {
+ if((*p == '+' || *p == '-')
+ && isdigit((unsigned char)p[1])
+ && isdigit((unsigned char)p[2])
+ && isdigit((unsigned char)p[3])
+ && isdigit((unsigned char)p[4])
+ && !isdigit((unsigned char)p[5])) {
+ char tmp[3];
+ d->min_off_gmt = d->hours_off_gmt = (*p == '+' ? 1 : -1);
+ p++;
+ tmp[0] = *p++;
+ tmp[1] = *p++;
+ tmp[2] = '\0';
+ d->hours_off_gmt *= atoi(tmp);
+ tmp[0] = *p++;
+ tmp[1] = *p++;
+ tmp[2] = '\0';
+ d->min_off_gmt *= atoi(tmp);
+ } else {
+ for(n = 0; known_zones[n].zone; n++)
+ if(struncmp(p, known_zones[n].zone, known_zones[n].len) == 0){
+ d->hours_off_gmt = (int) known_zones[n].hour_offset;
+ d->min_off_gmt = (int) known_zones[n].min_offset;
+ break;
+ }
+ }
+ }
+
+ if(d->wkday == -1){
+ MESSAGECACHE elt;
+ struct tm *tm;
+ time_t t;
+
+ /*
+ * Not sure why we aren't just using this from the gitgo, but
+ * since not sure will just use it to repair wkday.
+ */
+ if(mail_parse_date(&elt, (unsigned char *) given_date)){
+ t = mail_longdate(&elt);
+ tm = localtime(&t);
+
+ if(tm)
+ d->wkday = tm->tm_wday;
+ }
+ }
+}
+
+
+char *
+convert_date_to_local(char *date)
+{
+ struct tm *tm;
+ time_t ltime;
+ static char datebuf[26];
+
+ ltime = date_to_local_time_t(date);
+ if(ltime == (time_t) -1)
+ return(date);
+
+ tm = localtime(&ltime);
+
+ if(tm == NULL)
+ return(date);
+
+ snprintf(datebuf, sizeof(datebuf), "%.3s, %d %.3s %d %02d:%02d:%02d",
+ day_abbrev(tm->tm_wday), tm->tm_mday, month_abbrev(tm->tm_mon+1),
+ tm->tm_year+1900, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+ return(datebuf);
+}
+
+
+time_t
+date_to_local_time_t(char *date)
+{
+ time_t ourtime;
+ struct tm theirtime;
+ struct date d;
+ static int zone = 1000000; /* initialize timezone offset */
+ static int dst;
+
+ if(zone == 1000000){
+ int julian;
+ struct tm *tm;
+ time_t now;
+
+ zone = 0;
+ /* find difference between gmtime and localtime, from c-client do_date */
+ now = time((time_t *) 0);
+ if(now != (time_t) -1){
+ tm = gmtime(&now);
+ if(tm != NULL){
+ zone = tm->tm_hour * 60 + tm->tm_min; /* minutes */
+ julian = tm->tm_yday;
+
+ tm = localtime(&now);
+ dst = tm->tm_isdst; /* for converting back to our time */
+
+ zone = tm->tm_hour * 60 + tm->tm_min - zone;
+ if((julian = tm->tm_yday - julian) != 0)
+ zone += ((julian < 0) == (abs(julian) == 1)) ? -24*60 : 24*60;
+
+ zone *= 60; /* change to seconds */
+ }
+ }
+ }
+
+ parse_date(date, &d);
+
+ /* put d into struct tm so we can use mktime */
+ memset(&theirtime, 0, sizeof(theirtime));
+ theirtime.tm_year = d.year - 1900;
+ theirtime.tm_mon = d.month - 1;
+ theirtime.tm_mday = d.day;
+ theirtime.tm_hour = d.hour - d.hours_off_gmt;
+ theirtime.tm_min = d.minute - d.min_off_gmt;
+ theirtime.tm_sec = d.sec;
+
+ theirtime.tm_isdst = dst;
+
+ ourtime = mktime(&theirtime); /* still theirtime, actually */
+
+ /* convert to the time we want to show */
+ if(ourtime != (time_t) -1)
+ ourtime += zone;
+
+ return(ourtime);
+}
+
+
+/*----------------------------------------------------------------------
+ Create a little string of blanks of the specified length.
+ Max n is MAX_SCREEN_COLS. Can use up to e repeat_char results at once.
+ ----*/
+char *
+repeat_char(int n, int c)
+{
+ static char bb[3][MAX_SCREEN_COLS+1];
+ static int whichbb = 0;
+ char *b;
+
+ whichbb = (whichbb + 1) % 3;
+ b = bb[whichbb];
+
+ if(n > sizeof(bb[0]))
+ n = sizeof(bb[0]) - 1;
+
+ bb[whichbb][n--] = '\0';
+ while(n >= 0)
+ bb[whichbb][n--] = c;
+
+ return(bb[whichbb]);
+}
+
+
+/*----------------------------------------------------------------------
+ Format number as amount of bytes, appending Kb, Mb, Gb, bytes
+
+ Args: bytes -- number of bytes to format
+
+ Returns pointer to static string. The numbers are divided to produce a
+nice string with precision of about 2-4 digits
+ ----*/
+char *
+byte_string(long int bytes)
+{
+ char *a, aa[5];
+ char *abbrevs = "GMK";
+ long i, ones, tenths;
+ static char string[10];
+
+ ones = 0L;
+ tenths = 0L;
+
+ if(bytes == 0L){
+ strncpy(string, "0 bytes", sizeof(string));
+ string[sizeof(string)-1] = '\0';
+ }
+ else {
+ for(a = abbrevs, i = 1000000000; i >= 1; i /= 1000, a++) {
+ if(bytes > i) {
+ ones = bytes/i;
+ if(ones < 10L && i > 10L)
+ tenths = (bytes - (ones * i)) / (i / 10L);
+ break;
+ }
+ }
+
+ aa[0] = *a; aa[1] = '\0';
+
+ if(tenths == 0)
+ snprintf(string, sizeof(string), "%ld%s%s", ones, aa, *a ? "B" : "bytes");
+ else
+ snprintf(string, sizeof(string), "%ld.%ld%s%s", ones, tenths, aa, *a ? "B" : "bytes");
+ }
+
+ return(string);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Print a string corresponding to the number given:
+ 1st, 2nd, 3rd, 105th, 92342nd....
+ ----*/
+
+char *
+enth_string(int i)
+{
+ static char enth[10];
+
+ enth[0] = '\0';
+
+ switch (i % 10) {
+
+ case 1:
+ if( (i % 100 ) == 11)
+ snprintf(enth, sizeof(enth),"%dth", i);
+ else
+ snprintf(enth, sizeof(enth),"%dst", i);
+ break;
+
+ case 2:
+ if ((i % 100) == 12)
+ snprintf(enth, sizeof(enth), "%dth",i);
+ else
+ snprintf(enth, sizeof(enth), "%dnd",i);
+ break;
+
+ case 3:
+ if(( i % 100) == 13)
+ snprintf(enth, sizeof(enth), "%dth",i);
+ else
+ snprintf(enth, sizeof(enth), "%drd",i);
+ break;
+
+ default:
+ snprintf(enth, sizeof(enth),"%dth",i);
+ break;
+ }
+ return(enth);
+}
+
+
+/*
+ * Inserts newlines for folding at whitespace.
+ *
+ * Args src -- The source text.
+ * width -- Approximately where the fold should happen.
+ * maxwidth -- Maximum width we want to fold at.
+ * first_indent -- String to use as indent on first line.
+ * indent -- String to use as indent for subsequent folded lines.
+ * flags -- FLD_CRLF End of line is \r\n instead of \n.
+ * FLD_PWS PreserveWhiteSpace when folding. This is
+ * for vcard folding where CRLF SPACE is
+ * removed when unfolding, so we need to
+ * leave the space in. With rfc2822 unfolding
+ * only the CRLF is removed when unfolding.
+ *
+ * Returns An allocated string which caller should free.
+ */
+char *
+fold(char *src, int width, int maxwidth, char *first_indent, char *indent, unsigned int flags)
+{
+ char *next_piece, *res, *p;
+ int i, len = 0, starting_point, winner, eol, this_width;
+ int indent1 = 0, /* width of first_indent */
+ indent2 = 0, /* width of indent */
+ nbindent2 = 0, /* number of bytes in indent */
+ nb = 0; /* number of bytes needed */
+ int cr, preserve_ws;
+ char save_char;
+ char *endptr = NULL;
+ unsigned shorter, longer;
+ unsigned got_width;
+
+ cr = (flags & FLD_CRLF);
+ preserve_ws = (flags & FLD_PWS);
+
+ if(indent){
+ indent2 = (int) utf8_width(indent);
+ nbindent2 = strlen(indent);
+ }
+
+ if(first_indent){
+ indent1 = (int) utf8_width(first_indent);
+ nb = strlen(first_indent);
+ }
+
+ len = indent1;
+ next_piece = src;
+ eol = cr ? 2 : 1;
+ if(!src || !*src)
+ nb += eol;
+
+ /*
+ * We can't tell how much space is going to be needed without actually
+ * passing through the data to see.
+ */
+ while(next_piece && *next_piece){
+ if(next_piece != src && indent2){
+ len += indent2;
+ nb += nbindent2;
+ }
+
+ this_width = (int) utf8_width(next_piece);
+ if(this_width + len <= width){
+ nb += (strlen(next_piece) + eol);
+ break;
+ }
+ else{ /* fold it */
+ starting_point = width - len; /* space left on this line */
+ /* find a good folding spot */
+ winner = -1;
+ for(i = 0;
+ winner == -1 && (starting_point - i > 5 || i < maxwidth - width);
+ i++){
+
+ if((shorter=starting_point-i) > 5){
+ endptr = utf8_count_forw_width(next_piece, shorter, &got_width);
+ if(endptr && got_width == shorter && isspace((unsigned char) *endptr))
+ winner = (int) shorter;
+ }
+
+ if(winner == -1
+ && (longer=starting_point+i) && i < maxwidth - width){
+ endptr = utf8_count_forw_width(next_piece, longer, &got_width);
+ if(endptr && got_width == longer && isspace((unsigned char) *endptr))
+ winner = (int) longer;
+ }
+ }
+
+ if(winner == -1){ /* if no good folding spot, fold at width */
+ winner = starting_point;
+ endptr = NULL;
+ }
+
+ if(endptr == NULL){
+ endptr = utf8_count_forw_width(next_piece, (unsigned) winner, &got_width);
+ winner = (int) got_width;
+ }
+
+ nb += ((endptr - next_piece) + eol);
+ next_piece = endptr;
+ if(!preserve_ws && isspace((unsigned char) next_piece[0]))
+ next_piece++;
+ }
+
+ len = 0;
+ }
+
+ res = (char *) fs_get((nb+1) * sizeof(char));
+ p = res;
+ sstrncpy(&p, first_indent, nb+1-(p-res));
+ len = indent1;
+ next_piece = src;
+
+ while(next_piece && *next_piece){
+ if(next_piece != src && indent2){
+ sstrncpy(&p, indent, nb+1-(p-res));
+ len += indent2;
+ }
+
+ this_width = (int) utf8_width(next_piece);
+ if(this_width + len <= width){
+ sstrncpy(&p, next_piece, nb+1-(p-res));
+ if(cr && p-res < nb+1)
+ *p++ = '\r';
+
+ if(p-res < nb+1)
+ *p++ = '\n';
+
+ break;
+ }
+ else{ /* fold it */
+ starting_point = width - len; /* space left on this line */
+ /* find a good folding spot */
+ winner = -1;
+ for(i = 0;
+ winner == -1 && (starting_point - i > 5 || i < maxwidth - width);
+ i++){
+
+ if((shorter=starting_point-i) > 5){
+ endptr = utf8_count_forw_width(next_piece, shorter, &got_width);
+ if(endptr && got_width == shorter && isspace((unsigned char) *endptr))
+ winner = (int) shorter;
+ }
+
+ if(winner == -1
+ && (longer=starting_point+i) && i < maxwidth - width){
+ endptr = utf8_count_forw_width(next_piece, longer, &got_width);
+ if(endptr && got_width == longer && isspace((unsigned char) *endptr))
+ winner = (int) longer;
+ }
+ }
+
+ if(winner == -1){ /* if no good folding spot, fold at width */
+ winner = starting_point;
+ endptr = NULL;
+ }
+
+ if(endptr == NULL){
+ endptr = utf8_count_forw_width(next_piece, (unsigned) winner, &got_width);
+ winner = (int) got_width;
+ }
+
+ if(endptr){
+ save_char = *endptr;
+ *endptr = '\0';
+ sstrncpy(&p, next_piece, nb+1-(p-res));
+ *endptr = save_char;
+ next_piece = endptr;
+ }
+
+ if(cr && p-res < nb+1)
+ *p++ = '\r';
+
+ if(p-res < nb+1)
+ *p++ = '\n';
+
+ if(!preserve_ws && isspace((unsigned char) next_piece[0]))
+ next_piece++;
+ }
+
+ len = 0;
+ }
+
+ if(!src || !*src){
+ if(cr && p-res < nb+1)
+ *p++ = '\r';
+
+ if(p-res < nb+1)
+ *p++ = '\n';
+ }
+
+ if(p-res < nb+1)
+ *p = '\0';
+
+ res[nb] = '\0';
+
+ return(res);
+}
+
+
+/*
+ * strsquish - fancifies a string into the given buffer if it's too
+ * long to fit in the given width
+ */
+char *
+strsquish(char *buf, size_t buflen, char *src, int width)
+{
+ /*
+ * Replace strsquish() with calls to short_str().
+ */
+ if(width > 14)
+ return(short_str(src, buf, buflen, width, MidDots));
+ else
+ return(short_str(src, buf, buflen, width, FrontDots));
+}
+
+
+char *
+long2string(long int l)
+{
+ static char string[20];
+
+ snprintf(string, sizeof(string), "%ld", l);
+ return(string);
+}
+
+
+char *
+ulong2string(unsigned long int l)
+{
+ static char string[20];
+
+ snprintf(string, sizeof(string), "%lu", l);
+ return(string);
+}
+
+
+char *
+int2string(int i)
+{
+ static char string[20];
+
+ snprintf(string, sizeof(string), "%d", i);
+ return(string);
+}
+
+
+/*
+ * strtoval - convert the given string to a positive integer.
+ */
+char *
+strtoval(char *s, int *val, int minmum, int maxmum, int otherok, char *errbuf,
+ size_t errbuflen, char *varname)
+{
+ int i = 0, neg = 1;
+ char *p = s, *errstr = NULL;
+
+ removing_leading_and_trailing_white_space(p);
+ for(; *p; p++)
+ if(isdigit((unsigned char) *p)){
+ i = (i * 10) + (*p - '0');
+ }
+ else if(*p == '-' && i == 0){
+ neg = -1;
+ }
+ else{
+ snprintf(errstr = errbuf, errbuflen,
+ "Non-numeric value ('%c' in \"%.8s\") in %s. Using \"%d\"",
+ *p, s, varname, *val);
+ return(errbuf);
+ }
+
+ i *= neg;
+
+ /* range describes acceptable values */
+ if(maxmum > minmum && (i < minmum || i > maxmum) && i != otherok)
+ snprintf(errstr = errbuf, errbuflen,
+ "%s of %d not supported (M%s %d). Using \"%d\"",
+ varname, i, (i > maxmum) ? "ax" : "in",
+ (i > maxmum) ? maxmum : minmum, *val);
+ /* range describes unacceptable values */
+ else if(minmum > maxmum && !(i < maxmum || i > minmum))
+ snprintf(errstr = errbuf, errbuflen, "%s of %d not supported. Using \"%d\"",
+ varname, i, *val);
+ else
+ *val = i;
+
+ return(errstr);
+}
+
+
+/*
+ * strtolval - convert the given string to a positive _long_ integer.
+ */
+char *
+strtolval(char *s, long int *val, long int minmum, long int maxmum, long int otherok,
+ char *errbuf, size_t errbuflen, char *varname)
+{
+ long i = 0, neg = 1L;
+ char *p = s, *errstr = NULL;
+
+ removing_leading_and_trailing_white_space(p);
+ for(; *p; p++)
+ if(isdigit((unsigned char) *p)){
+ i = (i * 10L) + (*p - '0');
+ }
+ else if(*p == '-' && i == 0L){
+ neg = -1L;
+ }
+ else{
+ snprintf(errstr = errbuf, errbuflen,
+ "Non-numeric value ('%c' in \"%.8s\") in %s. Using \"%ld\"",
+ *p, s, varname, *val);
+ return(errbuf);
+ }
+
+ i *= neg;
+
+ /* range describes acceptable values */
+ if(maxmum > minmum && (i < minmum || i > maxmum) && i != otherok)
+ snprintf(errstr = errbuf, errbuflen,
+ "%s of %ld not supported (M%s %ld). Using \"%ld\"",
+ varname, i, (i > maxmum) ? "ax" : "in",
+ (i > maxmum) ? maxmum : minmum, *val);
+ /* range describes unacceptable values */
+ else if(minmum > maxmum && !(i < maxmum || i > minmum))
+ snprintf(errstr = errbuf, errbuflen, "%s of %ld not supported. Using \"%ld\"",
+ varname, i, *val);
+ else
+ *val = i;
+
+ return(errstr);
+}
+
+
+/*
+ * Function to parse the given string into two space-delimited fields
+ * Quotes may be used to surround labels or values with spaces in them.
+ * Backslash negates the special meaning of a quote.
+ * Unescaping of backslashes only happens if the pair member is quoted,
+ * this provides for backwards compatibility.
+ *
+ * Args -- string -- the source string
+ * label -- the first half of the string, a return value
+ * value -- the last half of the string, a return value
+ * firstws -- if set, the halves are delimited by the first unquoted
+ * whitespace, else by the last unquoted whitespace
+ * strip_internal_label_quotes -- unescaped quotes in the middle of the label
+ * are removed. This is useful for vars
+ * like display-filters and url-viewers
+ * which may require quoting of an arg
+ * inside of a _TOKEN_.
+ */
+void
+get_pair(char *string, char **label, char **value, int firstws, int strip_internal_label_quotes)
+{
+ char *p, *q, *tmp, *token = NULL;
+ int quoted = 0;
+
+ *label = *value = NULL;
+
+ /*
+ * This for loop just finds the beginning of the value. If firstws
+ * is set, then it begins after the first whitespace. Otherwise, it begins
+ * after the last whitespace. Quoted whitespace doesn't count as
+ * whitespace. If there is no unquoted whitespace, then there is no
+ * label, there's just a value.
+ */
+ for(p = string; p && *p;){
+ if(*p == '"') /* quoted label? */
+ quoted = (quoted) ? 0 : 1;
+
+ if(*p == '\\' && *(p+1) == '"') /* escaped quote? */
+ p++; /* skip it... */
+
+ if(isspace((unsigned char)*p) && !quoted){ /* if space, */
+ while(*++p && isspace((unsigned char)*p)) /* move past it */
+ ;
+
+ if(!firstws || !token)
+ token = p; /* remember start of text */
+ }
+ else
+ p++;
+ }
+
+ if(token){ /* copy label */
+ *label = p = (char *)fs_get(((token - string) + 1) * sizeof(char));
+
+ /* make a copy of the string */
+ tmp = (char *)fs_get(((token - string) + 1) * sizeof(char));
+ strncpy(tmp, string, token - string);
+ tmp[token-string] = '\0';
+
+ removing_leading_and_trailing_white_space(tmp);
+ quoted = removing_double_quotes(tmp);
+
+ for(q = tmp; *q; q++){
+ if(quoted && *q == '\\' && (*(q+1) == '"' || *(q+1) == '\\'))
+ *p++ = *++q;
+ else if(!(strip_internal_label_quotes && *q == '"'))
+ *p++ = *q;
+ }
+
+ *p = '\0'; /* tie off label */
+ fs_give((void **)&tmp);
+ if(*label == '\0')
+ fs_give((void **)label);
+ }
+ else
+ token = string;
+
+ if(token){ /* copy value */
+ *value = p = (char *)fs_get((strlen(token) + 1) * sizeof(char));
+
+ tmp = cpystr(token);
+ removing_leading_and_trailing_white_space(tmp);
+ quoted = removing_double_quotes(tmp);
+
+ for(q = tmp; *q ; q++){
+ if(quoted && *q == '\\' && (*(q+1) == '"' || *(q+1) == '\\'))
+ *p++ = *++q;
+ else
+ *p++ = *q;
+ }
+
+ *p = '\0'; /* tie off value */
+ fs_give((void **)&tmp);
+ }
+}
+
+
+/*
+ * This is sort of the inverse of get_pair.
+ *
+ * Args -- label -- the first half of the string
+ * value -- the last half of the string
+ *
+ * Returns -- an allocated string which is "label" SPACE "value"
+ *
+ * Label and value are quoted separately. If quoting is needed (they contain
+ * whitespace) then backslash escaping is done inside the quotes for
+ * " and for \. If quoting is not needed, no escaping is done.
+ */
+char *
+put_pair(char *label, char *value)
+{
+ char *result, *lab = label, *val = value;
+ size_t l;
+
+ if(label && *label)
+ lab = quote_if_needed(label);
+
+ if(value && *value)
+ val = quote_if_needed(value);
+
+ l = strlen(lab) + strlen(val) +1;
+ result = (char *) fs_get((l+1) * sizeof(char));
+
+ snprintf(result, l+1, "%s%s%s",
+ lab ? lab : "",
+ (lab && lab[0] && val && val[0]) ? " " : "",
+ val ? val : "");
+
+ if(lab && lab != label)
+ fs_give((void **)&lab);
+ if(val && val != value)
+ fs_give((void **)&val);
+
+ return(result);
+}
+
+
+/*
+ * This is for put_pair type uses. It returns either an allocated
+ * string which is the quoted src string or it returns a pointer to
+ * the src string if no quoting is needed.
+ */
+char *
+quote_if_needed(char *src)
+{
+ char *result = src, *qsrc = NULL;
+
+ if(src && *src){
+ /* need quoting? */
+ if(strpbrk(src, " \t") != NULL)
+ qsrc = add_escapes(src, "\\\"", '\\', "", "");
+
+ if(qsrc && !*qsrc)
+ fs_give((void **)&qsrc);
+
+ if(qsrc){
+ size_t l;
+
+ l = strlen(qsrc)+2;
+ result = (char *) fs_get((l+1) * sizeof(char));
+ snprintf(result, l+1, "\"%s\"", qsrc);
+ fs_give((void **)&qsrc);
+ }
+ }
+
+ return(result);
+}
+
+
+/*
+ * Convert a 1, 2, or 3-digit octal string into an 8-bit character.
+ * Only the first three characters of s will be used, and it is ok not
+ * to null-terminate it.
+ */
+int
+read_octal(char **s)
+{
+ register int i, j;
+
+ i = 0;
+ for(j = 0; j < 3 && **s >= '0' && **s < '8' ; (*s)++, j++)
+ i = (i * 8) + (int)(unsigned char)**s - '0';
+
+ return(i);
+}
+
+
+/*
+ * Convert two consecutive HEX digits to an integer. First two
+ * chars pointed to by "s" MUST already be tested for hexness.
+ */
+int
+read_hex(char *s)
+{
+ return(X2C(s));
+}
+
+
+/*
+ * Given a character c, put the 3-digit ascii octal value of that char
+ * in the 2nd argument, which must be at least 3 in length.
+ */
+void
+char_to_octal_triple(int c, char *octal)
+{
+ c &= 0xff;
+
+ octal[2] = (c % 8) + '0';
+ c /= 8;
+ octal[1] = (c % 8) + '0';
+ c /= 8;
+ octal[0] = c + '0';
+}
+
+
+/*
+ * Convert in memory string s to a C-style string, with backslash escapes
+ * like they're used in C character constants.
+ * Also convert leading spaces because read_pinerc deletes those
+ * if not quoted.
+ *
+ * Returns allocated C string version of s.
+ */
+char *
+string_to_cstring(char *s)
+{
+ char *b, *p;
+ int n, i, all_space_so_far = 1;
+
+ if(!s)
+ return(cpystr(""));
+
+ n = 20;
+ b = (char *)fs_get((n+1) * sizeof(char));
+ p = b;
+ *p = '\0';
+ i = 0;
+
+ while(*s){
+ if(*s != SPACE)
+ all_space_so_far = 0;
+
+ if(i + 4 > n){
+ /*
+ * The output string may overflow the output buffer.
+ * Make more room.
+ */
+ n += 20;
+ fs_resize((void **)&b, (n+1) * sizeof(char));
+ p = &b[i];
+ }
+ else{
+ switch(*s){
+ case '\n':
+ *p++ = '\\';
+ *p++ = 'n';
+ i += 2;
+ break;
+
+ case '\r':
+ *p++ = '\\';
+ *p++ = 'r';
+ i += 2;
+ break;
+
+ case '\t':
+ *p++ = '\\';
+ *p++ = 't';
+ i += 2;
+ break;
+
+ case '\b':
+ *p++ = '\\';
+ *p++ = 'b';
+ i += 2;
+ break;
+
+ case '\f':
+ *p++ = '\\';
+ *p++ = 'f';
+ i += 2;
+ break;
+
+ case '\\':
+ *p++ = '\\';
+ *p++ = '\\';
+ i += 2;
+ break;
+
+ case SPACE:
+ if(all_space_so_far){ /* use octal output */
+ *p++ = '\\';
+ char_to_octal_triple(*s, p);
+ p += 3;
+ i += 4;
+ break;
+ }
+ else{
+ /* fall through */
+ }
+
+
+ default:
+ if(*s >= SPACE && *s < '~' && *s != '\"' && *s != '$'){
+ *p++ = *s;
+ i++;
+ }
+ else{ /* use octal output */
+ *p++ = '\\';
+ char_to_octal_triple(*s, p);
+ p += 3;
+ i += 4;
+ }
+
+ break;
+ }
+
+ s++;
+ }
+ }
+
+ *p = '\0';
+ return(b);
+}
+
+
+/*
+ * Convert C-style string, with backslash escapes, into a hex string, two
+ * hex digits per character.
+ *
+ * Returns allocated hexstring version of s.
+ */
+char *
+cstring_to_hexstring(char *s)
+{
+ char *b, *p;
+ int n, i, c;
+
+ if(!s)
+ return(cpystr(""));
+
+ n = 20;
+ b = (char *)fs_get((n+1) * sizeof(char));
+ p = b;
+ *p = '\0';
+ i = 0;
+
+ while(*s){
+ if(i + 2 > n){
+ /*
+ * The output string may overflow the output buffer.
+ * Make more room.
+ */
+ n += 20;
+ fs_resize((void **)&b, (n+1) * sizeof(char));
+ p = &b[i];
+ }
+ else{
+ if(*s == '\\'){
+ s++;
+ switch(*s){
+ case 'n':
+ c = '\n';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'r':
+ c = '\r';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 't':
+ c = '\t';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'v':
+ c = '\v';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'b':
+ c = '\b';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'f':
+ c = '\f';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 'a':
+ c = '\007';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '\\':
+ c = '\\';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '?':
+ c = '?';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '\'':
+ c = '\'';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case '\"':
+ c = '\"';
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ case 0: /* reached end of s too early */
+ c = 0;
+ C2XPAIR(c, p);
+ i += 2;
+ s++;
+ break;
+
+ /* hex number */
+ case 'x':
+ s++;
+ if(isxpair(s)){
+ c = X2C(s);
+ s += 2;
+ }
+ else if(isxdigit((unsigned char)*s)){
+ c = XDIGIT2C(*s);
+ s++;
+ }
+ else
+ c = 0;
+
+ C2XPAIR(c, p);
+ i += 2;
+
+ break;
+
+ /* octal number */
+ default:
+ c = read_octal(&s);
+ C2XPAIR(c, p);
+ i += 2;
+
+ break;
+ }
+ }
+ else{
+ C2XPAIR(*s, p);
+ i += 2;
+ s++;
+ }
+ }
+ }
+
+ *p = '\0';
+ return(b);
+}
+
+
+/*
+ * Convert C-style string, with backslash escapes, into a regular string.
+ * Result goes in dst, which should be as big as src.
+ *
+ */
+void
+cstring_to_string(char *src, char *dst)
+{
+ char *p;
+ int c;
+
+ dst[0] = '\0';
+ if(!src)
+ return;
+
+ p = dst;
+
+ while(*src){
+ if(*src == '\\'){
+ src++;
+ switch(*src){
+ case 'n':
+ *p++ = '\n';
+ src++;
+ break;
+
+ case 'r':
+ *p++ = '\r';
+ src++;
+ break;
+
+ case 't':
+ *p++ = '\t';
+ src++;
+ break;
+
+ case 'v':
+ *p++ = '\v';
+ src++;
+ break;
+
+ case 'b':
+ *p++ = '\b';
+ src++;
+ break;
+
+ case 'f':
+ *p++ = '\f';
+ src++;
+ break;
+
+ case 'a':
+ *p++ = '\007';
+ src++;
+ break;
+
+ case '\\':
+ *p++ = '\\';
+ src++;
+ break;
+
+ case '?':
+ *p++ = '?';
+ src++;
+ break;
+
+ case '\'':
+ *p++ = '\'';
+ src++;
+ break;
+
+ case '\"':
+ *p++ = '\"';
+ src++;
+ break;
+
+ case 0: /* reached end of s too early */
+ src++;
+ break;
+
+ /* hex number */
+ case 'x':
+ src++;
+ if(isxpair(src)){
+ c = X2C(src);
+ src += 2;
+ }
+ else if(isxdigit((unsigned char)*src)){
+ c = XDIGIT2C(*src);
+ src++;
+ }
+ else
+ c = 0;
+
+ *p++ = c;
+
+ break;
+
+ /* octal number */
+ default:
+ c = read_octal(&src);
+ *p++ = c;
+ break;
+ }
+ }
+ else
+ *p++ = *src++;
+ }
+
+ *p = '\0';
+}
+
+
+/*
+ * Quotes /'s and \'s with \
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with backslash quoting added. Any / in the string is
+ * replaced with \/ and any \ is replaced with \\, and any
+ * " is replaced with \".
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+add_backslash_escapes(char *src)
+{
+ return(add_escapes(src, "/\\\"", '\\', "", ""));
+}
+
+
+/*
+ * Undoes backslash quoting of source string.
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with backslash quoting removed or NULL. The string starts
+ * at src and goes until the end of src or until a / is reached. The
+ * / is not included in the string. /'s may be quoted by preceding
+ * them with a backslash (\) and \'s may also be quoted by
+ * preceding them with a \. In fact, \ quotes any character.
+ * Not quite, \nnn is octal escape, \xXX is hex escape.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+remove_backslash_escapes(char *src)
+{
+ char *ans = NULL, *q, *p;
+ int done = 0;
+
+ if(src){
+ p = q = (char *)fs_get(strlen(src) + 1);
+
+ while(!done){
+ switch(*src){
+ case '\\':
+ src++;
+ if(*src){
+ if(isdigit((unsigned char)*src))
+ *p++ = (char)read_octal(&src);
+ else if((*src == 'x' || *src == 'X') &&
+ *(src+1) && *(src+2) && isxpair(src+1)){
+ *p++ = (char)read_hex(src+1);
+ src += 3;
+ }
+ else
+ *p++ = *src++;
+ }
+
+ break;
+
+ case '\0':
+ case '/':
+ done++;
+ break;
+
+ default:
+ *p++ = *src++;
+ break;
+ }
+ }
+
+ *p = '\0';
+
+ ans = cpystr(q);
+ fs_give((void **)&q);
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Quote values for viewer-hdr-colors. We quote backslash, comma, and slash.
+ * Also replaces $ with $$.
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with backslash quoting added.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+add_viewerhdr_escapes(char *src)
+{
+ char *tmp, *ans = NULL;
+
+ tmp = add_escapes(src, "/\\", '\\', ",", "");
+
+ if(tmp){
+ ans = dollar_escape_dollars(tmp);
+ fs_give((void **) &tmp);
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Quote dollar sign by preceding it with another dollar sign. We use $$
+ * instead of \$ so that it will work for both PC-Pine and unix.
+ *
+ * Args: src -- The source string.
+ *
+ * Returns: A string with $$ quoting added.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+dollar_escape_dollars(char *src)
+{
+ return(add_escapes(src, "$", '$', "", ""));
+}
+
+
+/*
+ * This adds the quoting for vcard backslash quoting.
+ * That is, commas are backslashed, backslashes are backslashed,
+ * semicolons are backslashed, and CRLFs are \n'd.
+ * This is thought to be correct for draft-ietf-asid-mime-vcard-06.txt, Apr 98.
+ */
+char *
+vcard_escape(char *src)
+{
+ char *p, *q;
+
+ q = add_escapes(src, ";,\\", '\\', "", "");
+ if(q){
+ /* now do CRLF -> \n in place */
+ for(p = q; *p != '\0'; p++)
+ if(*p == '\r' && *(p+1) == '\n'){
+ *p++ = '\\';
+ *p = 'n';
+ }
+ }
+
+ return(q);
+}
+
+
+/*
+ * This undoes the vcard backslash quoting.
+ *
+ * In particular, it turns \n into newline, \, into ',', \\ into \, \; -> ;.
+ * In fact, \<anything_else> is also turned into <anything_else>. The ID
+ * isn't clear on this.
+ *
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+vcard_unescape(char *src)
+{
+ char *ans = NULL, *p;
+ int done = 0;
+
+ if(src){
+ p = ans = (char *)fs_get(strlen(src) + 1);
+
+ while(!done){
+ switch(*src){
+ case '\\':
+ src++;
+ if(*src == 'n' || *src == 'N'){
+ *p++ = '\n';
+ src++;
+ }
+ else if(*src)
+ *p++ = *src++;
+
+ break;
+
+ case '\0':
+ done++;
+ break;
+
+ default:
+ *p++ = *src++;
+ break;
+ }
+ }
+
+ *p = '\0';
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Turn folded lines into long lines in place.
+ *
+ * CRLF whitespace sequences are removed, the space is not preserved.
+ */
+void
+vcard_unfold(char *string)
+{
+ char *p = string;
+
+ while(*string) /* while something to copy */
+ if(*string == '\r' &&
+ *(string+1) == '\n' &&
+ (*(string+2) == SPACE || *(string+2) == TAB))
+ string += 3;
+ else
+ *p++ = *string++;
+
+ *p = '\0';
+}
+
+
+/*
+ * Quote specified chars with escape char.
+ *
+ * Args: src -- The source string.
+ * quote_these_chars -- Array of chars to quote
+ * quoting_char -- The quoting char to be used (e.g., \)
+ * hex_these_chars -- Array of chars to hex escape
+ * hex_these_quoted_chars -- Array of chars to hex escape if they are
+ * already quoted with quoting_char (that is,
+ * turn \, into hex comma)
+ *
+ * Returns: An allocated copy of string with quoting added.
+ * The caller is responsible for freeing the memory allocated for the answer.
+ */
+char *
+add_escapes(char *src, char *quote_these_chars, int quoting_char,
+ char *hex_these_chars, char *hex_these_quoted_chars)
+{
+ char *ans = NULL;
+
+ if(!quote_these_chars)
+ panic("bad arg to add_escapes");
+
+ if(src){
+ char *q, *p, *qchar;
+
+ p = q = (char *)fs_get(2*strlen(src) + 1);
+
+ while(*src){
+ if(*src == quoting_char)
+ for(qchar = hex_these_quoted_chars; *qchar != '\0'; qchar++)
+ if(*(src+1) == *qchar)
+ break;
+
+ if(*src == quoting_char && *qchar){
+ src++; /* skip quoting_char */
+ *p++ = '\\';
+ *p++ = 'x';
+ C2XPAIR(*src, p);
+ src++; /* skip quoted char */
+ }
+ else{
+ for(qchar = quote_these_chars; *qchar != '\0'; qchar++)
+ if(*src == *qchar)
+ break;
+
+ if(*qchar){ /* *src is a char to be quoted */
+ *p++ = quoting_char;
+ *p++ = *src++;
+ }
+ else{
+ for(qchar = hex_these_chars; *qchar != '\0'; qchar++)
+ if(*src == *qchar)
+ break;
+
+ if(*qchar){ /* *src is a char to be escaped */
+ *p++ = '\\';
+ *p++ = 'x';
+ C2XPAIR(*src, p);
+ src++;
+ }
+ else /* a regular char */
+ *p++ = *src++;
+ }
+ }
+
+ }
+
+ *p = '\0';
+
+ ans = cpystr(q);
+ fs_give((void **)&q);
+ }
+
+ return(ans);
+}
+
+
+/*
+ * Copy a string enclosed in "" without fixing \" or \\. Skip past \"
+ * but copy it as is, removing only the enclosing quotes.
+ */
+char *
+copy_quoted_string_asis(char *src)
+{
+ char *q, *p;
+ int done = 0, quotes = 0;
+
+ if(src){
+ p = q = (char *)fs_get(strlen(src) + 1);
+
+ while(!done){
+ switch(*src){
+ case QUOTE:
+ if(++quotes == 2)
+ done++;
+ else
+ src++;
+
+ break;
+
+ case BSLASH: /* don't count \" as a quote, just copy */
+ if(*(src+1) == QUOTE){
+ if(quotes == 1){
+ *p++ = *src;
+ *p++ = *(src+1);
+ }
+
+ src += 2;
+ }
+ else{
+ if(quotes == 1)
+ *p++ = *src;
+
+ src++;
+ }
+
+ break;
+
+ case '\0':
+ fs_give((void **)&q);
+ return(NULL);
+
+ default:
+ if(quotes == 1)
+ *p++ = *src;
+
+ src++;
+
+ break;
+ }
+ }
+
+ *p = '\0';
+ }
+
+ return(q);
+}
+
+
+/*
+ * isxpair -- return true if the first two chars in string are
+ * hexidecimal characters
+ */
+int
+isxpair(char *s)
+{
+ return(isxdigit((unsigned char) *s) && isxdigit((unsigned char) *(s+1)));
+}
+
+
+
+
+
+/*
+ * * * * * * * something to help managing lists of strings * * * * * * * *
+ */
+
+
+STRLIST_S *
+new_strlist(char *name)
+{
+ STRLIST_S *sp = (STRLIST_S *) fs_get(sizeof(STRLIST_S));
+ memset(sp, 0, sizeof(STRLIST_S));
+ if(name)
+ sp->name = cpystr(name);
+
+ return(sp);
+}
+
+
+STRLIST_S *
+copy_strlist(STRLIST_S *src)
+{
+ STRLIST_S *ret = NULL, *sl, *ss, *new_sl;
+
+ if(src){
+ ss = NULL;
+ for(sl = src; sl; sl = sl->next){
+ new_sl = (STRLIST_S *) fs_get(sizeof(*new_sl));
+ memset((void *) new_sl, 0, sizeof(*new_sl));
+ if(sl->name)
+ new_sl->name = cpystr(sl->name);
+
+ if(ss){
+ ss->next = new_sl;
+ ss = ss->next;
+ }
+ else{
+ ret = new_sl;
+ ss = ret;
+ }
+ }
+ }
+
+ return(ret);
+}
+
+
+/*
+ * Add the second list to the end of the first.
+ */
+void
+combine_strlists(STRLIST_S **first, STRLIST_S *second)
+{
+ STRLIST_S *sl;
+
+ if(!second)
+ return;
+
+ if(first){
+ if(*first){
+ for(sl = *first; sl->next; sl = sl->next)
+ ;
+
+ sl->next = second;
+ }
+ else
+ *first = second;
+ }
+}
+
+
+void
+free_strlist(STRLIST_S **strp)
+{
+ if(strp && *strp){
+ if((*strp)->next)
+ free_strlist(&(*strp)->next);
+
+ if((*strp)->name)
+ fs_give((void **) &(*strp)->name);
+
+ fs_give((void **) strp);
+ }
+}