#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: strings.c,v 4.118 1999/01/29 02:05:56 hubert Exp $";
#endif
/*----------------------------------------------------------------------

            T H E    P I N E    M A I L   S Y S T E M

   Laurence Lundblade and Mike Seibel
   Networks and Distributed Computing
   Computing and Communications
   University of Washington
   Administration Builiding, AG-44
   Seattle, Washington, 98195, USA
   Internet: lgl@CAC.Washington.EDU
             mikes@CAC.Washington.EDU

   Please address all bugs and comments to "pine-bugs@cac.washington.edu"


   Pine and Pico are registered trademarks of the University of Washington.
   No commercial use of these trademarks may be made without prior written
   permission of the University of Washington.

   Pine, Pico, and Pilot software and its included text are Copyright
   1989-1999 by the University of Washington.

   The full text of our legal notices is contained in the file called
   CPYRIGHT, included with this distribution.


   Pine is in part based on The Elm Mail System:
    ***********************************************************************
    *  The Elm Mail System  -  Revision: 2.13                             *
    *                                                                     *
    * 			Copyright (c) 1986, 1987 Dave Taylor              *
    * 			Copyright (c) 1988, 1989 USENET Community Trust   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/

/*======================================================================
    strings.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 
      - 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
      - sstrcpy        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
      - pretty_command Return nice string describing character
      - repeat_char    Returns a string n chars long
      - comatose       Format number with nice commas
      - 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
      - add_backslash_escapes    Escape / and \ with \
      - remove_backslash_escapes Undo the \ escaping, and stop string at /.

 ====*/

#include "headers.h"

typedef enum {Subj, From, To, Cc, News, Sender, ResentTo} SrchType;
typedef struct role_args {
    SrchType type;
    char    *ourcharset;
    int      multi;
    char   **cset;
} ROLE_ARGS_T;

char       *add_escapes PROTO((char *, char *, int));
void        char_to_octal_triple PROTO((int, char *));
int         read_octal PROTO((char **));
char       *copy_quoted_string_asis PROTO((char *));
PAT_S      *first_any_pattern PROTO((PAT_HANDLE *));
PAT_S      *last_any_pattern PROTO((PAT_HANDLE *));
PAT_S      *prev_any_pattern PROTO((PAT_HANDLE *));
PAT_S      *next_any_pattern PROTO((PAT_HANDLE *));
int         write_pattern_file PROTO((char **, PAT_LINE_S *));
int         write_pattern_lit PROTO((char **, PAT_LINE_S *));
char       *data_for_patline PROTO((PAT_S *));
PAT_LINE_S *parse_pat_lit PROTO((char *));
PAT_S      *parse_pat PROTO((char *));
PATTERN_S  *parse_pattern PROTO((char *, char *));
void        free_pat_list PROTO((PAT_S **));
void        free_patline_list PROTO((PAT_LINE_S **));
void        free_patline PROTO((PAT_LINE_S **));
void        free_patgrp PROTO((PATGRP_S **));
void        free_action PROTO((ACTION_S **));
void        free_pattern PROTO((PATTERN_S **));
void        set_up_search_pgm PROTO((PATTERN_S *, SEARCHPGM *, ROLE_ARGS_T *));
void        add_type_to_pgm PROTO((PATTERN_S *, SEARCHPGM *, ROLE_ARGS_T *));
void        set_srch PROTO((char *, SEARCHPGM *, ROLE_ARGS_T *));
void        set_srch_list PROTO((char *, SEARCHPGM *, ROLE_ARGS_T *));
void        set_srch_hdr PROTO((char *, SEARCHPGM *, ROLE_ARGS_T *));
long        mail_search_att PROTO((MAILSTREAM *, unsigned long, char *,
				   SEARCHPGM *));
int         is_ascii_string PROTO((char *));


/*
 * Useful def's to help with HEX string conversions
 */
#define	XDIGIT2C(C)	((C) - (isdigit((unsigned char) (C)) \
			  ? '0' : (isupper((unsigned char)(C))? '7' : 'W')))
#define	X2C(S)		((XDIGIT2C(*(S)) << 4) | XDIGIT2C(*((S)+1)))
#define	XCHARS		"0123456789ABCDEF"
#define	C2XPAIR(C, S)	{ \
			    *(S)++ = XCHARS[((C) & 0xff) >> 4]; \
			    *(S)++ = XCHARS[(C) & 0xf]; \
			}





/*----------------------------------------------------------------------
       Replace n characters in one string with another given string

   args: os -- the output string
         dl -- the number of character to delete from start of os
         is -- The string to insert
  
 Result: returns pointer in originl string to end of string just inserted
         First 
  ---*/
char *
rplstr(os,dl,is)
char *os,*is;
int dl;
{   
    register char *x1,*x2,*x3;
    int           diff;

    if(os == NULL)
        return(NULL);
       
    for(x1 = os; *x1; x1++);
    if(dl > x1 - os)
        dl = x1 - os;
        
    x2 = is;      
    if(is != NULL){
        while(*x2++);
        x2--;
    }

    if((diff = (x2 - is) - dl) < 0){
        x3 = os; /* String shrinks */
        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;
    } else {                
        /* String grows */
        for(x3 = x1 + diff; x3 >= os + (x2 - is); *x3-- = *x1--); /* shift*/
        for(x1 = os, x2 = is; *x2 ; *x1++ = *x2++);
        while(*x3) x3++;                 
    }
    return(x3);
}



/*----------------------------------------------------------------------
     Squeeze out blanks 
  ----------------------------------------------------------------------*/
void
sqzspaces(string)
     char *string;
{
    char *p = string;

    while(*string = *p++)		   /* 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(string)
    char *string;
{
    char *p = string;

    while(*string = *p++)		      /* 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(string)
     char *string;
{
    register char *p;

    if(!string)
      return;

    for(p = string; *p; p++)		/* find the first non-blank  */
      if(!isspace((unsigned char) *p)){
	  while(*string++ = *p++)	/* 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(string)
    char *string;
{
    char *p = NULL;

    if(!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(string)
     char *string;
{
    register char *p, *q = NULL;

    if(!string)
      return;

    for(p = string; *p; p++)		/* find the first non-blank  */
      if(!isspace((unsigned char)*p)){
	  while(*string = *p++){	/* copy back from there... */
	      q = (!isspace((unsigned char)*string)) ? NULL : (!q) ? string : q;
	      string++;
	  }

	  if(q)
	    *q = '\0';
	    
	  return;
      }

    if(*string != '\0')
      *string = '\0';
}


/*----------------------------------------------------------------------  
       Remove one set of double quotes surrounding string in place
  
  Args: string -- string to remove quotes from
  ----*/
void
removing_double_quotes(string)
     char *string;
{
    register char *p;

    if(string && string[0] == '"' && string[1] != '\0'){
	p = string + strlen(string) - 1;
	if(*p == '"'){
	    *p = '\0';
	    for(p = string; *p; p++) 
	      *p = *(p+1);
	}
    }
}



/*----------------------------------------------------------------------  
  return a pointer to first non-whitespace char in string
  
  Args: string -- string to scan
  ----*/
char *
skip_white_space(string)
     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(string)
     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(string)
    char *string;
{
    register char *p, *q;

    if(*(p = q = string) == '\"'){
	do
	  if(*q == '\"' || *q == '\\')
	    q++;
	while(*p++ = *q++);
    }

    return(string);
}



/*---------------------------------------------------
     Remove leading whitespace, trailing whitespace and convert 
     to lowercase

   Args: s, -- The string to clean

 Result: the cleaned string
  ----*/
char *
strclean(string)
     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);
}



/*----------------------------------------------------------------------
        Search one string for another

   Args:  is -- The string to search in, the larger string
          ss -- The string to search for, the smaller string

   Search for first occurrence of ss in the is, and return a pointer
   into the string is when it is found. The search is case indepedent.
  ----*/
char *	    
srchstr(is, ss)
    char *is, *ss;
{
    register char *p, *q;

    if(ss && is)
      for(; *is; is++)
	for(p = ss, q = is; ; p++, q++){
	    if(!*p)
	      return(is);			/* winner! */
	    else if(!*q)
	      return(NULL);			/* len(ss) > len(is)! */
	    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(is, ss)
register char *is, *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(buffer, ch)
    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(buffer, ch)
    char *buffer;
    int   ch;
{
    char *address = NULL;

    do
      if(*buffer == ch)
	address = buffer;
    while (*buffer++ != '\0');
    return(address);
}



/*----------------------------------------------------------------------
  copy the source string onto the destination string returning with
  the destination string pointer at the end of the destination text

  motivation for this is to avoid twice passing over a string that's
  being appended to twice (i.e., strcpy(t, x); t += strlen(t))
 ----*/
void
sstrcpy(d, s)
    char **d;
    char *s;
{
    while((**d = *s++) != '\0')
      (*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(d, s, n)
    char *d, *s;
    int n;
{
    char *rv = d;

    do
      if(F_OFF(F_PASS_CONTROL_CHARS, ps_global) && *s && CAN_DISPLAY(*s))
	if(n-- >= 0){
	    *d++ = '^';

	    if(n-- >= 0)
	      *d++ = *s++ + '@';
	}
    while(n-- >= 0 && (*d++ = *s++));

    return(rv);
}



char *xdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL};

char *
month_abbrev(month_num)
     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_name(month_num)
     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 *
week_abbrev(week_day)
     int week_day;
{
    if(week_day < 0 || week_day > 6)
      return("???");
    return(xdays[week_day]);
}


/*----------------------------------------------------------------------
      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(s)
     char *s;
{
    int month, year;
    int i;

    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 < 100 ? year + 1900 : 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(given_date, d)
     char        *given_date;
     struct date *d;
{
    char *p, **i, *q, n;
    int   month;

    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 month, weekday or day ? */
    for(i = xdays; *i != NULL; i++) 
      if(struncmp(p, *i, 3) == 0) /* Match first 3 letters */
        break;
    if(*i != NULL) {
        /* Started with week day */
        d->wkday = i - xdays;
        while(*p && !isspace((unsigned char)*p) && *p != ',')
          p++;
        while(*p && (isspace((unsigned char)*p) || *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;
	      }
        }
    }
    dprint(9, (debugfile,
	 "Parse date: \"%s\" to..  hours_off_gmt:%d  min_off_gmt:%d\n",
               given_date, d->hours_off_gmt, d->min_off_gmt));
    dprint(9, (debugfile,
	       "Parse date: wkday:%d  month:%d  year:%d  day:%d  hour:%d  min:%d  sec:%d\n",
            d->wkday, d->month, d->year, d->day, d->hour, d->minute, d->sec));
}



/*----------------------------------------------------------------------
     Map some of the special characters into sensible strings for human
   consumption.
  ----*/
char *
pretty_command(c)
     int c;
{
    static char  buf[10];
    char	*s;

    switch(c){
      case '\033'    : s = "ESC";		break;
      case '\177'    : s = "DEL";		break;
      case ctrl('I') : s = "TAB";		break;
      case ctrl('J') : s = "LINEFEED";		break;
      case ctrl('M') : s = "RETURN";		break;
      case ctrl('Q') : s = "XON";		break;
      case ctrl('S') : s = "XOFF";		break;
      case KEY_UP    : s = "Up Arrow";		break;
      case KEY_DOWN  : s = "Down Arrow";	break;
      case KEY_RIGHT : s = "Right Arrow";	break;
      case KEY_LEFT  : s = "Left Arrow";	break;
      case KEY_PGUP  : s = "Prev Page";		break;
      case KEY_PGDN  : s = "Next Page";		break;
      case KEY_HOME  : s = "Home";		break;
      case KEY_END   : s = "End";		break;
      case KEY_DEL   : s = "Delete";		break; /* Not necessary DEL! */
      case PF1	     :
      case PF2	     :
      case PF3	     :
      case PF4	     :
      case PF5	     :
      case PF6	     :
      case PF7	     :
      case PF8	     :
      case PF9	     :
      case PF10	     :
      case PF11	     :
      case PF12	     :
        sprintf(s = buf, "F%d", c - PF1 + 1);
	break;

      default:
	if(c < ' ')
	  sprintf(s = buf, "^%c", c + 'A' - 1);
	else
	  sprintf(s = buf, "%c", c);

	break;
    }

    return(s);
}
        
    

/*----------------------------------------------------------------------
     Create a little string of blanks of the specified length.
   Max n is 511.
  ----*/
char *
repeat_char(n, c)
     int  n;
     int  c;
{
    static char bb[512];
    if(n > sizeof(bb))
       n = sizeof(bb) - 1;
    bb[n--] = '\0';
    while(n >= 0)
      bb[n--] = c;
    return(bb);
}


/*----------------------------------------------------------------------
        Turn a number into a string with comma's

   Args: number -- The long to be turned into a string. 

  Result: pointer to static string representing number with commas
  ---*/
char *
comatose(number) 
    long number;
{
#ifdef	DOS
    static char buf[3][16];
    static int whichbuf = 0;
    char *b;
    short i;

    if(!number)
	return("0");

    whichbuf = (whichbuf + 1) % 3;

    if(number < 0x7FFFFFFFL){		/* largest DOS signed long */
        buf[whichbuf][15] = '\0';
        b = &buf[whichbuf][14];
        i = 2;
	while(number){
 	    *b-- = (number%10) + '0';
	    if((number /= 10) && i-- == 0 ){
		*b-- = ',';
		i = 2;
	    }
	}
    }
    else
      return("Number too big!");		/* just fits! */

    return(++b);
#else
    long        i, x, done_one;
    static char buf[3][50];
    static int whichbuf = 0;
    char       *b;

    whichbuf = (whichbuf + 1) % 3;
    dprint(9, (debugfile, "comatose(%ld) returns:", number));
    if(number == 0){
        strcpy(buf[whichbuf], "0");
        return(buf[whichbuf]);
    }
    
    done_one = 0;
    b = buf[whichbuf];
    for(i = 1000000000; i >= 1; i /= 1000) {
	x = number / i;
	number = number % i;
	if(x != 0 || done_one) {
	    if(b != buf[whichbuf])
	      *b++ = ',';
	    sprintf(b, done_one ? "%03ld" : "%d", x);
	    b += strlen(b);
	    done_one = 1;
	}
    }
    *b = '\0';

    dprint(9, (debugfile, "\"%s\"\n", buf[whichbuf]));

    return(buf[whichbuf]);
#endif	/* DOS */
}



/*----------------------------------------------------------------------
   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(bytes)
     long bytes;
{
    char       *a, aa[5];
    char       *abbrevs = "GMK";
    long        i, ones, tenths;
    static char string[10];

    ones   = 0L;
    tenths = 0L;

    if(bytes == 0L){
        strcpy(string, "0 bytes");
    } 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)
          sprintf(string, "%ld%s%s", ones, aa, *a ? "B" : "bytes");
        else
          sprintf(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(i)
     int i;
{
    static char enth[10];

    switch (i % 10) {
        
      case 1:
        if( (i % 100 ) == 11)
          sprintf(enth,"%dth", i);
        else
          sprintf(enth,"%dst", i);
        break;

      case 2:
        if ((i % 100) == 12)
          sprintf(enth, "%dth",i);
        else
          sprintf(enth, "%dnd",i);
        break;

      case 3:
        if(( i % 100) == 13)
          sprintf(enth, "%dth",i);
        else
          sprintf(enth, "%drd",i);
        break;

      default:
        sprintf(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.
 *                cr -- End of line is \r\n instead of just \n.
 *       preserve_ws -- Preserve whitespace when folding. This is for vcard
 *                       folding where CRLF SPACE is removed when unfolding, so
 *                       we need to leave the space in. With rfc822 unfolding
 *                       only the CRLF is removed when unfolding.
 *      first_indent -- String to use as indent on first line.
 *            indent -- String to use as indent for subsequent folded lines.
 *
 * Returns   An allocated string which caller should free.
 */
char *
fold(src, width, maxwidth, cr, preserve_ws, first_indent, indent)
    char *src;
    int   width,
	  maxwidth,
	  cr,
	  preserve_ws;
    char *first_indent,
	 *indent;
{
    char *next_piece, *res, *p;
    int   i, len, nb, starting_point, shorter, longer, winner, eol;
    int   indent1 = 0, indent2 = 0;
    char  save_char;

    if(indent)
      indent2 = strlen(indent);

    if(first_indent)
      indent1 = strlen(first_indent);

    nb = indent1;
    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 += indent2;
	}

	if(strlen(next_piece) + 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 > len + 8
		      || starting_point + i < maxwidth - width);
		i++){

		if((shorter=starting_point-i) > len + 8
		   && isspace((unsigned char)next_piece[shorter]))
		  winner = shorter;
		else if((longer=starting_point+i) < maxwidth - width
			&& (!next_piece[longer]
		            || isspace((unsigned char)next_piece[longer])))
		  winner = longer;
	    }

	    if(winner == -1) /* if no good folding spot, fold at width */
	      winner = starting_point;
	    
	    nb += (winner + eol);
	    next_piece += winner;
	    if(!preserve_ws && isspace((unsigned char)next_piece[0]))
	      next_piece++;
	}

	len = 0;
    }

    res = (char *)fs_get((nb+1) * sizeof(char));
    p = res;
    sstrcpy(&p, first_indent);
    len = indent1;
    next_piece = src;

    while(next_piece && *next_piece){
	if(next_piece != src && indent2){
	    sstrcpy(&p, indent);
	    len += indent2;
	}

	if(strlen(next_piece) + len <= width){
	    sstrcpy(&p, next_piece);
	    if(cr)
	      *p++ = '\r';

	    *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 > len + 8
		      || starting_point + i < maxwidth - width);
		i++){

		if((shorter=starting_point-i) > len + 8
		   && isspace((unsigned char)next_piece[shorter]))
		  winner = shorter;
		else if((longer=starting_point+i) < maxwidth - width
			&& (!next_piece[longer]
		            || isspace((unsigned char)next_piece[longer])))
		  winner = longer;
	    }

	    if(winner == -1) /* if no good folding spot, fold at width */
	      winner = starting_point;
	    
	    save_char = next_piece[winner];
	    next_piece[winner] = '\0';
	    sstrcpy(&p, next_piece);
	    if(cr)
	      *p++ = '\r';

	    *p++ = '\n';
	    next_piece[winner] = save_char;
	    next_piece += winner;
	    if(!preserve_ws && isspace((unsigned char)next_piece[0]))
	      next_piece++;
	}

	len = 0;
    }

    if(!src || !*src){
	if(cr)
	  *p++ = '\r';

	*p++ = '\n';
    }

    *p = '\0';

    return(res);
}


/*
 * strsquish - fancifies a string into the given buffer if it's too
 *	       long to fit in the given width
 */
char *
strsquish(buf, s, width)
    char *buf, *s;
    int   width;
{
    int i, offset;

    if(width > 0){
	if((i = strlen(s)) <= width)
	  return(s);

	if(width > 14){
	    strncpy(buf, s, offset = ((width / 2) - 2));
	    strcpy(buf + offset, "...");
	    offset += 3;
	}
	else if(width > 3){
	    strcpy(buf, "...");
	    offset = 3;
	}
	else
	  offset = 0;

	strcpy(buf + offset, s + (i - width) + offset);
	return(buf);
    }
    else
      return("");
}


char *
long2string(l)
     long l;
{
    static char string[20];
    sprintf(string, "%ld", l);
    return(string);
}

char *
int2string(i)
     int i;
{
    static char string[20];
    sprintf(string, "%d", i);
    return(string);
}


/*
 * strtoval - convert the given string to a positive integer.
 */
char *
strtoval(s, val, minmum, maxmum, errbuf, varname)
    char *s;
    int  *val;
    int   minmum, maxmum;
    char *errbuf, *varname;
{
    int   i = 0;
    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{
	  sprintf(errstr = errbuf,
		  "Non-numeric value ('%c' in \"%.8s\") in %s. Using \"%d\"",
		  *p, s, varname, *val);
	  return(errbuf);
      }

    /* range describes acceptable values */
    if(maxmum > minmum && (i < minmum || i > maxmum))
      sprintf(errstr = errbuf,
	      "%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))
      sprintf(errstr = errbuf, "%s of %d not supported. Using \"%d\"",
	      varname, i, *val);
    else
      *val = i;

    return(errstr);
}


/*
 *  Function to parse the given string into two space-delimited fields
 */
void
get_pair(string, label, value, firstws)
    char *string, **label, **value;
    int   firstws;
{
    char *p, *token = NULL;
    int	  quoted = 0;

    *label = *value = NULL;
    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){
	*label = p = (char *)fs_get(((token - string) + 1) * sizeof(char));
	for(; string < token ; string++){
	    if(*string == '\\' && *(string+1) == '"')
	      *p++ = *++string;
	    else if(*string != '"')
	      *p++ = *string;
	}

	*p = '\0';				/* tie off nickname */
	removing_trailing_white_space(*label);
	*value = cpystr(token);
    }
    else
      *value = cpystr(string ? string : "");
}


/*
 * 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(s)
    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(s)
    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(c, octal)
    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.
 *
 * Returns allocated C string version of s.
 */
char *
string_to_cstring(s)
    char *s;
{
    char *b, *p;
    int   n, i;

    if(!s)
      return(cpystr(""));

    n = 20;
    b = (char *)fs_get((n+1) * sizeof(char));
    p  = b;
    *p = '\0';
    i  = 0;

    while(*s){
	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;

	      default:
		if(*s >= SPACE && *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(s)
    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);
}


/*
 * 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(src)
    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(src)
    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(src)
    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(string)
    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., \)
 *
 * Returns: An allocated copy of string with quoting added.
 *   The caller is responsible for freeing the memory allocated for the answer.
 */
char *
add_escapes(src, quote_these_chars, quoting_char)
    char *src;
    char *quote_these_chars;
    int   quoting_char;
{
    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){
	    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			/* a regular char */
	      *p++ = *src++;
	}

	*p = '\0';

	ans = cpystr(q);
	fs_give((void **)&q);
    }

    return(ans);
}


/*
 * 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.
 *
 *   The caller is responsible for freeing the memory allocated for the answer.
 */
char *
remove_backslash_escapes(src)
    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++;
		*p++ = *src++;
		break;
	    
	      case '\0':
	      case '/':
		done++;
		break;

	      default:
		*p++ = *src++;
		break;
	    }
	}

	*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(src)
    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);
}


/*
 * Returns non-zero if dir is a prefix of path.
 *         zero     if dir is not a prefix of path, or if dir is empty.
 */
int
in_dir(dir, path)
    char *dir;
    char *path;
{
    return(*dir ? !strncmp(dir, path, strlen(dir)) : 0);
}


/*
 * isxpair -- return true if the first two chars in string are
 *	      hexidecimal characters
 */
int
isxpair(s)
    char *s;
{
    return(isxdigit((unsigned char) *s) && isxdigit((unsigned char) *(s+1)));
}





/*
 *  * * * * * *  something to help managing lists of strings   * * * * * * * *
 */


STRLIST_S *
new_strlist()
{
    STRLIST_S *sp = (STRLIST_S *) fs_get(sizeof(STRLIST_S));
    memset(sp, 0, sizeof(STRLIST_S));
    return(sp);
}



void
free_strlist(strp)
    STRLIST_S **strp;
{
    if(*strp){
	if((*strp)->next)
	  free_strlist(&(*strp)->next);

	if((*strp)->name)
	  fs_give((void **) &(*strp)->name);

	fs_give((void **) strp);
    }
}



/*
 *  * * * * * * * *      RFC 1522 support routines      * * * * * * * *
 *
 *   RFC 1522 support is *very* loosely based on code contributed
 *   by Lars-Erik Johansson <lej@cdg.chalmers.se>.  Thanks to Lars-Erik,
 *   and appologies for taking such liberties with his code.
 */


#define	RFC1522_INIT	"=?"
#define	RFC1522_INIT_L	2
#define RFC1522_TERM	"?="
#define	RFC1522_TERM_L	2
#define	RFC1522_DLIM	"?"
#define	RFC1522_DLIM_L	1
#define	RFC1522_MAXW	75
#define	ESPECIALS	"()<>@,;:\"/[]?.="
#define	RFC1522_OVERHEAD(S)	(RFC1522_INIT_L + RFC1522_TERM_L +	\
				 (2 * RFC1522_DLIM_L) + strlen(S) + 1);
#define	RFC1522_ENC_CHAR(C)	(((C) & 0x80) || !rfc1522_valtok(C)	\
				 || (C) == '_' )


int	       rfc1522_token PROTO((char *, int (*) PROTO((int)), char *,
				    char **));
int	       rfc1522_valtok PROTO((int));
int	       rfc1522_valenc PROTO((int));
int	       rfc1522_valid PROTO((char *, char **, char **, char **,
				    char **));
char	      *rfc1522_8bit PROTO((void *, int));
char	      *rfc1522_binary PROTO((void *, int));
unsigned char *rfc1522_encoded_word PROTO((unsigned char *, int, char *));


/*
 * rfc1522_decode - decode the given source string ala RFC 2047 (nee 1522),
 *		    IF NECESSARY, into the given destination buffer.
 *		    Don't bother copying if it turns out decoding
 *		    isn't necessary.
 *
 * Returns: pointer to either the destination buffer containing the
 *	    decoded text, or a pointer to the source buffer if there was
 *	    no reason to decode it.
 */
unsigned char *
rfc1522_decode(d, s, charset)
    unsigned char  *d;
    char	   *s;
    char	  **charset;
{
    unsigned char *rv = NULL, *p;
    char	  *start = s, *sw, *cset, *enc, *txt, *ew, **q, *lang;
    unsigned long  l;
    int		   i;

    *d = '\0';					/* init destination */
    if(charset)
      *charset = NULL;

    while(s && (sw = strstr(s, RFC1522_INIT))){
	/* validate the rest of the encoded-word */
	if(rfc1522_valid(sw, &cset, &enc, &txt, &ew)){
	    if(!rv)
	      rv = d;				/* remember start of dest */

	    /* copy everything between s and sw to destination */
	    for(i = 0; &s[i] < sw; i++)
	      if(!isspace((unsigned char)s[i])){ /* if some non-whitespace */
		  while(s < sw)
		    *d++ = (unsigned char) *s++;

		  break;
	      }

	    enc[-1] = txt[-1] = ew[0] = '\0';	/* tie off token strings */

	    if(lang = strchr(cset, '*'))
	      *lang++ = '\0';

	    /* Insert text explaining charset if we don't know what it is */
	    if((!ps_global->VAR_CHAR_SET
		|| strucmp((char *) cset, ps_global->VAR_CHAR_SET))
	       && strucmp((char *) cset, "US-ASCII")){
		dprint(5, (debugfile, "RFC1522_decode: charset mismatch: %s\n",
			   cset));
		if(charset){
		    if(!*charset)		/* only write first charset */
		      *charset = cpystr(cset);
		}
		else{
		    *d++ = '[';
		    sstrcpy((char **) &d, cset);
		    *d++ = ']';
		    *d++ = SPACE;
		}
	    }

	    /* based on encoding, write the encoded text to output buffer */
	    switch(*enc){
	      case 'Q' :			/* 'Q' encoding */
	      case 'q' :
		/* special hocus-pocus to deal with '_' exception, too bad */
		for(l = 0L, i = 0; txt[l]; l++)
		  if(txt[l] == '_')
		    i++;

		if(i){
		    q = (char **) fs_get((i + 1) * sizeof(char *));
		    for(l = 0L, i = 0; txt[l]; l++)
		      if(txt[l] == '_'){
			  q[i++] = &txt[l];
			  txt[l] = SPACE;
		      }

		    q[i] = NULL;
		}
		else
		  q = NULL;

		if(p = rfc822_qprint((unsigned char *)txt, strlen(txt), &l)){
		    strcpy((char *) d, (char *) p);
		    fs_give((void **)&p);	/* free encoded buf */
		    d += l;			/* advance dest ptr to EOL */
		}
		else{
		    if(q)
		      fs_give((void **) &q);

		    goto bogus;
		}

		if(q){				/* restore underscores */
		    for(i = 0; q[i]; i++)
		      *(q[i]) = '_';

		    fs_give((void **)&q);
		}

		break;

	      case 'B' :			/* 'B' encoding */
	      case 'b' :
		if(p = rfc822_base64((unsigned char *) txt, strlen(txt), &l)){
		    strcpy((char *) d, (char *) p);
		    fs_give((void **)&p);	/* free encoded buf */
		    d += l;			/* advance dest ptr to EOL */
		}
		else
		  goto bogus;

		break;

	      default:
		sstrcpy((char **) &d, txt);
		dprint(1, (debugfile, "RFC1522_decode: Unknown ENCODING: %s\n",
			   enc));
		break;
	    }

	    /* restore trompled source string */
	    enc[-1] = txt[-1] = '?';
	    ew[0]   = RFC1522_TERM[0];

	    /* advance s to start of text after encoded-word */
	    s = ew + RFC1522_TERM_L;

	    if(lang)
	      lang[-1] = '*';
	}
	else{
	    /* found intro, but bogus data followed */
	    strncpy((char *) d, s, (int) (l = (sw - s) + RFC1522_INIT_L));
	    *(d += l) = '\0';			/* advance d, tie off text */
	    s += l;				/* advance s beyond intro */
	}
    }

    if(rv && *s)				/* copy remaining text */
      strcat((char *) rv, s);

/* BUG: MUST do code page mapping under DOS after decoding */

    return(rv ? rv : (unsigned char *) start);

  bogus:
    dprint(1, (debugfile, "RFC1522_decode: BOGUS INPUT: -->%s<--\n", start));
    return((unsigned char *) start);
}


/*
 * rfc1522_token - scan the given source line up to the end_str making
 *		   sure all subsequent chars are "valid" leaving endp
 *		   a the start of the end_str.
 * Returns: TRUE if we got a valid token, FALSE otherwise
 */
int
rfc1522_token(s, valid, end_str, endp)
    char  *s;
    int	 (*valid) PROTO((int));
    char  *end_str;
    char **endp;
{
    while(*s){
	if((char) *s == *end_str		/* test for matching end_str */
	   && ((end_str[1])
	        ? !strncmp((char *)s + 1, end_str + 1, strlen(end_str + 1))
	        : 1)){
	    *endp = s;
	    return(TRUE);
	}

	if(!(*valid)(*s++))			/* test for valid char */
	  break;
    }

    return(FALSE);
}


/*
 * rfc1522_valtok - test for valid character in the RFC 1522 encoded
 *		    word's charset and encoding fields.
 */
int
rfc1522_valtok(c)
    int c;
{
    return(!(c == SPACE || iscntrl(c & 0x7f) || strindex(ESPECIALS, c)));
}


/*
 * rfc1522_valenc - test for valid character in the RFC 1522 encoded
 *		    word's encoded-text field.
 */
int
rfc1522_valenc(c)
    int c;
{
    return(!(c == '?' || c == SPACE) && isprint((unsigned char)c));
}


/*
 * rfc1522_valid - validate the given string as to it's rfc1522-ness
 */
int
rfc1522_valid(s, charset, enc, txt, endp)
    char  *s;
    char **charset;
    char **enc;
    char **txt;
    char **endp;
{
    char *c, *e, *t, *p;
    int   rv;

    rv = rfc1522_token(c = s+RFC1522_INIT_L, rfc1522_valtok, RFC1522_DLIM, &e)
	   && rfc1522_token(++e, rfc1522_valtok, RFC1522_DLIM, &t)
	   && rfc1522_token(++t, rfc1522_valenc, RFC1522_TERM, &p)
	   && p - s <= RFC1522_MAXW;

    if(charset)
      *charset = c;

    if(enc)
      *enc = e;

    if(txt)
      *txt = t;

    if(endp)
      *endp = p;

    return(rv);
}


/*
 * rfc1522_encode - encode the given source string ala RFC 1522,
 *		    IF NECESSARY, into the given destination buffer.
 *		    Don't bother copying if it turns out encoding
 *		    isn't necessary.
 *
 * Returns: pointer to either the destination buffer containing the
 *	    encoded text, or a pointer to the source buffer if we didn't
 *          have to encode anything.
 */
char *
rfc1522_encode(d, s, charset)
    char	  *d;
    unsigned char *s;
    char	  *charset;
{
    unsigned char *p, *q;
    int		   n;

    if(!s)
      return((char *) s);

    if(!charset)
      charset = UNKNOWN_CHARSET;

    /* look for a reason to encode */
    for(p = s, n = 0; *p; p++)
      if((*p) & 0x80){
	  n++;
      }
      else if(*p == RFC1522_INIT[0]
	      && !strncmp((char *) p, RFC1522_INIT, RFC1522_INIT_L)){
	  if(rfc1522_valid((char *) p, NULL, NULL, NULL, (char **) &q))
	    p = q + RFC1522_TERM_L - 1;		/* advance past encoded gunk */
      }
      else if(*p == ESCAPE && match_escapes((char *)(p+1))){
	  n++;
      }

    if(n){					/* found, encoding to do */
	char *rv  = d, *t,
	      enc = (n > (2 * (p - s)) / 3) ? 'B' : 'Q';

	while(*s){
	    sstrcpy(&d, RFC1522_INIT);		/* insert intro header, */
	    sstrcpy(&d, charset);		/* character set tag, */
	    sstrcpy(&d, RFC1522_DLIM);		/* and encoding flavor */
	    *d++ = enc;
	    sstrcpy(&d, RFC1522_DLIM);

	    /*
	     * feed lines to encoder such that they're guaranteed
	     * less than RFC1522_MAXW.
	     */
	    p = rfc1522_encoded_word(s, enc, charset);
	    if(enc == 'B')			/* insert encoded data */
	      sstrcpy(&d, t = rfc1522_binary(s, p - s));
	    else				/* 'Q' encoding */
	      sstrcpy(&d, t = rfc1522_8bit(s, p - s));

	    sstrcpy(&d, RFC1522_TERM);		/* insert terminator */
	    fs_give((void **) &t);
	    if(*p)				/* more src string follows */
	      sstrcpy(&d, "\015\012 ");	/* insert continuation line */

	    s = p;				/* advance s */
	}

	return(rv);
    }
    else
      return((char *) s);			/* no work for us here */
}



/*
 * rfc1522_encoded_word -- cut given string into max length encoded word
 *
 * Return: pointer into 's' such that the encoded 's' is no greater
 *	   than RFC1522_MAXW
 *
 *  NOTE: this line break code is NOT cognizant of any SI/SO
 *  charset requirements nor similar strategies using escape
 *  codes.  Hopefully this will matter little and such
 *  representation strategies don't also include 8bit chars.
 */
unsigned char *
rfc1522_encoded_word(s, enc, charset)
    unsigned char *s;
    int		   enc;
    char	  *charset;
{
    int goal = RFC1522_MAXW - RFC1522_OVERHEAD(charset);

    if(enc == 'B')			/* base64 encode */
      for(goal = ((goal / 4) * 3) - 2; goal && *s; goal--, s++)
	;
    else				/* special 'Q' encoding */
      for(; goal && *s; s++)
	if((goal -= RFC1522_ENC_CHAR(*s) ? 3 : 1) < 0)
	  break;

    return(s);
}



/*
 * rfc1522_8bit -- apply RFC 1522 'Q' encoding to the given 8bit buffer
 *
 * Return: alloc'd buffer containing encoded string
 */
char *
rfc1522_8bit(src, slen)
    void *src;
    int   slen;
{
    char *ret = (char *) fs_get ((size_t) (3*slen + 2));
    char *d = ret;
    unsigned char c;
    unsigned char *s = (unsigned char *) src;

    while (slen--) {				/* for each character */
	if (((c = *s++) == '\015') && (*s == '\012') && slen) {
	    *d++ = '\015';			/* true line break */
	    *d++ = *s++;
	    slen--;
	}
	else if(c == SPACE){			/* special encoding case */
	    *d++ = '_';
	}
	else if(RFC1522_ENC_CHAR(c)){
	    *d++ = '=';				/* quote character */
	    C2XPAIR(c, d);
	}
	else
	  *d++ = (char) c;			/* ordinary character */
    }

    *d = '\0';					/* tie off destination */
    return(ret);
}


/*
 * rfc1522_binary -- apply RFC 1522 'B' encoding to the given 8bit buffer
 *
 * Return: alloc'd buffer containing encoded string
 */
char *
rfc1522_binary (src, srcl)
    void *src;
    int   srcl;
{
    static char *v =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    unsigned char *s = (unsigned char *) src;
    char *ret, *d;

    d = ret = (char *) fs_get ((size_t) ((((srcl + 2) / 3) * 4) + 1));
    for (; srcl; s += 3) {	/* process tuplets */
				/* byte 1: high 6 bits (1) */
	*d++ = v[s[0] >> 2];
				/* byte 2: low 2 bits (1), high 4 bits (2) */
	*d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
				/* byte 3: low 4 bits (2), high 2 bits (3) */
	*d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) :0)) & 0x3f] :'=';
				/* byte 4: low 6 bits (3) */
	*d++ = srcl ? v[s[2] & 0x3f] : '=';
	if(srcl)
	  srcl--;		/* count third character if processed */
    }

    *d = '\0';			/* tie off string */
    return(ret);		/* return the resulting string */
}



/*
 *  * * * * * * * *      RFC 1738 support routines      * * * * * * * *
 */


/*
 * Various helpful definitions
 */
#define	RFC1738_SAFE	"$-_.+"			/* "safe" */
#define	RFC1738_EXTRA	"!*'(),"		/* "extra" */
#define	RFC1738_RSVP	";/?:@&="		/* "reserved" */
#define	RFC1738_NEWS	"-.+_"			/* valid for "news:" URL */
#define	RFC1738_FUDGE	"#~"			/* Unsafe, but popular */
#define	RFC1738_ESC(S)	(*(S) == '%' && isxpair((S) + 1))


int   rfc1738uchar PROTO((char *));
int   rfc1738xchar PROTO((char *));
char *rfc1738_scheme_part PROTO((char *));



/*
 * rfc1738_scan -- Scan the given line for possible URLs as defined
 *		   in RFC1738
 */
char *
rfc1738_scan(line, len)
    char *line;
    int  *len;
{
    char *colon, *start, *end;
    int   n;

    /* process each : in the line */
    for(; colon = strindex(line, ':'); line = end){
	end = colon + 1;
	if(colon == line)		/* zero length scheme? */
	  continue;

	/*
	 * Valid URL (ala RFC1738 BNF)?  First, first look to the
	 * left to make sure there are valid "scheme" chars...
	 */
	start = colon - 1;
	while(1)
	  if(!(isdigit((unsigned char) *start)
	       || isalpha((unsigned char) *start)
	       || strchr("+-.", *start))){
	      start++;			/* advance over bogus char */
	      break;
	  }
	  else if(start > line)
	    start--;
	  else
	    break;

	/*
	 * Make sure everyhing up to the colon is a known scheme...
	 */
	if(start && (n = colon - start) && !isdigit((unsigned char) *start)
	   && (((n == 3
		 && (*start == 'F' || *start == 'f')
		 && !struncmp(start+1, "tp", 2))
		|| (n == 4
		    && (((*start == 'H' || *start == 'h') 
			 && !struncmp(start + 1, "ttp", 3))
			|| ((*start == 'N' || *start == 'n')
			    && !struncmp(start + 1, "ews", 3))
			|| ((*start == 'N' || *start == 'n')
			    && !struncmp(start + 1, "ntp", 3))
			|| ((*start == 'W' || *start == 'w')
			    && !struncmp(start + 1, "ais", 3))
#ifdef	ENABLE_LDAP
			|| ((*start == 'L' || *start == 'l')
			    && !struncmp(start + 1, "dap", 3))
#endif
			|| ((*start == 'I' || *start == 'i')
			    && !struncmp(start + 1, "map", 3))
			|| ((*start == 'F' || *start == 'f')
			    && !struncmp(start + 1, "ile", 3))))
		|| (n == 5
		    && (*start == 'H' || *start == 'h')
		    && !struncmp(start+1, "ttps", 4))
		|| (n == 6
		    && (((*start == 'G' || *start == 'g')
			 && !struncmp(start+1, "opher", 5))
			|| ((*start == 'M' || *start == 'm')
			    && !struncmp(start + 1, "ailto", 5))
			|| ((*start == 'T' || *start == 't')
			    && !struncmp(start + 1, "elnet", 5))))
		|| (n == 8
		    && (*start == 'P' || *start == 'p')
		    && !struncmp(start + 1, "rospero", 7)))
	       || url_external_specific_handler(start, n))){
		/*
		 * Second, make sure that everything to the right of the
		 * colon is valid for a "schemepart"...
		 */

	    if((end = rfc1738_scheme_part(colon + 1)) - colon > 1){
		*len = end - start;

		/*
		 * Special case handling for comma.
		 * See the problem is comma's valid, but if it's the
		 * last character in the url, it's likely intended
		 * as a delimiter in the text rather part of the URL.
		 * In most cases any way, that's why we have the
		 * exception.
		 */
		if(*(end - 1) == ','
		   || (*(end - 1) == '.' && (!*end  || *end == ' ')))
		  (*len)--;

		return(start);
	    }
	}
    }

    return(NULL);
}


/*
 * rfc1738_scheme_part - make sure what's to the right of the
 *			 colon is valid
 *
 * NOTE: we have a problem matching closing parens when users 
 *       bracket the url in parens.  So, lets try terminating our
 *	 match on any closing paren that doesn't have a coresponding
 *       open-paren.
 */
char *
rfc1738_scheme_part(s)
    char *s;
{
    int n, paren = 0;

    while(1)
      switch(*s){
	default :
	  if(n = rfc1738xchar(s)){
	      s += n;
	      break;
	  }

	case '\0' :
	  return(s);

	case '(' :
	  paren++;
	  s++;
	  break;

	case ')' :
	  if(paren--){
	      s++;
	      break;
	  }

	  return(s);
      }
}



/*
 * rfc1738_str - convert rfc1738 escaped octets in place
 */
char *
rfc1738_str(s)
    char *s;
{
    register char *p = s, *q = s;

    while(1)
      switch(*q = *p++){
	case '%' :
	  if(isxpair(p)){
	      *q = X2C(p);
	      p += 2;
	  }

	default :
	  q++;
	  break;

	case '\0':
	  return(s);
      }
}


/*
 * rfc1738uchar - returns TRUE if the given char fits RFC 1738 "uchar" BNF
 */
int
rfc1738uchar(s)
    char *s;
{
    return((RFC1738_ESC(s))		/* "escape" */
	     ? 2
	     : (isalnum((unsigned char) *s)	/* alphanumeric */
		|| strchr(RFC1738_SAFE, *s)	/* other special stuff */
		|| strchr(RFC1738_EXTRA, *s)));
}


/*
 * rfc1738xchar - returns TRUE if the given char fits RFC 1738 "xchar" BNF
 */
int
rfc1738xchar(s)
    char *s;
{
    int n;

    return((n = rfc1738uchar(s))
	    ? n
	    : (strchr(RFC1738_RSVP, *s) != NULL
	       || strchr(RFC1738_FUDGE, *s)));
}


/*
 * rfc1738_num - return long value of a string of digits, possibly escaped
 */
long
rfc1738_num(s)
    char **s;
{
    register char *p = *s;
    long n = 0L;

    for(; *p; p++)
      if(*p == '%' && isxpair(p+1)){
	  int c = X2C(p+1);
	  if(isdigit((unsigned char) c)){
	      n = (c - '0') + (n * 10);
	      p += 2;
	  }
	  else
	    break;
      }
      else if(isdigit((unsigned char) *p))
	n = (*p - '0') + (n * 10);
      else
	break;

    *s = p;
    return(n);
}


int
rfc1738_group(s)
    char *s;
{
    return(isalnum((unsigned char) *s)
	   || RFC1738_ESC(s)
	   || strchr(RFC1738_NEWS, *s));
}


/*
 * Encode (hexify) a mailto url.
 *
 * Args  s -- src url
 *
 * Returns  An allocated string which is suitably encoded.
 *          Result should be freed by caller.
 *
 * Since we don't know here which characters are reserved characters (? and &)
 * for use in delimiting the pieces of the url and which are just those
 * characters contained in the data that should be encoded, we always encode
 * them. That's because we know we don't use those as reserved characters.
 * If you do use those as reserved characters you have to encode each part
 * separately.
 */
char *
rfc1738_encode_mailto(s)
    char *s;
{
    char *p, *d, *ret = NULL;

    if(s){
	/* Worst case, encode every character */
	ret = d = (char *)fs_get((3*strlen(s) + 1) * sizeof(char));
	while(*s){
	    if(isalnum((unsigned char)*s)
	       || strchr(RFC1738_SAFE, *s)
	       || strchr(RFC1738_EXTRA, *s))
	      *d++ = *s++;
	    else{
		*d++ = '%';
		C2XPAIR(*s, d);
		s++;
	    }
	}

	*d = '\0';
    }

    return(ret);
}


/*
 *  * * * * * * * *      RFC 1808 support routines      * * * * * * * *
 */


int
rfc1808_tokens(url, scheme, net_loc, path, parms, query, frag)
    char  *url;
    char **scheme, **net_loc, **path, **parms, **query, **frag;
{
    char *p, *q, *start, *tmp = cpystr(url);

    start = tmp;
    if(p = strchr(start, '#')){		/* fragment spec? */
	*p++ = '\0';
	if(*p)
	  *frag = cpystr(p);
    }

    if((p = strchr(start, ':')) && p != start){ /* scheme part? */
	for(q = start; q < p; q++)
	  if(!(isdigit((unsigned char) *q)
	       || isalpha((unsigned char) *q)
	       || strchr("+-.", *q)))
	    break;

	if(p == q){
	    *p++ = '\0';
	    *scheme = cpystr(start);
	    start = p;
	}
    }

    if(*start == '/' && *(start+1) == '/'){ /* net_loc */
	if(p = strchr(start+2, '/'))
	  *p++ = '\0';

	*net_loc = cpystr(start+2);
	if(p)
	  start = p;
	else *start = '\0';		/* End of parse */
    }

    if(p = strchr(start, '?')){
	*p++ = '\0';
	*query = cpystr(p);
    }

    if(p = strchr(start, ';')){
	*p++ = '\0';
	*parms = cpystr(p);
    }

    if(*start)
      *path = cpystr(start);

    fs_give((void **) &tmp);

    return(1);
}



/*
 * web_host_scan -- Scan the given line for possible web host names
 *
 * NOTE: scan below is limited to DNS names ala RFC1034
 */
char *
web_host_scan(line, len)
    char *line;
    int  *len;
{
    char *end, last = '\0';
    int   n;

    for(; *line; last = *line++)
      if((*line == 'w' || *line == 'W')
	 && (!last || !(isalnum((unsigned char) last)
			|| last == '.' || last == '-'))
	 && (((*(line + 1) == 'w' || *(line + 1) == 'W')	/* "www" */
	      && (*(line + 2) == 'w' || *(line + 2) == 'W'))
	     || ((*(line + 1) == 'e' || *(line + 1) == 'E')	/* "web." */
		 && (*(line + 2) == 'b' || *(line + 2) == 'B')
		 && *(line + 3) == '.'))){
	  end = rfc1738_scheme_part(line + 3);
	  if((*len = end - line) > ((*(line+3) == '.') ? 4 : 3)){
	      /* Dread comma exception, see note in rfc1738_scan */
	      if(strchr(",:", *(line + (*len) - 1))
		 || (*(line + (*len) - 1) == '.'
		     && (!*(line + (*len)) || *(line + (*len)) == ' ')))
		(*len)--;

	      return(line);
	  }
	  else
	    line += 3;
      }

    return(NULL);
}


/*
 * mail_addr_scan -- Scan the given line for possible RFC822 addr-spec's
 *
 * NOTE: Well, OK, not strictly addr-specs since there's alot of junk
 *	 we're tying to sift thru and we'd like to minimize false-pos
 *	 matches.
 */
char *
mail_addr_scan(line, len)
    char *line;
    int  *len;
{
    char *amp, *start, *end;
    int   n;

    /* process each : in the line */
    for(; amp = strindex(line, '@'); line = end){
	end = amp + 1;
	/* zero length addr? */
	if(amp == line || !isalnum((unsigned char) *(start = amp - 1)))
	  continue;

	/*
	 * Valid address (ala RFC822 BNF)?  First, first look to the
	 * left to make sure there are valid "scheme" chars...
	 */
	while(1)
	  /* NOTE: we're not doing quoted-strings */
	  if(!(isalnum((unsigned char) *start)
	       || *start == '.' || *start == '-' || *start == '_')){
	      /* advance over bogus char, and erase leading punctuation */
	      for(start++;
		  *start == '.' || *start == '-' || *start == '_';
		  start++)
		;

	      break;
	  }
	  else if(start > line)
	    start--;
	  else
	    break;

	/*
	 * Make sure everyhing up to the colon is a known scheme...
	 */
	if(start && (n = amp - start) > 0){
	    /*
	     * Second, make sure that everything to the right of
	     * amp is valid for a "domain"...
	     */
	    if(*(end = amp + 1) == '['){ /* domain literal */
		int dots = 3;

		for(++end; *end ; end++)
		  if(*end == ']'){
		      if(!dots){
			  *len = end - start + 1;
			  return(start);
		      }
		      else
			break;		/* bogus */
		  }
		  else if(*end == '.'){
		      if(--dots < 0)
			break;		/* bogus */
		  }
		  else if(!isdigit((unsigned char) *end))
		    break;		/* bogus */
	    }
	    else if(isalpha((unsigned char) *end)){ /* domain name? */
		for(++end; ; end++)
		  if(!(*end && (isalnum((unsigned char) *end)
				|| *end == '-'
				|| *end == '.'
				|| *end == '_'))){
		      /* can't end with dash, dot or underscore */
		      while(!isalnum((unsigned char) *(end - 1)))
			end--;

		      *len = end - start;
		      return(start);
		  }
	    }
	}
    }

    return(NULL);
}



/*
 *  * * * * * * * *      RFC 2231 support routines      * * * * * * * *
 */


/* Useful def's */
#define	RFC2231_MAX	64


char *
rfc2231_get_param(parms, name, charset, lang)
    PARAMETER *parms;
    char      *name, **charset, **lang;
{
    char *buf, *p;
    int	  decode = 0, name_len, i, n;

    name_len = strlen(name);
    for(; parms ; parms = parms->next)
      if(!struncmp(name, parms->attribute, name_len))
	if(parms->attribute[name_len] == '*'){
	    for(p = &parms->attribute[name_len + 1], n = 0; *(p+n); n++)
	      ;

	    decode = *(p + n - 1) == '*';

	    if(isdigit((unsigned char) *p)){
		char *pieces[RFC2231_MAX];
		int   count = 0, len;

		memset(pieces, 0, RFC2231_MAX * sizeof(char *));

		while(parms){
		    n = 0;
		    do
		      n = (n * 10) + (*p - '0');
		    while(isdigit(*++p));

		    if(n < RFC2231_MAX){
			pieces[n] = parms->value;
			if(n > count)
			  count = n;
		    }
		    else
		      return(NULL);		/* Too many segments! */

		    while(parms = parms->next)
		      if(!struncmp(name, parms->attribute, name_len)){
			  if(*(p = &parms->attribute[name_len]) == '*'
			      && isdigit((unsigned char) *++p))
			    break;
			  else
			    return(NULL);	/* missing segment no.! */
		      }
		}

		for(i = len = 0; i <= count; i++)
		  if(pieces[i])
		    len += strlen(pieces[i]);
		  else
		    return(NULL);		/* hole! */

		buf = (char *) fs_get((len + 1) * sizeof(char));

		for(i = len = 0; i <= count; i++){
		    if(n = *(p = pieces[i]) == '\"') /* quoted? */
		      p++;

		    while(*p && !(n && *p == '\"' && !*(p+1)))
		      buf[len++] = *p++;
		}

		buf[len] = '\0';
	    }
	    else
	      buf = cpystr(parms->value);

	    /* Do any RFC 2231 decoding? */
	    if(decode){
		n = 0;

		if(p = strchr(buf, '\'')){
		    n = (p - buf) + 1;
		    if(charset){
			*p = '\0';
			*charset = cpystr(buf);
			*p = '\'';
		    }

		    if(p = strchr(&buf[n], '\'')){
			n = (p - buf) + 1;
			if(lang){
			    *p = '\0';
			    *lang = cpystr(p);
			    *p = '\'';
			}
		    }
		}

		if(n){
		    /* Suck out the charset & lang while decoding hex */
		    p = &buf[n];
		    for(i = 0; buf[i] = *p; i++)
		      if(*p++ == '%' && isxpair(p)){
			  buf[i] = X2C(p);
			  p += 2;
		      }
		}
		else
		  fs_give((void **) &buf);	/* problems!?! */
	    }
	    
	    return(buf);
	}
	else
	  return(cpystr(parms->value ? parms->value : ""));

    return(NULL);
}


int
rfc2231_output(so, attrib, value, specials, charset)
    STORE_S *so;
    char    *attrib, *value, *specials, *charset;
{
    int  i, line = 0, encode = 0, quote = 0;

    /*
     * scan for hibit first since encoding clue has to
     * come on first line if any parms are broken up...
     */
    for(i = 0; value && value[i]; i++)
      if(value[i] & 0x80){
	  encode++;
	  break;
      }

    for(i = 0; ; i++){
	if(!(value && value[i]) || i > 80){	/* flush! */
	    if((line++ && !so_puts(so, ";\015\012        "))
	       || !so_puts(so, attrib))
		return(0);

	    if(value){
		if(((value[i] || line > 1) /* more lines or already lines */
		    && !(so_writec('*', so)
			 && so_puts(so, int2string(line - 1))))
		   || (encode && !so_writec('*', so))
		   || !so_writec('=', so)
		   || (quote && !so_writec('\"', so))
		   || ((line == 1 && encode)
		       && !(so_puts(so, ((ps_global->VAR_CHAR_SET
					  && strucmp(ps_global->VAR_CHAR_SET,
						     "us-ascii"))
					    ? ps_global->VAR_CHAR_SET
					    : UNKNOWN_CHARSET))
			     && so_puts(so, "''"))))
		  return(0);

		while(i--){
		    if(*value & 0x80){
			char tmp[3], *p;

			p = tmp;
			C2XPAIR(*value, p);
			*p = '\0';
			if(!(so_writec('%', so) && so_puts(so, tmp)))
			  return(0);
		    }
		    else if(((*value == '\\' || *value == '\"')
			     && !so_writec('\\', so))
			    || !so_writec(*value, so))
		      return(0);

		    value++;
		}

		if(quote && !so_writec('\"', so))
		  return(0);

		if(*value)			/* more? */
		  i = quote = 0;		/* reset! */
		else
		  return(1);			/* done! */
	    }
	    else
	      return(1);
	}

	if(!quote && strchr(specials, value[i]))
	  quote++;
    }
}


PARMLIST_S *
rfc2231_newparmlist(params)
    PARAMETER *params;
{
    PARMLIST_S *p = NULL;

    if(params){
	p = (PARMLIST_S *) fs_get(sizeof(PARMLIST_S));
	memset(p, 0, sizeof(PARMLIST_S));
	p->list = params;
    }

    return(p);
}


void
rfc2231_free_parmlist(p)
    PARMLIST_S **p;
{
    if(*p){
	if((*p)->value)
	  fs_give((void **) &(*p)->value);

	mail_free_body_parameter(&(*p)->seen);
	fs_give((void **) p);
    }
}


int
rfc2231_list_params(plist)
    PARMLIST_S *plist;
{
    PARAMETER *pp, **ppp;
    int	       i;
    char      *cp;

    if(plist->value)
      fs_give((void **) &plist->value);

    for(pp = plist->list; pp; pp = pp->next)
      /* get a name */
      for(i = 0; i < 32; i++)
	if(!(plist->attrib[i] = pp->attribute[i]) ||  pp->attribute[i] == '*'){
	    plist->attrib[i] = '\0';

	    for(ppp = &plist->seen;
		*ppp && strucmp((*ppp)->attribute, plist->attrib);
		ppp = &(*ppp)->next)
	      ;

	    if(!*ppp){
		plist->list = pp->next;
		*ppp = mail_newbody_parameter();	/* add to seen list */
		(*ppp)->attribute = cpystr(plist->attrib);
		plist->value = rfc2231_get_param(pp,plist->attrib,NULL,NULL);
		return(TRUE);
	    }

	    break;
	}

    return(FALSE);
}


PAT_HANDLE *
open_patterns()
{
    PAT_HANDLE *pat_handle;
    PAT_S      *pat;
    PAT_LINE_S *patline, *pl = NULL;
    int         err = 0;
    char      **t;

    dprint(7, (debugfile, "open_patterns()\n"));

    pat_handle = (PAT_HANDLE *)fs_get(sizeof(*pat_handle));
    memset((void *)pat_handle, 0, sizeof(*pat_handle));

    if(ps_global->VAR_PATTERNS && ps_global->VAR_PATTERNS[0]){
	for(t = ps_global->VAR_PATTERNS; !err && t[0] && t[0][0]; t++){
	    if(*t && !strncmp("LIT:", *t, 4))
	      patline = parse_pat_lit(*t + 4);
	    else if(*t && !strncmp("FILE:", *t, 5))
	      patline = parse_pat_file(*t + 5);
	    else
	      patline = NULL;

	    if(patline){
		if(pl){
		    pl->next      = patline;
		    patline->prev = pl;
		    pl = pl->next;
		}
		else{
		    pat_handle->patlinehead = patline;
		    pl = patline;
		}
	    }
	    else
	      q_status_message1(SM_ORDER, 0, 3,
				"Invalid patterns line \"%.40s\"", *t);
	}
    }

    if(err && pat_handle)
      close_patterns(&pat_handle);

    return(pat_handle);
}


PAT_HANDLE *
open_nonempty_patterns()
{
    PAT_HANDLE *handle;

    if((handle = open_patterns()) != NULL){
	if(!handle->patlinehead){
	    close_patterns(&handle);
	    handle = NULL;
	}
    }

    return(handle);
}


PAT_LINE_S *
parse_pat_lit(litpat)
    char *litpat;
{
    PAT_LINE_S *patline;
    PAT_S      *pat;

    patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
    memset((void *)patline, 0, sizeof(*patline));
    patline->type = Literal;


    if((pat = parse_pat(litpat)) != NULL){
	pat->patline   = patline;
	patline->first = pat;
	patline->last  = pat;
    }

    return(patline);
}


/*
 * This always returns a patline even if we can't read the file. The patline
 * returned will say readonly in the worst case and there will be no patterns.
 * If the file doesn't exist, this creates it if possible.
 */
PAT_LINE_S *
parse_pat_file(filename)
    char *filename;
{
#define BUF_SIZE 5000
    PAT_LINE_S *patline;
    PAT_S      *pat, *p;
    char        path[MAXPATH+1], buf[BUF_SIZE];
    char       *dir, *q;
    FILE       *fp;
    int         ok = 0, some_pats = 0;
    struct variable *vars = ps_global->vars;

    signature_path(filename, path, MAXPATH);

    if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, path)){
	q_status_message1(SM_ORDER | SM_DING, 3, 4,
			  "Can't use Roles file outside of %s", VAR_OPER_DIR);
	return(NULL);
    }

    patline = (PAT_LINE_S *)fs_get(sizeof(*patline));
    memset((void *)patline, 0, sizeof(*patline));
    patline->type     = File;
    patline->filename = cpystr(filename);
    patline->filepath = cpystr(path);

    if(q = last_cmpnt(path)){
	int save;

	save = *--q;
	*q = '\0';
	dir  = cpystr(*path ? path : "/");
	*q = save;
    }
    else
      dir = cpystr(".");

#if	defined(DOS) || defined(OS2)
    /*
     * If the dir has become a drive letter and : (e.g. "c:")
     * then append a "\".  The library function access() in the
     * win 16 version of MSC seems to require this.
     */
    if(isalpha((unsigned char) *dir)
       && *(dir+1) == ':' && *(dir+2) == '\0'){
	*(dir+2) = '\\';
	*(dir+3) = '\0';
    }
#endif	/* DOS || OS2 */

    /*
     * Even if we can edit the file itself, we aren't going
     * to be able to change it unless we can also write in
     * the directory that contains it (because we write into a
     * temp file and then rename).
     */
    if(can_access(dir, EDIT_ACCESS) != 0)
      patline->readonly = 1;

    if(can_access(path, EDIT_ACCESS) == 0){
	if(patline->readonly)
	  q_status_message1(SM_ORDER, 0, 3,
			    "Pattern file directory (%s) is ReadOnly", dir);
    }
    else if(can_access(path, READ_ACCESS) == 0)
      patline->readonly = 1;

    if(can_access(path, ACCESS_EXISTS) == 0){
	if((fp = fopen(path, "r")) != NULL){
	    /* Check to see if this is a valid patterns file */
	    if(fp_file_size(fp) <= 0L)
	      ok++;
	    else{
		size_t len;

		len = strlen(PATTERN_MAGIC);
	        if(fread(buf, sizeof(char), len+3, fp) == len+3){
		    buf[len+3] = '\0';
		    buf[len] = '\0';
		    if(strcmp(buf, PATTERN_MAGIC) == 0){
			if(atoi(PATTERN_FILE_VERS) < atoi(buf + len + 1))
			  q_status_message1(SM_ORDER, 0, 4,
      "Pattern file \"%s\" is made by newer Pine, will try to use it anyway",
					    filename);

			ok++;
			some_pats++;
			/* toss rest of first line */
			(void)fgets(buf, BUF_SIZE, fp);
		    }
		}
	    }
		
	    if(!ok){
		patline->readonly = 1;
		q_status_message1(SM_ORDER | SM_DING, 3, 4,
				  "\"%s\" is not a Pattern file", path);
	    }

	    p = NULL;
	    while(some_pats && fgets(buf, BUF_SIZE, fp) != NULL){
		if((pat = parse_pat(buf)) != NULL){
		    pat->patline = patline;
		    if(!patline->first)
		      patline->first = pat;

		    patline->last  = pat;

		    if(p){
			p->next   = pat;
			pat->prev = p;
			p = p->next;
		    }
		    else
		      p = pat;
		}
	    }

	    (void)fclose(fp);
	}
	else{
	    patline->readonly = 1;
	    q_status_message2(SM_ORDER | SM_DING, 3, 4,
			      "Error \"%s\" reading pattern file \"%s\"",
			      error_description(errno), path);
	}
    }
    else{		/* doesn't exist yet, try to create it */
	if(patline->readonly)
	  q_status_message1(SM_ORDER, 0, 3,
			    "Pattern file directory (%s) is ReadOnly", dir);
	else{
	    /*
	     * We try to create it by making up an empty patline and calling
	     * write_pattern_file.
	     */
	    patline->dirty = 1;
	    if(write_pattern_file(NULL, patline) != 0){
		patline->readonly = 1;
		patline->dirty = 0;
		q_status_message1(SM_ORDER | SM_DING, 3, 4,
				  "Error creating pattern file \"%s\"",
				  path);
	    }
	}
    }

    if(dir)
      fs_give((void **)&dir);

    return(patline);
}


PAT_S *
parse_pat(str)
    char *str;
{
    PAT_S *pat = NULL;
    char  *p, *q, *astr, *pstr;
    char  *ptrn = "pattern=";
    char  *actn = "action=";

    if(str)
      removing_trailing_white_space(str);

    if(!str || !*str || *str == '#')
      return(pat);

    pat = (PAT_S *)fs_get(sizeof(*pat));
    memset((void *)pat, 0, sizeof(*pat));

    if((p = srchstr(str, ptrn)) != NULL){
	pat->patgrp = (PATGRP_S *)fs_get(sizeof(*pat->patgrp));
	memset((void *)pat->patgrp, 0, sizeof(*pat->patgrp));

	if((pstr = copy_quoted_string_asis(p+strlen(ptrn))) != NULL){
	    /* get the nickname (we always force a nickname) */
	    if((q = srchstr(pstr, "/NICK=")) != NULL)
	      pat->patgrp->nick = remove_backslash_escapes(q+6);
	    else
	      pat->patgrp->nick = cpystr("Alternate Role");

	    pat->patgrp->to     = parse_pattern("/TO=",     pstr);
	    pat->patgrp->cc     = parse_pattern("/CC=",     pstr);
	    pat->patgrp->from   = parse_pattern("/FROM=",   pstr);
	    pat->patgrp->sender = parse_pattern("/SENDER=", pstr);
	    pat->patgrp->news   = parse_pattern("/NEWS=",   pstr);
	    pat->patgrp->subj   = parse_pattern("/SUBJ=",   pstr);
	    fs_give((void **)&pstr);
	}
    }

    if((p = srchstr(str, actn)) != NULL){
	pat->action = (ACTION_S *)fs_get(sizeof(*pat->action));
	memset((void *)pat->action, 0, sizeof(*pat->action));

	if((astr = copy_quoted_string_asis(p+strlen(actn))) != NULL){

	    if(srchstr(astr, "ROLE=1")){
		ROLE_ACTION_S  *role;
		ROLE_NAMEVAL_S *v;
		int             i;

		pat->action->role =
			   (ROLE_ACTION_S *)fs_get(sizeof(*pat->action->role));
		role = pat->action->role;
		memset((void *)role, 0, sizeof(*role));

		role->nick = cpystr((pat->patgrp->nick && pat->patgrp->nick[0])
				       ? pat->patgrp->nick : "Alternate Role");

		/* reply type */
		role->repl_type = ROLE_REPL_DEFL;
		if((q = srchstr(astr, "/RTYPE=")) != NULL){
		    if((p = remove_backslash_escapes(q+7)) != NULL){
			for(i = 0; v = role_repl_types(i); i++)
			  if(!strucmp(p, v->shortname)){
			      role->repl_type = v->value;
			      break;
			  }

			fs_give((void **)&p);
		    }
		}

		/* forward type */
		role->forw_type = ROLE_FORW_DEFL;
		if((q = srchstr(astr, "/FTYPE=")) != NULL){
		    if((p = remove_backslash_escapes(q+7)) != NULL){
			for(i = 0; v = role_forw_types(i); i++)
			  if(!strucmp(p, v->shortname)){
			      role->forw_type = v->value;
			      break;
			  }

			fs_give((void **)&p);
		    }
		}

		/* get the from */
		if((q = srchstr(astr, "/FROM=")) != NULL){
		    if((p = remove_backslash_escapes(q+6)) != NULL){
			rfc822_parse_adrlist(&role->from, p,
					     ps_global->maildomain);
			fs_give((void **)&p);
		    }
		}

		/* get the fcc */
		if((q = srchstr(astr, "/FCC=")) != NULL)
		  role->fcc = remove_backslash_escapes(q+5);

		/* get the sig file */
		if((q = srchstr(astr, "/SIG=")) != NULL)
		  role->sig = remove_backslash_escapes(q+5);

		/* get the template file */
		if((q = srchstr(astr, "/TEMPLATE=")) != NULL)
		  role->template = remove_backslash_escapes(q+10);

		/* get the inherit nick */
		if((q = srchstr(astr, "/INICK=")) != NULL)
		  role->inherit_nick = remove_backslash_escapes(q+7);
	    }

	    fs_give((void **)&astr);
	}
    }
    
    return(pat);
}


/*
 * Look for label in str and return a pointer to parsed string.
 * Converts from string from patterns file which looks like
 *       /NEWS=comp.mail.,comp.mail.pine/TO=...
 * This is the string that came from pattern="string" with the pattern=
 * and outer quotes removed.
 * This converts the string to a PATTERN_S list and returns
 * an allocated copy.
 */
PATTERN_S *
parse_pattern(label, str)
    char *label;
    char *str;
{
    char      *q, *labeled_str;
    PATTERN_S *head = NULL;

    if(!label || !str)
      return(NULL);

    if((q = srchstr(str, label)) != NULL){
	if((labeled_str = remove_backslash_escapes(q+strlen(label))) != NULL){
	    head = string_to_pattern(labeled_str);
	    fs_give((void **)&labeled_str);
	}
    }

    return(head);
}


/*
 * Converts an unquoted string (that is, the quotes and backslashes escaping
 * commas have already been removed) to a PATTERN_S list and returns an
 * allocated copy. The source string looks like
 *        string1,string2,...
 */
PATTERN_S *
string_to_pattern(str)
    char *str;
{
    char      *q, *substr;
    PATTERN_S *p, *head = NULL;

    if(!str)
      return(head);
    
    /*
     * We want an empty string to cause an empty substring in the pattern
     * instead of returning a NULL pattern. That can be used as a way to
     * match any header. For example, if all the patterns but the news
     * pattern were null and the news pattern was a substring of "" then
     * we use that to match any message with a newsgroups header.
     */
    if(!*str){
	head = (PATTERN_S *)fs_get(sizeof(*p));
	memset((void *)head, 0, sizeof(*head));
	head->substring = cpystr("");
    }
    else
      for(q = strtok(str, ","); q; q = strtok(NULL, ",")){
	substr = (q && *q) ? cpystr(q) : NULL;
	p = (PATTERN_S *)fs_get(sizeof(*p));
	memset((void *)p, 0, sizeof(*p));
	p->substring = substr;
	p->next      = head;
	head         = p;
      }

    return(head);
}

    
/*
 * Converts a PATTERN_S list to a string. The string is not quoted.
 * The resulting string is allocated here and looks like
 *        string1,string2,...
 * If the original PATTERN_S has commas in it things won't work right.
 */
char *
pattern_to_string(pattern)
    PATTERN_S *pattern;
{
    PATTERN_S *p;
    char      *result = NULL, *q;
    size_t     n;

    if(!pattern)
      return(result);

    /* how much space is needed? */
    n = 0;
    for(p = pattern; p; p = p->next)
      n += (strlen(p->substring ? p->substring : "") +
				  ((p != pattern) ? 1 : 0));

    q = result = (char *)fs_get(++n);
    for(p = pattern; p; p = p->next){
	sprintf(q, "%s%s",
		p != pattern ? "," : "",
		p->substring ? p->substring : "");
	q += strlen(q);
    }

    return(result);
}


PAT_S *
first_any_pattern(handle)
    PAT_HANDLE *handle;
{
    PAT_LINE_S *patline;

    if(handle){
	/* Find first patline with a pat */
	for(patline = handle->patlinehead;
	    patline && !patline->first;
	    patline = patline->next)
	  ;

	if(patline){
	    handle->patlinecurrent = patline;
	    handle->patcurrent     = patline->first;
	}
	else{
	    handle->patlinecurrent = NULL;
	    handle->patcurrent     = NULL;
	}
    }

    return(handle ? handle->patcurrent : NULL);
}


/*
 * Return only patterns of the specified type.
 */
PAT_S *
first_pattern(handle, flags)
    PAT_HANDLE *handle;
    int         flags;
{
    PAT_S *pat;

    for(pat = first_any_pattern(handle);
	pat && (!pat->action || !pat->action->role ||
	        !(flags & ROLE_ANY ||
	          (flags & ROLE_REPLY &&
		   (pat->action->role->repl_type == ROLE_REPL_YES ||
		    pat->action->role->repl_type == ROLE_REPL_NOCONF)) ||
	          (flags & ROLE_FORWARD &&
		   (pat->action->role->forw_type == ROLE_FORW_YES ||
		    pat->action->role->forw_type == ROLE_FORW_NOCONF))));
	pat = next_any_pattern(handle))
      ;
    
    return(pat);
}


PAT_S *
last_any_pattern(handle)
    PAT_HANDLE *handle;
{
    PAT_LINE_S *patline;

    handle->patlinecurrent = NULL;
    handle->patcurrent     = NULL;

    if(handle){
	/* Find last patline with a pat */
	for(patline = handle->patlinehead; patline; patline = patline->next)
	  if(patline->last)
	    handle->patlinecurrent = patline;
	
	if(handle->patlinecurrent)
	  handle->patcurrent = handle->patlinecurrent->last;
    }

    return(handle ? handle->patcurrent : NULL);
}


PAT_S *
last_pattern(handle, flags)
    PAT_HANDLE *handle;
    int         flags;
{
    PAT_S *pat;

    for(pat = last_any_pattern(handle);
	pat && (!pat->action || !pat->action->role ||
	        !(flags & ROLE_ANY ||
	          (flags & ROLE_REPLY &&
		   (pat->action->role->repl_type == ROLE_REPL_YES ||
		    pat->action->role->repl_type == ROLE_REPL_NOCONF)) ||
	          (flags & ROLE_FORWARD &&
		   (pat->action->role->forw_type == ROLE_FORW_YES ||
		    pat->action->role->forw_type == ROLE_FORW_NOCONF))));
	pat = prev_any_pattern(handle))
      ;
    
    return(pat);
}

    
PAT_S *
next_any_pattern(handle)
    PAT_HANDLE *handle;
{
    PAT_LINE_S *patline;

    if(handle && handle->patlinecurrent){
	if(handle->patcurrent && handle->patcurrent->next)
	  handle->patcurrent = handle->patcurrent->next;
	else{
	    /* Find next patline with a pat */
	    for(patline = handle->patlinecurrent->next;
		patline && !patline->first;
		patline = patline->next)
	      ;
	    
	    if(patline){
		handle->patlinecurrent = patline;
		handle->patcurrent     = patline->first;
	    }
	    else{
		handle->patlinecurrent = NULL;
		handle->patcurrent     = NULL;
	    }
	}
    }

    return(handle ? handle->patcurrent : NULL);
}


PAT_S *
next_pattern(handle, flags)
    PAT_HANDLE *handle;
    int         flags;
{
    PAT_S *pat;

    for(pat = next_any_pattern(handle);
	pat && (!pat->action || !pat->action->role ||
	        !(flags & ROLE_ANY ||
	          (flags & ROLE_REPLY &&
		   (pat->action->role->repl_type == ROLE_REPL_YES ||
		    pat->action->role->repl_type == ROLE_REPL_NOCONF)) ||
	          (flags & ROLE_FORWARD &&
		   (pat->action->role->forw_type == ROLE_FORW_YES ||
		    pat->action->role->forw_type == ROLE_FORW_NOCONF))));
	pat = next_any_pattern(handle))
      ;
    
    return(pat);
}

    
PAT_S *
prev_any_pattern(handle)
    PAT_HANDLE *handle;
{
    PAT_LINE_S *patline;

    if(handle && handle->patlinecurrent){
	if(handle->patcurrent && handle->patcurrent->prev)
	  handle->patcurrent = handle->patcurrent->prev;
	else{
	    /* Find prev patline with a pat */
	    for(patline = handle->patlinecurrent->prev;
		patline && !patline->last;
		patline = patline->prev)
	      ;
	    
	    if(patline){
		handle->patlinecurrent = patline;
		handle->patcurrent     = patline->last;
	    }
	    else{
		handle->patlinecurrent = NULL;
		handle->patcurrent     = NULL;
	    }
	}
    }

    return(handle ? handle->patcurrent : NULL);
}


PAT_S *
prev_pattern(handle, flags)
    PAT_HANDLE *handle;
    int         flags;
{
    PAT_S *pat;

    for(pat = prev_any_pattern(handle);
	pat && (!pat->action || !pat->action->role ||
	        !(flags & ROLE_ANY ||
	          (flags & ROLE_REPLY &&
		   (pat->action->role->repl_type == ROLE_REPL_YES ||
		    pat->action->role->repl_type == ROLE_REPL_NOCONF)) ||
	          (flags & ROLE_FORWARD &&
		   (pat->action->role->forw_type == ROLE_FORW_YES ||
		    pat->action->role->forw_type == ROLE_FORW_NOCONF))));
	pat = prev_any_pattern(handle))
      ;
    
    return(pat);
}

    
int
write_patterns(handle)
    PAT_HANDLE *handle;
{
    int            err = 0, lineno = 0;
    char         **lvalue = NULL;
    PAT_LINE_S    *patline;

    dprint(7, (debugfile, "write_patterns()\n"));

    if(!handle){
	q_status_message(SM_ORDER | SM_DING, 3, 4,
			  "Unknown error saving pattern file");
	return(-1);
    }

    if(handle->dirtypinerc){
	/* Count how many lines will be in patterns variable */
	for(patline = handle->patlinehead; patline; patline = patline->next)
	  lineno++;
    
	lvalue = (char **)fs_get((lineno+1)*sizeof(char *));
	memset(lvalue, 0, (lineno+1) * sizeof(char *));
    }

    for(patline = handle->patlinehead, lineno = 0;
	!err && patline;
	patline = patline->next, lineno++){
	if(patline->type == File)
	  err = write_pattern_file(handle->dirtypinerc ? &lvalue[lineno] : NULL,
				   patline);
	else if(patline->type == Literal && handle->dirtypinerc)
	  err = write_pattern_lit(&lvalue[lineno], patline);
    }

    if(handle->dirtypinerc){
	if(err)
	  free_list_array(&lvalue);
	else{
	    free_list_array(&ps_global->vars[V_PATTERNS].user_val.l);
	    ps_global->vars[V_PATTERNS].user_val.l = lvalue;
	    set_current_val(&ps_global->vars[V_PATTERNS], TRUE, TRUE);

	    write_pinerc(ps_global);
	}
    }

    if(!err)
      handle->dirtypinerc = 0;

    return(err);
}


/*
 * Write pattern lines into a file.
 *
 * Args  lvalue -- Pointer to char * to fill in variable value
 *      patline -- 
 *
 * Returns  0 -- all is ok, lvalue has been filled in, file has been written
 *       else -- error, lvalue untouched, file not written
 */
int
write_pattern_file(lvalue, patline)
    char      **lvalue;
    PAT_LINE_S *patline;
{
    char  *p, *tfile;
    int    fd = -1, err = 0;
    FILE  *fp_new;
    PAT_S *pat;

    dprint(7, (debugfile, "write_pattern_file(%s)\n", patline->filepath));

    if(lvalue){
	p = (char *)fs_get((strlen(patline->filename) + 6) * sizeof(char));
	strcat(strcpy(p, "FILE:"), patline->filename);
	*lvalue = p;
    }

    if(patline->readonly || !patline->dirty)	/* doesn't need writing */
      return(err);

    /* Get a tempfile to write the patterns into */
    if(((tfile = tempfile_in_same_dir(patline->filepath, ".pt", NULL)) == NULL)
       || ((fd = open(tfile, O_TRUNC|O_WRONLY|O_CREAT, 0600)) < 0)
       || ((fp_new = fdopen(fd, "w")) == NULL)){
	q_status_message1(SM_ORDER | SM_DING, 3, 4,
			  "Can't write in directory containing file \"%s\"",
			  patline->filepath);
	if(tfile)
	  fs_give((void **)&tfile);
	
	if(fd >= 0)
	  close(fd);

	return(-1);
    }

    dprint(9, (debugfile, "write_pattern_file: writing into %s\n", tfile));
    
    if(fprintf(fp_new, "%s %s\n", PATTERN_MAGIC, PATTERN_FILE_VERS) == EOF)
      err--;

    for(pat = patline->first; !err && pat; pat = pat->next){
	if((p = data_for_patline(pat)) != NULL){
	    if(fprintf(fp_new, "%s\n", p) == EOF)
	      err--;
	    
	    fs_give((void **)&p);
	}
    }

    if(err || fclose(fp_new) == EOF){
	if(err)
	  (void)fclose(fp_new);

	err--;
	q_status_message2(SM_ORDER | SM_DING, 3, 4,
			  "I/O error: \"%s\": %s",
			  tfile, error_description(errno));
    }

    if(!err && rename_file(tfile, patline->filepath) < 0){
	err--;
	q_status_message3(SM_ORDER | SM_DING, 3, 4,
			  "Error renaming \"%s\" to \"%s\": %s",
			  tfile, patline->filepath, error_description(errno));
	dprint(2, (debugfile,
		   "write_pattern_file: Error renaming (%s,%s): %s\n",
		   tfile, patline->filepath, error_description(errno)));
    }

    if(tfile)
      fs_give((void **)&tfile);

    if(!err)
      patline->dirty = 0;

    return(err);
}


/*
 * Write literal pattern lines into lvalue (pinerc variable).
 *
 * Args  lvalue -- Pointer to char * to fill in variable value
 *      patline -- 
 *       handle -- 
 *
 * Returns  0 -- all is ok, lvalue has been filled in, file has been written
 *       else -- error, lvalue untouched, file not written
 */
int
write_pattern_lit(lvalue, patline)
    char      **lvalue;
    PAT_LINE_S *patline;
{
    char  *p = NULL;
    int    err = 0;
    PAT_S *pat;

    pat = patline ? patline->first : NULL;
    
    if(pat && (p = data_for_patline(pat)) != NULL){
	*lvalue = (char *)fs_get((strlen(p) + 5) * sizeof(char));
	strcat(strcpy(*lvalue, "LIT:"), p);
    }
    else{
	q_status_message(SM_ORDER | SM_DING, 3, 4,
			 "Unknown error saving pattern variable");
	err--;
    }
    
    if(p)
      fs_give((void **)&p);

    return(err);
}


char *
data_for_patline(pat)
    PAT_S *pat;
{
    char          *p, *to_pat = NULL, *news_pat = NULL, *from_pat = NULL,
		  *sender_pat = NULL, *cc_pat = NULL, *subj_pat = NULL,
		  *from_act = NULL, *fcc_act = NULL,
		  *sig_act = NULL, *nick = NULL, *templ_act = NULL,
		  *repl_val = NULL, *forw_val = NULL, *inherit_nick = NULL;
    ROLE_ACTION_S *role = NULL;
    ROLE_NAMEVAL_S *f;

    if(pat->patgrp){
	if(pat->patgrp->nick)
	  nick = add_backslash_escapes(pat->patgrp->nick);

	if(pat->patgrp->to){
	    p = pattern_to_string(pat->patgrp->to);
	    if(p){
		to_pat = add_backslash_escapes(p);
		fs_give((void **)&p);
	    }
	}

	if(pat->patgrp->from){
	    p = pattern_to_string(pat->patgrp->from);
	    if(p){
		from_pat = add_backslash_escapes(p);
		fs_give((void **)&p);
	    }
	}

	if(pat->patgrp->sender){
	    p = pattern_to_string(pat->patgrp->sender);
	    if(p){
		sender_pat = add_backslash_escapes(p);
		fs_give((void **)&p);
	    }
	}

	if(pat->patgrp->cc){
	    p = pattern_to_string(pat->patgrp->cc);
	    if(p){
		cc_pat = add_backslash_escapes(p);
		fs_give((void **)&p);
	    }
	}

	if(pat->patgrp->news){
	    p = pattern_to_string(pat->patgrp->news);
	    if(p){
		news_pat = add_backslash_escapes(p);
		fs_give((void **)&p);
	    }
	}

	if(pat->patgrp->subj){
	    p = pattern_to_string(pat->patgrp->subj);
	    if(p){
		subj_pat = add_backslash_escapes(p);
		fs_give((void **)&p);
	    }
	}
    }

    if(pat->action && pat->action->role){
	role = pat->action->role;
	if(role->inherit_nick)
	  inherit_nick = add_backslash_escapes(role->inherit_nick);
	if(role->fcc)
	  fcc_act = add_backslash_escapes(role->fcc);
	if(role->sig)
	  sig_act = add_backslash_escapes(role->sig);
	if(role->template)
	  templ_act = add_backslash_escapes(role->template);

	if((f = role_repl_types(role->repl_type)) != NULL)
	  repl_val = f->shortname;

	if((f = role_forw_types(role->forw_type)) != NULL)
	  forw_val = f->shortname;

	if(role->from){
	    char *bufp;

	    bufp = (char *)fs_get((size_t)est_size(role->from));
	    p = addr_string(role->from, bufp);
	    if(p){
		from_act = add_backslash_escapes(p);
		fs_give((void **)&p);
	    }
	}
    }

    p = (char *)fs_get((strlen((nick && *nick) ? nick : "Alternate Role") +
			strlen(to_pat ? to_pat : "") +
			strlen(from_pat ? from_pat : "") +
			strlen(sender_pat ? sender_pat : "") +
			strlen(cc_pat ? cc_pat : "") +
			strlen(news_pat ? news_pat : "") +
			strlen(subj_pat ? subj_pat : "") +
			strlen(inherit_nick ? inherit_nick : "") +
			strlen(from_act ? from_act : "") +
			strlen(fcc_act ? fcc_act : "") +
			strlen(sig_act ? sig_act : "") +
			strlen(templ_act ? templ_act : "") + 300)*sizeof(char));

    sprintf(p, "pattern=\"/NICK=%s%s%s%s%s%s%s%s%s%s%s%s%s\" action=\"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\"",
	       (nick && *nick) ? nick : "Alternate Role",
	       to_pat ? "/TO=" : "",
	       to_pat ? to_pat : "",
	       from_pat ? "/FROM=" : "",
	       from_pat ? from_pat : "",
	       sender_pat ? "/SENDER=" : "",
	       sender_pat ? sender_pat : "",
	       cc_pat ? "/CC=" : "",
	       cc_pat ? cc_pat : "",
	       news_pat ? "/NEWS=" : "",
	       news_pat ? news_pat : "",
	       subj_pat ? "/SUBJ=" : "",
	       subj_pat ? subj_pat : "",
	       (inherit_nick && *inherit_nick) ? "/INICK=" : "",
	       inherit_nick ? inherit_nick : "",
	       role ? "/ROLE=1" : "",
	       from_act ? "/FROM=" : "",
	       from_act ? from_act : "",
	       fcc_act ? "/FCC=" : "",
	       fcc_act ? fcc_act : "",
	       sig_act ? "/SIG=" : "",
	       sig_act ? sig_act : "",
	       templ_act ? "/TEMPLATE=" : "",
	       templ_act ? templ_act : "",
	       role ? "/RTYPE=" : "",
	       repl_val ? repl_val : "NO",
	       role ? "/FTYPE=" : "",
	       forw_val ? forw_val : "NO");

    if(to_pat)
      fs_give((void **)&to_pat);
    if(from_pat)
      fs_give((void **)&from_pat);
    if(sender_pat)
      fs_give((void **)&sender_pat);
    if(cc_pat)
      fs_give((void **)&cc_pat);
    if(news_pat)
      fs_give((void **)&news_pat);
    if(subj_pat)
      fs_give((void **)&subj_pat);
    if(from_act)
      fs_give((void **)&from_act);
    if(fcc_act)
      fs_give((void **)&fcc_act);
    if(templ_act)
      fs_give((void **)&templ_act);
    if(sig_act)
      fs_give((void **)&sig_act);
    if(nick)
      fs_give((void **)&nick);
    if(inherit_nick)
      fs_give((void **)&inherit_nick);

    return(p);
}

    
void
close_patterns(handle)
    PAT_HANDLE **handle;
{
    if(handle && *handle){
	free_patline_list(&(*handle)->patlinehead);
	fs_give((void **)handle);
    }
}


/*
 * Returns 1 if this message matches this pattern, else 0.
 */
int
match_pattern(patgrp, stream, msgno, section)
    PATGRP_S   *patgrp;
    MAILSTREAM *stream;
    long        msgno;
    char       *section;
{
    char         *charset = NULL;
    SEARCHPGM    *pgm;
    MESSAGECACHE *mc;
    ROLE_ARGS_T   rargs;

    if(!patgrp)
      return(0);

    rargs.multi = 0;
    rargs.cset = &charset;
    rargs.ourcharset = (ps_global->VAR_CHAR_SET && ps_global->VAR_CHAR_SET[0]) ?
					      ps_global->VAR_CHAR_SET : NULL;

    pgm = mail_newsearchpgm();

    /* a single message set */
    pgm->msgno = mail_newsearchset();
    pgm->msgno->first  = pgm->msgno->last = msgno;
    pgm->msgno->next   = NULL;

    if(patgrp->subj){
	rargs.type = Subj;
	set_up_search_pgm(patgrp->subj, pgm, &rargs);
    }

    if(patgrp->cc){
	rargs.type = Cc;
	set_up_search_pgm(patgrp->cc, pgm, &rargs);
    }

    if(patgrp->from){
	rargs.type = From;
	set_up_search_pgm(patgrp->from, pgm, &rargs);
    }

    if(patgrp->to){
	rargs.type = To;
	set_up_search_pgm(patgrp->to, pgm, &rargs);
    }

    if(patgrp->sender){
	rargs.type = Sender;
	set_up_search_pgm(patgrp->sender, pgm, &rargs);
    }

    if(patgrp->news){
	rargs.type = News;
	set_up_search_pgm(patgrp->news, pgm, &rargs);
    }

    if(section){
	int charset_unknown = 0;

	/*
	 * Mail_search_full only searches the top-level msg. We want to
	 * search an attached msg instead, in this case. First do the stuff
	 * that mail_search_full would have done before calling
	 * mail_search_msg, then call our mail_search_att, which is similar
	 * to mail_search_msg.
	 */

	mail_elt(stream,msgno)->searched = NIL;
	if(charset && *charset &&  /* convert if charset not ASCII or UTF-8 */
	  !(((charset[0] == 'U') || (charset[0] == 'u')) &&
	    ((((charset[1] == 'S') || (charset[1] == 's')) &&
	      (charset[2] == '-') &&
	      ((charset[3] == 'A') || (charset[3] == 'a')) &&
	      ((charset[4] == 'S') || (charset[4] == 's')) &&
	      ((charset[5] == 'C') || (charset[5] == 'c')) &&
	      ((charset[6] == 'I') || (charset[6] == 'i')) &&
	      ((charset[7] == 'I') || (charset[7] == 'i')) && !charset[8]) ||
	     (((charset[1] == 'T') || (charset[1] == 't')) &&
	      ((charset[2] == 'F') || (charset[2] == 'f')) &&
	      (charset[3] == '-') && (charset[4] == '8') && !charset[5])))){
	    if(utf8_text(NIL,charset,NIL,T))
	      utf8_searchpgm (pgm,charset);
	    else
	      charset_unknown++;
	}

	if(!charset_unknown && mail_search_att(stream,msgno,section,pgm))
	  mail_elt(stream,msgno)->searched = T;

	/* this is the SE_FREE flag action */
	mail_free_searchpgm(&pgm);
    }
    else
      mail_search_full(stream, charset, pgm, SO_NOSERVER|SE_NOPREFETCH|SE_FREE);

    if(charset)
      fs_give((void **)&charset);

    mc = mail_elt(stream, msgno);
    return(mc && mc->searched);
}


void
set_up_search_pgm(pattern, pgm, rargs)
    PATTERN_S   *pattern;
    SEARCHPGM   *pgm;
    ROLE_ARGS_T *rargs;
{
    SEARCHOR *or, **or_ptr;

    if(pattern && rargs && pgm){
	/*
	 * To is special because we want to use the ReSent-To header instead
	 * of the To header if it exists.  We set up something like:
	 *
	 * if((<resent-to exists> AND (resent-to matches pat1 or pat2...))
	 *                  OR
	 *    (<resent-to doesn't exist> AND (to matches pat1 or pat2...)))
	 */
	if(rargs->type == To){
	    ROLE_ARGS_T local_args;
	    char       *space = " ";

	    /* find next unused or slot */
	    for(or = pgm->or; or && or->next; or = or->next)
	      ;

	    if(or)
	      or_ptr = &or->next;
	    else
	      or_ptr = &pgm->or;

	    *or_ptr = mail_newsearchor();

	    local_args = *rargs;
	    local_args.type = ResentTo;
	    local_args.cset = NULL;	/* just to save having to check */
	    /* check for resent-to exists */
	    set_srch(space, (*or_ptr)->first, &local_args);

	    local_args.cset = rargs->cset;
	    add_type_to_pgm(pattern, (*or_ptr)->first, &local_args);

	    /* check for resent-to doesn't exist */
	    (*or_ptr)->second->not = mail_newsearchpgmlist();
	    local_args.cset = NULL;
	    set_srch(space, (*or_ptr)->second->not->pgm, &local_args);

	    /* now add the real To search to second */
	    local_args.cset = rargs->cset;
	    local_args.type = To;
	    add_type_to_pgm(pattern, (*or_ptr)->second, &local_args);
	}
	else
	  add_type_to_pgm(pattern, pgm, rargs);
    }
}


void
add_type_to_pgm(pattern, pgm, rargs)
    PATTERN_S   *pattern;
    SEARCHPGM   *pgm;
    ROLE_ARGS_T *rargs;
{
    PATTERN_S *p;
    SEARCHOR  *or, **or_ptr;

    if(pattern && rargs && pgm){
	for(p = pattern; p; p = p->next){
	    if(p->next){
		/*
		 * The list of or's (the or->next thing) is actually AND'd
		 * together. So we want to use a different member of that
		 * list for things we want to AND, like Subject A or B AND
		 * From C or D. On the other hand, for multiple items in
		 * one group which we really do want OR'd together, like
		 * Subject A or B or C we don't use the list, we use an
		 * OR tree (which is what this for loop is building).
		 */

		/* find next unused or slot */
		for(or = pgm->or; or && or->next; or = or->next)
		  ;

		if(or)
		  or_ptr = &or->next;
		else
		  or_ptr = &pgm->or;

		*or_ptr = mail_newsearchor();
		set_srch(p->substring ? p->substring : "",
			 (*or_ptr)->first, rargs);
		pgm = (*or_ptr)->second;
	    }
	    else{
		set_srch(p->substring ? p->substring : "", pgm, rargs);
	    }
	}
    }
}


void
set_srch(value, pgm, rargs)
    char        *value;
    SEARCHPGM   *pgm;
    ROLE_ARGS_T *rargs;
{
    if(rargs){
	switch(rargs->type){
	  case Subj:
	  case From:
	  case To:
	  case Cc:
	    set_srch_list(value, pgm, rargs);
	    break;
	  case News:
	  case Sender:
	  case ResentTo:
	    set_srch_hdr(value, pgm, rargs);
	    break;
	}
    }
}


void
set_srch_list(value, pgm, rargs)
    char        *value;
    SEARCHPGM   *pgm;
    ROLE_ARGS_T *rargs;
{
    char        *decoded, *cs = NULL, *charset = NULL;
    STRINGLIST **list;

    if(!(value && rargs && pgm))
      return;

    switch(rargs->type){
      case Subj:
	list = &pgm->subject;
	break;
      case From:
	list = &pgm->from;
	break;
      case To:
	list = &pgm->to;
	break;
      case Cc:
	list = &pgm->cc;
	break;
      default:
	dprint(1, (debugfile, "set_srch_list: unknown type\n"));
	return;
    }

    if(!list)
      return;

    *list = mail_newstringlist();
    decoded = (char *)rfc1522_decode((unsigned char *)tmp_20k_buf,
				     value, &cs);

    (*list)->text.data = (unsigned char *)cpystr(decoded);
    (*list)->text.size = strlen(decoded);

    if(rargs->cset && !rargs->multi){
	if(decoded != value)
	  charset = (cs && cs[0]) ? cs : rargs->ourcharset;
	else if(!is_ascii_string(decoded))
	  charset = rargs->ourcharset;

	if(charset){
	    if(*rargs->cset){
		if(strucmp(*rargs->cset, charset) != 0){
		    rargs->multi = 1;
		    if(rargs->ourcharset &&
		       strucmp(rargs->ourcharset, *rargs->cset) != 0){
			fs_give((void **)rargs->cset);
			*rargs->cset = cpystr(rargs->ourcharset);
		    }
		}
	    }
	    else
	      *rargs->cset = cpystr(charset);
	}
    }

    if(cs)
      fs_give((void **)&cs);
}


void
set_srch_hdr(value, pgm, rargs)
    char        *value;
    SEARCHPGM   *pgm;
    ROLE_ARGS_T *rargs;
{
    char *decoded, *cs = NULL, *charset = NULL;
    SEARCHHEADER  **hdr;
    char           *field;

    if(!(value && rargs && pgm))
      return;

    switch(rargs->type){
      case News:
	field = "newsgroups";
	break;
      case Sender:
	field = "sender";
	break;
      case ResentTo:
	field = "resent-to";
	break;
      default:
	dprint(1, (debugfile, "set_srch_hdr: unknown type\n"));
	return;
    }

    hdr = &pgm->header;
    if(!hdr)
      return;

    decoded = (char *)rfc1522_decode((unsigned char *)tmp_20k_buf,
				     value, &cs);
    while(*hdr && (*hdr)->next)
      *hdr = (*hdr)->next;
      
    if(*hdr)
      (*hdr)->next = mail_newsearchheader(field, decoded);
    else
      *hdr = mail_newsearchheader(field, decoded);

    if(rargs->cset && !rargs->multi){
	if(decoded != value)
	  charset = (cs && cs[0]) ? cs : rargs->ourcharset;
	else if(!is_ascii_string(decoded))
	  charset = rargs->ourcharset;

	if(charset){
	    if(*rargs->cset){
		if(strucmp(*rargs->cset, charset) != 0){
		    rargs->multi = 1;
		    if(rargs->ourcharset &&
		       strucmp(rargs->ourcharset, *rargs->cset) != 0){
			fs_give((void **)rargs->cset);
			*rargs->cset = cpystr(rargs->ourcharset);
		    }
		}
	    }
	    else
	      *rargs->cset = cpystr(charset);
	}
    }

    if(cs)
      fs_give((void **)&cs);
}


/*
 * This is a hacked copy of c-client's mail_search_msg. The problem we're
 * solving is that we want to search an attachment of type message, but
 * IMAP SEARCH is defined to only do the top-level search, and so
 * mail_search_msg (and hence mail_search_full) only does the top-level search.
 * We assume that there is only a single attachment that we are searching.
 * We also assume that there are some types of searches we don't do,
 * for now. We do the search locally.
 *
 * This is derived from RCS version 1.9 of mail.c last modified 1999/01/14.
 *
 * Local mail search message
 * Accepts: MAIL stream
 *	    message number
 *	    section or NIL to do top-level
 *	    search program
 * Returns: T if found, NIL otherwise
 */

long
mail_search_att(stream, msgno, sec, pgm)
    MAILSTREAM   *stream;
    unsigned long msgno;
    char         *sec;
    SEARCHPGM    *pgm;
{
  unsigned short d;
  unsigned long i,uid;
  char tmp[MAILTMPLEN];
  SIZEDTEXT s;
  MESSAGECACHE *elt = mail_elt (stream,msgno);
  MESSAGECACHE delt;
  SEARCHHEADER *hdr;
  SEARCHSET *set;
  SEARCHOR *or;
  SEARCHPGMLIST *not;
  ENVELOPE *env;

  if(sec){
      BODY *body;
      if((body = mail_body(stream,msgno,sec)) != NIL &&
	 body->nested.msg != NIL &&
	 body->nested.msg->env != NIL)
        env = body->nested.msg->env;
      else
	return(NIL);
  }
  else  /* we won't use this branch */
    env = mail_fetchenvelope(stream,msgno);
				/* message sequences */
  if (set = pgm->msgno) {	/* must be inside this sequence */
    while (set) {		/* run down until find matching range */
      if (set->last ? ((msgno < set->first) || (msgno > set->last)) :
	  msgno != set->first) set = set->next;
      else break;
    }
    if (!set) return NIL;	/* not found within sequence */
  }

  if (set = pgm->uid) {		/* must be inside this sequence */
    uid = mail_uid (stream,msgno);
    while (set) {		/* run down until find matching range */
      if (set->last ? ((uid < set->first) || (uid > set->last)) :
	  uid != set->first) set = set->next;
      else break;
    }
    if (!set) return NIL;	/* not found within sequence */
  }
				/* require fast data for size ranges */
  if ((pgm->larger || pgm->smaller) && !elt->rfc822_size) {
    sprintf (tmp,"%ld",elt->msgno);
    mail_fetch_fast (stream,tmp,NIL);
  }
				/* size ranges */
  if ((pgm->larger && (elt->rfc822_size <= pgm->larger)) ||
      (pgm->smaller && (elt->rfc822_size >= pgm->smaller))) return NIL;

				/* message flags */
  if ((pgm->answered && !elt->answered) ||
      (pgm->unanswered && elt->answered) ||
      (pgm->deleted && !elt->deleted) ||
      (pgm->undeleted && elt->deleted) ||
      (pgm->draft && !elt->draft) ||
      (pgm->undraft && elt->draft) ||
      (pgm->flagged && !elt->flagged) ||
      (pgm->unflagged && elt->flagged) ||
      (pgm->recent && !elt->recent) ||
      (pgm->old && elt->recent) ||
      (pgm->seen && !elt->seen) ||
      (pgm->unseen && elt->seen)) return NIL;
				/* keywords */
  if (pgm->keyword && !mail_search_keyword (stream,elt,pgm->keyword))
    return NIL;
  if (pgm->unkeyword && mail_search_keyword (stream,elt,pgm->unkeyword))
    return NIL;
				/* sent date ranges */
  if (pgm->sentbefore || pgm->senton || pgm->sentsince) {
    if (!(env->date && mail_parse_date (&delt,env->date) &&
	  (d = (delt.year << 9) + (delt.month << 5) + delt.day))) return NIL;
    if (pgm->sentbefore && (d >= pgm->sentbefore)) return NIL;
    if (pgm->senton && (d != pgm->senton)) return NIL;
    if (pgm->sentsince && (d < pgm->sentsince)) return NIL;
  }
				/* internal date ranges */
  if (pgm->before || pgm->on || pgm->since) {
    if (!elt->year) {		/* make sure have fast data for dates */
      sprintf (tmp,"%ld",elt->msgno);
      mail_fetch_fast (stream,tmp,NIL);
    }
    d = (elt->year << 9) + (elt->month << 5) + elt->day;
    if (pgm->before && (d >= pgm->before)) return NIL;
    if (pgm->on && (d != pgm->on)) return NIL;
    if (pgm->since && (d < pgm->since)) return NIL;
  }
				/* search headers */
  if (pgm->bcc && !mail_search_addr (env->bcc,pgm->bcc)) return NIL;
  if (pgm->cc && !mail_search_addr (env->cc,pgm->cc)) return NIL;
  if (pgm->from && !mail_search_addr (env->from,pgm->from)) return NIL;
  if (pgm->to && !mail_search_addr (env->to,pgm->to)) return NIL;
  if (pgm->subject && !((s.data = (unsigned char *) env->subject) &&
			(s.size = strlen ((char *) s.data)) &&
			mail_search_header (&s,pgm->subject))) return NIL;

  if (hdr = pgm->header) {	/* search header lines */
    unsigned char *t,*v;
    STRINGLIST sth,stc;
    sth.next = stc.next = NIL;	/* only one at a time */
    do {			/* check headers one at a time */
      sth.text.data = hdr->line.data;
      sth.text.size = hdr->line.size;
      stc.text.data = hdr->text.data;
      stc.text.size = hdr->text.size;
      if (!((t = (unsigned char *)
	     mail_fetch_header (stream,msgno,sec,&sth,&s.size,
				FT_INTERNAL | FT_PEEK)) && s.size))
	return NIL;
      v = s.data = (unsigned char *) fs_get (s.size);
      for (i = 0; i < s.size;) {/* copy without leading field name */
	if ((t[i] != ' ') && (t[i] != '\t')) {
	  while ((i < s.size) && (t[i++] != ':'));
	  if ((i < s.size) && (t[i] == ':')) i++;
	}
	while ((i < s.size) && (t[i] != '\015') && (t[i] != '\012'))
	  *v++ = t[i++];	/* copy field text */
	*v++ = '\n';		/* tie off line */
	while (((t[i] == '\015') || (t[i] == '\012')) && i < s.size) i++;
      }
      *v = '\0';		/* tie off results */
      s.size = v - s.data;
      if (!mail_search_header (&s,&stc)) {
	fs_give ((void **) &s.data);
	return NIL;
      }
      fs_give ((void **) &s.data);
    }
    while (hdr = hdr->next);
  }
#if 0
  /* nag MRC to provide a hacked mail_search_text() that does the section
     if we ever need this code active. */
				/* search strings */
  if (pgm->text && !mail_search_text (stream,msgno,pgm->text,LONGT))return NIL;
  if (pgm->body && !mail_search_text (stream,msgno,pgm->body,NIL)) return NIL;
#endif
  if (or = pgm->or) do
    if (!(mail_search_att (stream,msgno,sec,or->first) ||
	  mail_search_att (stream,msgno,sec,or->second))) return NIL;
  while (or = or->next);
  if (not = pgm->not) do if (mail_search_att (stream,msgno,sec, not->pgm))
    return NIL;
  while (not = not->next);
  return T;
}


int
is_ascii_string(str)
    char *str;
{
    if(!str)
      return(0);
    
    while(*str && isascii(*str))
      str++;
    
    return(*str == '\0');
}


void
free_pat_list(pathead)
    PAT_S **pathead;
{
    PAT_S *pat, *the_next_pat;

    if(pathead && *pathead){
	pat = *pathead;
	while(pat){
	    the_next_pat = pat->next;
	    free_pat(&pat);
	    pat = the_next_pat;
	}

	*pathead = NULL;
    }
}


void
free_patline_list(patlinehead)
    PAT_LINE_S **patlinehead;
{
    PAT_LINE_S *patline, *the_next_patline;

    if(patlinehead && *patlinehead){
	patline = *patlinehead;
	while(patline){
	    the_next_patline = patline->next;
	    free_patline(&patline);
	    patline = the_next_patline;
	}

	*patlinehead = NULL;
    }
}


void
free_pat(pat)
    PAT_S **pat;
{
    if(pat && *pat){
	if((*pat)->patgrp)
	  free_patgrp(&(*pat)->patgrp);
	if((*pat)->action)
	  free_action(&(*pat)->action);
	
	fs_give((void **)pat);	/* also sets *pat = NULL */
    }
}


void
free_patline(patline)
    PAT_LINE_S **patline;
{
    if(patline && *patline){
	if((*patline)->filename)
	  fs_give((void **)&(*patline)->filename);
	
	if((*patline)->filepath)
	  fs_give((void **)&(*patline)->filepath);
	
	if((*patline)->first)
	  free_pat_list(&(*patline)->first);

	fs_give((void **)patline);
    }
}


void
free_patgrp(patgrp)
    PATGRP_S **patgrp;
{
    if(patgrp && *patgrp){
	if((*patgrp)->nick)
	  fs_give((void **)&(*patgrp)->nick);
	if((*patgrp)->to)
	  free_pattern(&(*patgrp)->to);
	if((*patgrp)->cc)
	  free_pattern(&(*patgrp)->cc);
	if((*patgrp)->from)
	  free_pattern(&(*patgrp)->from);
	if((*patgrp)->sender)
	  free_pattern(&(*patgrp)->sender);
	if((*patgrp)->news)
	  free_pattern(&(*patgrp)->news);
	if((*patgrp)->subj)
	  free_pattern(&(*patgrp)->subj);

	fs_give((void **)patgrp);
    }
}


void
free_action(action)
    ACTION_S **action;
{
    if(action && *action){
	free_role(&(*action)->role);
	fs_give((void **)action);
    }
}


void
free_pattern(pattern)
    PATTERN_S **pattern;
{
    PATTERN_S *p, *the_next_p;

    if(pattern && *pattern){
	p = *pattern;
	while(p){
	    if(p->substring)
	      fs_give((void **)&p->substring);

	    the_next_p = p->next;
	    fs_give((void **)&p);
	    p = the_next_p;
	}

	*pattern = NULL;
    }
}


void
free_role(role)
    ROLE_ACTION_S **role;
{
    if(role && *role){
	if((*role)->from)
	  mail_free_address(&(*role)->from);
	if((*role)->fcc)
	  fs_give((void **)&(*role)->fcc);
	if((*role)->sig)
	  fs_give((void **)&(*role)->sig);
	if((*role)->template)
	  fs_give((void **)&(*role)->template);
	if((*role)->nick)
	  fs_give((void **)&(*role)->nick);
	if((*role)->inherit_nick)
	  fs_give((void **)&(*role)->inherit_nick);

	fs_give((void **)role);
    }
}


/*
 * Returns an allocated copy of the role.
 *
 * Args   role -- the source role
 *
 * Returns a copy of role.
 */
ROLE_ACTION_S *
copy_role(role)
    ROLE_ACTION_S *role;
{
    ROLE_ACTION_S *newrole = NULL;

    if(role){
	newrole = (ROLE_ACTION_S *)fs_get(sizeof(*newrole));
	memset((void *)newrole, 0, sizeof(*newrole));

	newrole->repl_type = role->repl_type;
	newrole->forw_type = role->forw_type;

	if(role->from)
	  newrole->from = copyaddr(role->from);
	if(role->fcc)
	  newrole->fcc = cpystr(role->fcc);
	if(role->sig)
	  newrole->sig = cpystr(role->sig);
	if(role->template)
	  newrole->template = cpystr(role->template);
	if(role->nick)
	  newrole->nick = cpystr(role->nick);
	if(role->inherit_nick)
	  newrole->inherit_nick = cpystr(role->inherit_nick);
    }

    return(newrole);
}


/*
 * Given a role, return an allocated role. If this role inherits from
 * another role, then do the correct inheriting so that the result is
 * the role we want to use. The inheriting that is done is just the set
 * of set- actions. When no inheriting is happening then this is the
 * same as copy_role.
 *
 * Args   role -- The source role
 *   pattern_h -- The role handle
 *
 * Returns a role.
 */
ROLE_ACTION_S *
combine_inherited_role(role, pattern_h)
    ROLE_ACTION_S *role;
    PAT_HANDLE    *pattern_h;
{
    ROLE_ACTION_S *newrole = NULL, *inherit_role = NULL;

    if(role){
	newrole = (ROLE_ACTION_S *)fs_get(sizeof(*newrole));
	memset((void *)newrole, 0, sizeof(*newrole));

	newrole->repl_type = role->repl_type;
	newrole->forw_type = role->forw_type;

	if(role->inherit_nick && role->inherit_nick[0]){
	    PAT_S *pat;

	    /* find the inherit_nick pattern */
	    for(pat = first_any_pattern(pattern_h);
		pat;
		pat = next_any_pattern(pattern_h)){
		if(pat->patgrp &&
		   pat->patgrp->nick &&
		   !strucmp(role->inherit_nick, pat->patgrp->nick)){
		    /* found it, if it has a role, use it */
		    if(pat->action)
		      inherit_role = pat->action->role;

		    break;
		}
	    }
	}

	if(role->from)
	  newrole->from = copyaddr(role->from);
	else if(inherit_role && inherit_role->from)
	  newrole->from = copyaddr(inherit_role->from);

	if(role->fcc)
	  newrole->fcc = cpystr(role->fcc);
	else if(inherit_role && inherit_role->fcc)
	  newrole->fcc = cpystr(inherit_role->fcc);

	if(role->sig)
	  newrole->sig = cpystr(role->sig);
	else if(inherit_role && inherit_role->sig)
	  newrole->sig = cpystr(inherit_role->sig);

	if(role->template)
	  newrole->template = cpystr(role->template);
	else if(inherit_role && inherit_role->template)
	  newrole->template = cpystr(inherit_role->template);

	if(role->nick)
	  newrole->nick = cpystr(role->nick);
    }

    return(newrole);
}
