/*
 * ========================================================================
 * Copyright 2013-2022 Eduardo Chappa
 * Copyright 2006-2007 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include "../pith/headers.h"
#include "../pith/mailcmd.h"
#include "../pith/conf.h"
#include "../pith/status.h"
#include "../pith/flag.h"
#include "../pith/thread.h"
#include "../pith/util.h"
#include "../pith/folder.h"
#include "../pith/sort.h"
#include "../pith/newmail.h"
#include "../pith/mailview.h"
#include "../pith/mailindx.h"
#include "../pith/save.h"
#include "../pith/news.h"
#include "../pith/sequence.h"
#include "../pith/stream.h"
#include "../pith/ldap.h"
#include "../pith/options.h"
#include "../pith/busy.h"
#include "../pith/icache.h"
#include "../pith/ablookup.h"
#include "../pith/search.h"
#include "../pith/charconv/utf8.h"

#ifdef _WINDOWS
#include "../pico/osdep/mswin.h"
#endif


/*
 * Internal prototypes
 */


/*
 * optional function hooks
 */
int	(*pith_opt_read_msg_prompt)(long, char *);
int	(*pith_opt_reopen_folder)(struct pine *, int *);
int	(*pith_opt_expunge_prompt)(MAILSTREAM *, char *, long);
void	(*pith_opt_begin_closing)(int, char *);
void	  get_new_message_count(MAILSTREAM *, int, long *, long *);
char	 *new_messages_string(MAILSTREAM *);
void      search_for_our_regex_addresses(MAILSTREAM *stream, char type,
					 int not, SEARCHSET *searchset);



/*----------------------------------------------------------------------
   Complain about command on empty folder

  Args: map -- msgmap 
	type -- type of message that's missing
	cmd -- string explaining command attempted

 ----*/
int
any_messages(MSGNO_S *map, char *type, char *cmd)
{
    if(mn_get_total(map) <= 0L){
	q_status_message5(SM_ORDER, 0, 2, "No %s%s%s%s%s",
			  type ? type : "",
			  type ? " " : "",
			  THRD_INDX() ? "threads" : "messages",
			  (!cmd || *cmd != '.') ? " " : "",
			  cmd ? cmd : "in folder");
	return(FALSE);
    }

    return(TRUE);
}


/*----------------------------------------------------------------------
   test whether or not we have a valid stream to set flags on

  Args: state -- pine state containing vital signs
	cmd -- string explaining command attempted
	permflag -- associated permanent flag state

  Result: returns 1 if we can set flags, otw 0 and complains

 ----*/
int
can_set_flag(struct pine *state, char *cmd, int permflag)
{
    if((!permflag && READONLY_FOLDER(state->mail_stream))
       || sp_dead_stream(state->mail_stream)){
	q_status_message2(SM_ORDER | (sp_dead_stream(state->mail_stream)
				        ? SM_DING : 0),
			  0, 3,
			  "Can't %s message.  Folder is %s.", cmd,
			  (sp_dead_stream(state->mail_stream)) ? "closed" : "read-only");
	return(FALSE);
    }

    return(TRUE);
}


/*----------------------------------------------------------------------
   Complain about command on empty folder

  Args: type -- type of message that's missing
	cmd -- string explaining command attempted

 ----*/
void
cmd_cancelled(char *cmd)
{
    /* TRANSLATORS: Arg is replaced with the command name or the word Command */
    q_status_message1(SM_INFO, 0, 2, _("%s cancelled"), cmd ? cmd : _("Command"));
}


/*----------------------------------------------------------------------
   Execute DELETE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message delete flag set

 ----*/
int
cmd_delete(struct pine *state, MSGNO_S *msgmap, int copts,
	   char *(*cmd_action_f)(struct pine *, MSGNO_S *))
{
    int	  lastmsg, rv = 0;
    long  msgno, del_count = 0L, new;
    char *sequence = NULL, prompt[128];

    dprint((4, "\n - delete message -\n"));
    if(!(any_messages(msgmap, NULL, "to Delete")
	 && can_set_flag(state, "delete", state->mail_stream->perm_deleted)))
      return rv;

    rv++;

    if(sp_io_error_on_stream(state->mail_stream)){
	sp_set_io_error_on_stream(state->mail_stream, 0);
	pine_mail_check(state->mail_stream);		/* forces write */
    }

    if(MCMD_ISAGG(copts)){
	sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 0);
	snprintf(prompt, sizeof(prompt), "%ld%s message%s marked for deletion",
		del_count, (copts  & MCMD_AGG_2) ? "" : " selected", plural(del_count));
    }
    else{
	long rawno;

	msgno	  = mn_get_cur(msgmap);
	rawno     = mn_m2raw(msgmap, msgno);
	del_count = 1L;				/* return current */
	sequence  = cpystr(long2string(rawno));
	lastmsg	  = (msgno >= mn_get_total(msgmap));
	snprintf(prompt, sizeof(prompt), "%s%s marked for deletion",
		lastmsg ? "Last message" : "Message ",
		lastmsg ? "" : long2string(msgno));
    }

    dprint((3, "DELETE: msg %s\n", sequence ? sequence : "?"));
    new = sp_new_mail_count(state->mail_stream);
    mail_flag(state->mail_stream, sequence, "\\DELETED", ST_SET);
    fs_give((void **) &sequence);
    if(new != sp_new_mail_count(state->mail_stream))
      process_filter_patterns(state->mail_stream, state->msgmap,
			      sp_new_mail_count(state->mail_stream));

    if(cmd_action_f){
	char *rv;

	if((rv = (*cmd_action_f)(state, msgmap)) != NULL)
	  strncat(prompt, rv, sizeof(prompt) - strlen(prompt)- 1);
    }

    if(!(copts & MCMD_SILENT))
      q_status_message(SM_ORDER, 0, 3, prompt);

    return rv;
}


/*----------------------------------------------------------------------
   Execute UNDELETE message command

  Args: state --  Various satate info
        msgmap --  map of c-client to local message numbers

 Result: with side effect of "current" message delete flag UNset

 ----*/
int
cmd_undelete(struct pine *state, MSGNO_S *msgmap, int copts)
{
    long	  del_count;
    char	 *sequence;
    int		  wasdeleted = FALSE, rv = 0;
    MESSAGECACHE *mc;

    dprint((4, "\n - undelete -\n"));
    if(!(any_messages(msgmap, NULL, "to Undelete")
	 && can_set_flag(state, "undelete", state->mail_stream->perm_deleted)))
      return rv;

    rv++;

    if(MCMD_ISAGG(copts)){
	del_count = 0L;				/* return current */
	sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 1);
	wasdeleted = TRUE;
    }
    else{
	long rawno;
	int  exbits = 0;

	del_count = 1L;				/* return current */
	rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
	sequence  = cpystr(long2string(rawno));
	wasdeleted = (state->mail_stream
	              && rawno > 0L && rawno <= state->mail_stream->nmsgs
		      && (mc = mail_elt(state->mail_stream, rawno))
		       && mc->valid
		       && mc->deleted);
	/*
	 * Mark this message manually flagged so we don't re-filter it
	 * with a filter which only sets flags.
	 */
	if(msgno_exceptions(state->mail_stream, rawno, "0", &exbits, FALSE))
	  exbits |= MSG_EX_MANUNDEL;
	else
	  exbits = MSG_EX_MANUNDEL;

	msgno_exceptions(state->mail_stream, rawno, "0", &exbits, TRUE);
    }

    dprint((3, "UNDELETE: msg %s\n", sequence ? sequence : "?"));

    mail_flag(state->mail_stream, sequence, "\\DELETED", 0L);
    fs_give((void **) &sequence);

    if((copts & MCMD_SILENT) == 0){
	if(del_count == 1L && MCMD_ISAGG(copts) == 0){
	    q_status_message(SM_ORDER, 0, 3,
			     wasdeleted
			       ? _("Deletion mark removed, message won't be deleted")
			       : _("Message not marked for deletion; no action taken"));
	}
	else
	  q_status_message2(SM_ORDER, 0, 3,
			    _("Deletion mark removed from %s message%s"),
			    comatose(del_count), plural(del_count));
    }

    if(sp_io_error_on_stream(state->mail_stream)){
	sp_set_io_error_on_stream(state->mail_stream, 0);
	pine_mail_check(state->mail_stream);		/* forces write */
    }

    return rv;
}


int
cmd_expunge_work(MAILSTREAM *stream, MSGNO_S *msgmap, char *seq)
{
    long  old_max_msgno;
    int	  rv = 0;

    old_max_msgno = mn_get_total(msgmap);
    delete_filtered_msgs(stream);
    ps_global->expunge_in_progress = 1;
    mail_expunge_full(stream, seq, 0);
    ps_global->expunge_in_progress = 0;

    dprint((2,"expunge complete cur:%ld max:%ld\n",
	      mn_get_cur(msgmap), mn_get_total(msgmap)));
    /*
     * This is only actually necessary if this causes the width of the
     * message number field to change.  That is, it depends on the
     * format the user is using as well as on the max_msgno.  Since it
     * should be rare, we'll just do it whenever it happens.
     * Also have to check for an increase in max_msgno on new mail.
     */
    if((old_max_msgno >= 1000L && mn_get_total(msgmap) < 1000L)
       || (old_max_msgno >= 10000L && mn_get_total(msgmap) < 10000L)
       || (old_max_msgno >= 100000L && mn_get_total(msgmap) < 100000L)){
	clear_index_cache(stream, 0);
	rv = 1;
    }

    /*
     * mm_exists and mm_expunge take care of updating max_msgno
     * and selecting a new message should the selected get removed
     */


    reset_check_point(stream);

    return(rv);
}


CONTEXT_S *
broach_get_folder(CONTEXT_S *context, int *inbox, char **folder)
{
    CONTEXT_S *tc;

    if(ps_global->goto_default_rule == GOTO_LAST_FLDR){
	tc = context ? context : ps_global->context_current;
	*inbox = 1;		/* fill in last_folder below */
    }
    else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN){
	tc = (ps_global->context_list->use & CNTXT_INCMNG)
	  ? ps_global->context_list->next : ps_global->context_list;
	ps_global->last_unambig_folder[0] = '\0';
	*inbox = 1;		/* fill in last_folder below */
    }
    else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX){
	tc = (ps_global->context_list->use & CNTXT_INCMNG)
	  ? ps_global->context_list->next : ps_global->context_list;
	tc->last_folder[0] = '\0';
	*inbox = 0;
	ps_global->last_unambig_folder[0] = '\0';
    }
    else{
	*inbox = (ps_global->cur_folder
	          && ps_global->inbox_name
		  && strucmp(ps_global->cur_folder,ps_global->inbox_name) == 0
		  && (!ps_global->context_current
		      || ps_global->context_current->use & CNTXT_INCMNG
		      || (!(ps_global->context_list->use & CNTXT_INCMNG)
		          && ps_global->context_current == ps_global->context_list)));
	if(!*inbox)
	  tc = ps_global->context_list;		/* inbox's context */
	else if(ps_global->goto_default_rule == GOTO_INBOX_FIRST_CLCTN){
	    tc = (ps_global->context_list->use & CNTXT_INCMNG)
		  ? ps_global->context_list->next : ps_global->context_list;
	    ps_global->last_unambig_folder[0] = '\0';
	}
	else
	  tc = context ? context : ps_global->context_current;
    }

    if(folder){
	if(!*inbox){
	    *folder = ps_global->inbox_name;
	}
	else
	  *folder = (ps_global->last_unambig_folder[0])
			  ? ps_global->last_unambig_folder
			  : ((tc->last_folder[0]) ? tc->last_folder : NULL);
    }

    return(tc);
}


/*----------------------------------------------------------------------
    Actually attempt to open given folder 

  Args: newfolder -- The folder name to open
        streamp   -- Candidate stream for recycling. This stream will either
	             be re-used, or it will be closed.

 Result:  1 if the folder was successfully opened
          0 if the folder open failed and went back to old folder
         -1 if open failed and no folder is left open
      
  Attempt to open the folder name given. If the open of the new folder
  fails then the previously open folder will remain open, unless
  something really bad has happened. The designate inbox will always be
  kept open, and when a request to open it is made the already open
  stream will be used.
  ----*/
int
do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp,
		 long unsigned int flags)
{
    MAILSTREAM *m, *strm, *stream = streamp ? *streamp : NULL;
    int         open_inbox, rv, old_tros, we_cancel = 0,
                do_reopen = 0, n, was_dead = 0, cur_already_set = 0;
    char        expanded_file[MAX(MAXPATH,MAILTMPLEN)+1],
	       *old_folder, *old_path, *p, *report;
    unsigned char *fname;
    long        openmode, rflags = 0L, pc = 0L, cur, raw;
    ENVELOPE   *env = NULL;
    char        status_msg[81];
    SortOrder	old_sort;
    unsigned    perfolder_startup_rule;
    char        tmp1[MAILTMPLEN], tmp2[MAILTMPLEN], *lname, *mname;

    openmode = SP_USERFLDR;

    dprint((1, "About to open folder \"%s\"    inbox is: \"%s\"\n",
	       newfolder ? newfolder : "?",
	       ps_global->inbox_name ? ps_global->inbox_name : "?"));

    /*
     *--- Set flag that we're opening the inbox, a special case.
     *
     * We want to know if inbox is being opened either by name OR
     * fully qualified path...
     */
    if(strucmp(newfolder, ps_global->inbox_name) == 0)
      open_inbox = (flags & DB_INBOXWOCNTXT || new_context == ps_global->context_list);
    else{
	open_inbox = (strcmp(newfolder, ps_global->VAR_INBOX_PATH) == 0
		      || same_remote_mailboxes(newfolder, ps_global->VAR_INBOX_PATH)
		      || (!IS_REMOTE(newfolder)
			  && (lname=mailboxfile(tmp1,newfolder))
			  && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
			  && !strcmp(lname,mname)));

	/* further checking for inbox open */
	if(!open_inbox && new_context && context_isambig(newfolder)){
	    if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
		/*
		 * Check for an incoming folder other
		 * than INBOX that also point to INBOX.
		 */
		open_inbox = (strucmp(p, ps_global->inbox_name) == 0
			      || strcmp(p, ps_global->VAR_INBOX_PATH) == 0
			      || same_remote_mailboxes(p, ps_global->VAR_INBOX_PATH)
			      || (!IS_REMOTE(p)
				  && (lname=mailboxfile(tmp1,p))
				  && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
				  && !strcmp(lname,mname)));
	    }
	    else if(!(new_context->use & CNTXT_INCMNG)){
		char tmp3[MAILTMPLEN];

		/*
		 * Check to see if we are opening INBOX using the folder name
		 * and a context. We won't have recognized this is the
		 * same as INBOX without applying the context first.
		 */
		context_apply(tmp3, new_context, newfolder, sizeof(tmp3));
		open_inbox = (strucmp(tmp3, ps_global->inbox_name) == 0
			      || strcmp(tmp3, ps_global->VAR_INBOX_PATH) == 0
			      || same_remote_mailboxes(tmp3, ps_global->VAR_INBOX_PATH)
			      || (!IS_REMOTE(tmp3)
				  && (lname=mailboxfile(tmp1,tmp3))
				  && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
				  && !strcmp(lname,mname)));
	    }
	}
    }

    if(open_inbox)
      new_context = ps_global->context_list; /* restore first context */

    was_dead = sp_a_locked_stream_is_dead();

    /*----- Little to do to if reopening same folder -----*/
    if(new_context == ps_global->context_current && ps_global->mail_stream
       && (strcmp(newfolder, ps_global->cur_folder) == 0
           || (open_inbox && sp_flagged(ps_global->mail_stream, SP_INBOX)))){
	if(stream){
	    pine_mail_close(stream);	/* don't need it */
	    stream = NULL;
	}

	if(sp_dead_stream(ps_global->mail_stream))
	  do_reopen++;
	
	/*
	 * If it is a stream which could probably discover newmail by
	 * reopening and user has YES set for those streams, or it
	 * is a stream which may discover newmail by reopening and
	 * user has YES set for those stream, then do_reopen.
	 */
	if(!do_reopen
	   &&
	   (((ps_global->mail_stream->dtb
	      && ((ps_global->mail_stream->dtb->flags & DR_NONEWMAIL)
		  || (ps_global->mail_stream->rdonly
		      && ps_global->mail_stream->dtb->flags
					      & DR_NONEWMAILRONLY)))
	     && (ps_global->reopen_rule == REOPEN_YES_YES
	         || ps_global->reopen_rule == REOPEN_YES_ASK_Y
	         || ps_global->reopen_rule == REOPEN_YES_ASK_N
	         || ps_global->reopen_rule == REOPEN_YES_NO))
	    ||
	    ((ps_global->mail_stream->dtb
	      && ps_global->mail_stream->rdonly
	      && !(ps_global->mail_stream->dtb->flags & DR_LOCAL))
	     && (ps_global->reopen_rule == REOPEN_YES_YES))))
	  do_reopen++;

	/*
	 * If it is a stream which could probably discover newmail by
	 * reopening and user has ASK set for those streams, or it
	 * is a stream which may discover newmail by reopening and
	 * user has ASK set for those stream, then ask.
	 */
	if(!do_reopen
	   && pith_opt_reopen_folder
	   && (*pith_opt_reopen_folder)(ps_global, &do_reopen) < 0){
	    cmd_cancelled(NULL);
	    return(0);
	}

	if(do_reopen){
	    /*
	     * If it's not healthy or if the user explicitly wants to
	     * do a reopen, we reset things and fall thru
	     * to actually reopen it.
	     */
	    if(sp_dead_stream(ps_global->mail_stream)){
		dprint((2, "Stream was dead, reopening \"%s\"\n",
				      newfolder ? newfolder : "?"));
	    }

	    /* clean up */
	    pine_mail_actually_close(ps_global->mail_stream);
	    ps_global->mangled_header = 1;
	    clear_index_cache(ps_global->mail_stream, 0);
	}
	else{
	    if(!(flags & DB_NOVISIT))
	      sp_set_recent_since_visited(ps_global->mail_stream, 0L);

	    return(1);			/* successful open of same folder! */
	}
    }

    /*
     * If ambiguous foldername (not fully qualified), make sure it's
     * not a nickname for a folder in the given context...
     */

    /* might get reset below */
    strncpy(expanded_file, newfolder, sizeof(expanded_file));
    expanded_file[sizeof(expanded_file)-1] = '\0';

    if(!open_inbox && new_context && context_isambig(newfolder)){
	if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
	    strncpy(expanded_file, p, sizeof(expanded_file));
	    expanded_file[sizeof(expanded_file)-1] = '\0';
	    dprint((2, "broach_folder: nickname for %s is %s\n",
		       expanded_file ? expanded_file : "?",
		       newfolder ? newfolder : "?"));
	}
	else if((new_context->use & CNTXT_INCMNG)
		&& (folder_index(newfolder, new_context, FI_FOLDER) < 0)
		&& !is_absolute_path(newfolder)){
	    fname = folder_name_decoded((unsigned char *)newfolder);
	    q_status_message1(SM_ORDER, 3, 4,
			    _("Can't find Incoming Folder %s."), fname ? (char *) fname : newfolder);
	    if(stream)
	      pine_mail_close(stream);

	    if(fname)
		fs_give((void **)&fname);

	    return(0);
	}
    }

    /*--- Opening inbox, inbox has been already opened, the easy case ---*/
    /*
     * [ It is probably true that we could eliminate most of this special ]
     * [ inbox stuff and just get the inbox stream back when we do the    ]
     * [ context_open below, but figuring that out hasn't been done.      ]
     */
    if(open_inbox && (strm=sp_inbox_stream())){
      if(sp_dead_stream(strm)){
	/* 
	 * if dead INBOX, just close it and let it be reopened.
	 * This is different from the do_reopen case above,
	 * because we're going from another open mail folder to the
	 * dead INBOX.
	 */
	dprint((2, "INBOX was dead, closing before reopening\n"));
	pine_mail_actually_close(strm);
      }
      else{
	/*
	 * Clean up the mail_stream we're leaving.
	 */
	if(ps_global->mail_stream
	   && (!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
	       || (sp_flagged(ps_global->mail_stream, SP_INBOX)
	           && F_ON(F_EXPUNGE_INBOX, ps_global))
	       || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
		   && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
	           && F_ON(F_EXPUNGE_STAYOPENS, ps_global))))
          expunge_and_close(ps_global->mail_stream, NULL,
			    sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
			      ? EC_NO_CLOSE : EC_NONE);
	else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
	        && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
	    /*
	     * We want to save our position in the folder so that when we
	     * come back to this folder again, we can place the cursor on
	     * a reasonable message number.
	     */
	    sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);

	    if(ps_global->mail_stream->nmsgs > 0L){
		cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
		raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
		if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
		  env = pine_mail_fetchstructure(ps_global->mail_stream,
						 raw, NULL);
		
		if(env && env->message_id && env->message_id[0])
		  sp_set_saved_cur_msg_id(ps_global->mail_stream,
					  env->message_id);
	    }
	}

	/*
	 * Make the already open inbox the current mailbox.
	 */
	ps_global->mail_stream = strm;
	ps_global->msgmap      = sp_msgmap(strm);

	if(was_dead && pith_opt_icon_text)
	  (*pith_opt_icon_text)(NULL, IT_MCLOSED);

	dprint((7, "%ld %ld %x\n",
		   mn_get_cur(ps_global->msgmap),
                   mn_get_total(ps_global->msgmap),
		   ps_global->mail_stream));
	/*
	 * remember last context and folder
	 */
	if(context_isambig(ps_global->cur_folder)){
	    ps_global->context_last = ps_global->context_current;
	    snprintf(ps_global->context_current->last_folder,
		     sizeof(ps_global->context_current->last_folder),
		     "%.*s", (int) sizeof(ps_global->context_current->last_folder) - 1,ps_global->cur_folder);
	    ps_global->last_unambig_folder[0] = '\0';
	}
	else{
	    ps_global->context_last = NULL;
	    snprintf(ps_global->last_unambig_folder,
		     sizeof(ps_global->last_unambig_folder),
		     "%s", ps_global->cur_folder);
	}

	p = sp_fldr(ps_global->mail_stream) ? sp_fldr(ps_global->mail_stream)
					    : ps_global->inbox_name;
	strncpy(ps_global->cur_folder, p, sizeof(ps_global->cur_folder)-1);
	ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
	ps_global->context_current = ps_global->context_list;
	reset_index_format();
	clear_index_cache(ps_global->mail_stream, 0);
        /* MUST sort before restoring msgno! */
	refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);
	report = new_messages_string(ps_global->mail_stream);
	q_status_message3(SM_ORDER, 0, 3,
			  (mn_get_total(ps_global->msgmap) > 1)
			    ? _("Opened folder \"%s\" with %s messages%s")
			    : _("Opened folder \"%s\" with %s message%s"),
			  ps_global->inbox_name, 
                          long2string(mn_get_total(ps_global->msgmap)),
			  report ? report : "");
	if(report)
	   fs_give((void **)&report);

#ifdef	_WINDOWS
	mswin_settitle(ps_global->inbox_name);
#endif
	if(stream)
	  pine_mail_close(stream);

	if(!(flags & DB_NOVISIT))
	  sp_set_recent_since_visited(ps_global->mail_stream, 0L);

	return(1);
      }
    }

    if(!new_context && !expand_foldername(expanded_file,sizeof(expanded_file))){
	if(stream)
	  pine_mail_close(stream);

	return(0);
    }

    /*
     * This is a safe time to clean up dead streams because nothing should
     * be referencing them right now.
     */
    sp_cleanup_dead_streams();

    old_folder = NULL;
    old_path   = NULL;
    old_sort   = SortArrival;			/* old sort */
    old_tros   = 0;				/* old reverse sort ? */
    /*---- now close the old one we had open if there was one ----*/
    if(ps_global->mail_stream != NULL){
        old_folder   = cpystr(ps_global->cur_folder);
        old_path     = cpystr(ps_global->mail_stream->original_mailbox
	                        ? ps_global->mail_stream->original_mailbox
				: ps_global->mail_stream->mailbox);
	old_sort     = mn_get_sort(ps_global->msgmap);
	old_tros     = mn_get_revsort(ps_global->msgmap);
	if(!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
	    || (sp_flagged(ps_global->mail_stream, SP_INBOX)
	        && F_ON(F_EXPUNGE_INBOX, ps_global))
	    || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
	        && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
	        && F_ON(F_EXPUNGE_STAYOPENS, ps_global)))
          expunge_and_close(ps_global->mail_stream, NULL,
			    sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
			      ? EC_NO_CLOSE : EC_NONE);
	else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
	        && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
	    /*
	     * We want to save our position in the folder so that when we
	     * come back to this folder again, we can place the cursor on
	     * a reasonable message number.
	     */
	    
	    sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);

	    if(ps_global->mail_stream->nmsgs > 0L){
		cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
		raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
		if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
		  env = pine_mail_fetchstructure(ps_global->mail_stream,
						 raw, NULL);
		
		if(env && env->message_id && env->message_id[0])
		  sp_set_saved_cur_msg_id(ps_global->mail_stream,
					  env->message_id);
	    }
	}

	ps_global->mail_stream = NULL;
    }

    snprintf(status_msg, sizeof(status_msg), "%sOpening \"", do_reopen ? "Re-" : "");
    fname = folder_name_decoded((unsigned char *)newfolder);
    strncat(status_msg, pretty_fn(fname ? (char *) fname : newfolder),
	    sizeof(status_msg)-strlen(status_msg) - 2);
    if(fname) fs_give((void **)&fname);
    status_msg[sizeof(status_msg)-2] = '\0';
    strncat(status_msg, "\"", sizeof(status_msg)-strlen(status_msg) - 1);
    status_msg[sizeof(status_msg)-1] = '\0';
    we_cancel = busy_cue(status_msg, NULL, 0);

    /* 
     * if requested, make access to folder readonly (only once)
     */
    if(ps_global->open_readonly_on_startup){
	openmode |= OP_READONLY;
	ps_global->open_readonly_on_startup = 0;
    }

    if(!(flags & DB_NOVISIT))
      ps_global->first_open_was_attempted = 1;

    openmode |= SP_USEPOOL;

    if(stream)
      sp_set_first_unseen(stream, 0L);

    /* in case we closed the old stream by cancelling the connection, do
     * not let that interfere with opening the new stream.
     */
    ps_global->user_says_cancel = 0;

    m = context_open((new_context && !open_inbox) ? new_context : NULL,
		     stream, 
		     open_inbox ? ps_global->VAR_INBOX_PATH : expanded_file,
		     openmode | (open_inbox ? SP_INBOX : 0),
		     &rflags);

    if(m == NULL
	&& ps_global->in_xoauth2_auth
	&& ps_global->preserve_password){
        ps_global->in_xoauth2_auth = ps_global->user_says_cancel = ps_global->preserve_password = 0;
        m = context_open((new_context && !open_inbox) ? new_context : NULL,
		     stream,
		     open_inbox ? ps_global->VAR_INBOX_PATH : expanded_file,
		     openmode | (open_inbox ? SP_INBOX : 0),
		     &rflags);
    }

    /*
     * We aren't in a situation where we want a single cancel to
     * apply to multiple opens.
     */
    ps_global->user_says_cancel = 0;

    /* we are not in authentication mode anymore */
    ps_global->preserve_password = ps_global->in_xoauth2_auth = 0;

    if(streamp)
      *streamp = m;


    dprint((8, "Opened folder %p \"%s\" (context: \"%s\")\n",
               m, (m && m->mailbox) ? m->mailbox : "nil",
	       (new_context && new_context->context)
	         ? new_context->context : "nil"));


    /* Can get m != NULL if correct passwd for remote, but wrong name */
    if(m == NULL || m->halfopen){
	/*-- non-existent local mailbox, or wrong passwd for remote mailbox--*/
        /* fall back to currently open mailbox */
	if(we_cancel)
	  cancel_busy_cue(-1);

	ps_global->mail_stream = NULL;
	if(m)
	  pine_mail_actually_close(m);

        rv = 0;
        dprint((8, "Old folder: \"%s\"\n",
               old_folder == NULL ? "" : old_folder));
        if(old_folder != NULL){
            if(strucmp(old_folder, ps_global->inbox_name) == 0){
                ps_global->mail_stream = sp_inbox_stream();
		ps_global->msgmap      = sp_msgmap(ps_global->mail_stream);

                dprint((8, "Reactivate inbox %ld %ld %p\n",
                           mn_get_cur(ps_global->msgmap),
                           mn_get_total(ps_global->msgmap),
                           ps_global->mail_stream));
		p = sp_fldr(ps_global->mail_stream)
			  ? sp_fldr(ps_global->mail_stream)
			  : ps_global->inbox_name;
		strncpy(ps_global->cur_folder, p,
			sizeof(ps_global->cur_folder)-1);
		ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
            }
	    else{
                ps_global->mail_stream = pine_mail_open(NULL, old_path,
							openmode, &rflags);
                /* mm_log will take care of error message here */
                if(ps_global->mail_stream == NULL){
                    rv = -1;
                }
		else{
		    ps_global->msgmap = sp_msgmap(ps_global->mail_stream);
		    mn_set_sort(ps_global->msgmap, old_sort);
		    mn_set_revsort(ps_global->msgmap, old_tros);
		    ps_global->mangled_header = 1;
		    reset_index_format();
		    clear_index_cache(ps_global->mail_stream, 0);

		    if(!(rflags & SP_MATCH)){
			sp_set_expunge_count(ps_global->mail_stream, 0L);
			sp_set_new_mail_count(ps_global->mail_stream, 0L);
			sp_set_dead_stream(ps_global->mail_stream, 0);
			sp_set_noticed_dead_stream(ps_global->mail_stream, 0);

			reset_check_point(ps_global->mail_stream);
			if(IS_NEWS(ps_global->mail_stream)
			   && ps_global->mail_stream->rdonly)
			  msgno_exclude_deleted(ps_global->mail_stream,
					    sp_msgmap(ps_global->mail_stream),
					    NULL);

			if(mn_get_total(ps_global->msgmap) > 0)
			  mn_set_cur(ps_global->msgmap,
				     first_sorted_flagged(F_NONE,
						      ps_global->mail_stream,
						      0L,
						      THREADING()
							  ? 0 : FSF_SKIP_CHID));

			if(!(mn_get_sort(ps_global->msgmap) == SortArrival
			     && !mn_get_revsort(ps_global->msgmap)))
			  refresh_sort(ps_global->mail_stream,
				       ps_global->msgmap, SRT_NON);
		    }

		    fname = folder_name_decoded((unsigned char *)old_folder);
                    q_status_message1(SM_ORDER, 0, 3,
				      "Folder \"%s\" reopened", fname ? (char *)fname : old_folder);
		    if(fname)
		      fs_give((void **)&fname);
                }
            }

	    if(rv == 0)
	      mn_set_cur(ps_global->msgmap,
			 MIN(mn_get_cur(ps_global->msgmap), 
			     mn_get_total(ps_global->msgmap)));

            fs_give((void **)&old_folder);
            fs_give((void **)&old_path);
        }
	else
	  rv = -1;

        if(rv == -1){
            q_status_message(SM_ORDER | SM_DING, 0, 4, _("No folder opened"));
	    mn_set_total(ps_global->msgmap, 0L);
	    mn_set_nmsgs(ps_global->msgmap, 0L);
	    mn_set_cur(ps_global->msgmap, -1L);
	    ps_global->cur_folder[0] = '\0';
        }

	if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
	  (*pith_opt_icon_text)(NULL, IT_MCLOSED);

	if(ps_global->mail_stream && !(flags & DB_NOVISIT))
	  sp_set_recent_since_visited(ps_global->mail_stream, 0L);

	if(ps_global->mail_stream)
	  sp_set_first_unseen(ps_global->mail_stream, 0L);

        return(rv);
    }
    else{
        if(old_folder != NULL){
            fs_give((void **)&old_folder);
            fs_give((void **)&old_path);
        }
    }

    update_folder_unseen_by_stream(m, UFU_NONE);

    /*----- success in opening the new folder ----*/
    dprint((2, "Opened folder \"%s\" with %ld messages\n",
	       m->mailbox ? m->mailbox : "?", m->nmsgs));


    /*--- A Little house keeping ---*/

    ps_global->mail_stream = m;
    if(!(flags & DB_NOVISIT))
      sp_set_recent_since_visited(ps_global->mail_stream, 0L);

    ps_global->msgmap = sp_msgmap(m);
    if(!(rflags & SP_MATCH)){
	sp_set_expunge_count(m, 0L);
	sp_set_new_mail_count(m, 0L);
	sp_set_dead_stream(m, 0);
	sp_set_noticed_dead_stream(m, 0);
	sp_set_mail_box_changed(m, 0);
	reset_check_point(m);
    }

    if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
      (*pith_opt_icon_text)(NULL, IT_MCLOSED);

    ps_global->last_unambig_folder[0] = '\0';

    /*
     * remember old folder and context...
     */
    if(context_isambig(ps_global->cur_folder)){
	ps_global->context_last = ps_global->context_current;
	snprintf(ps_global->context_current->last_folder,
		 sizeof(ps_global->context_current->last_folder),
		 "%.*s", (int) sizeof(ps_global->context_current->last_folder) - 1, ps_global->cur_folder);
	ps_global->last_unambig_folder[0] = '\0';
    }
    else{
	ps_global->context_last = NULL;
	snprintf(ps_global->last_unambig_folder,
		 sizeof(ps_global->last_unambig_folder),
		 "%s", ps_global->cur_folder);
    }

    /* folder in a subdir of context? */
    if(ps_global->context_current->dir->prev)
      snprintf(ps_global->cur_folder, sizeof(ps_global->cur_folder), "%s%s",
		ps_global->context_current->dir->ref, newfolder);
    else{
	strncpy(ps_global->cur_folder,
		(open_inbox) ? ps_global->inbox_name : newfolder,
		sizeof(ps_global->cur_folder)-1);
	ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
    }

    sp_set_fldr(ps_global->mail_stream, ps_global->cur_folder);

    if(new_context){
	ps_global->context_last    = ps_global->context_current;
	ps_global->context_current = new_context;

	if(!open_inbox)
	  sp_set_context(ps_global->mail_stream, ps_global->context_current);
    }

    clear_index_cache(ps_global->mail_stream, 0);
    reset_index_format();

    /*
     * Start news reading with messages the user's marked deleted
     * hidden from view...
     */
    if(IS_NEWS(ps_global->mail_stream) && ps_global->mail_stream->rdonly)
      msgno_exclude_deleted(ps_global->mail_stream, ps_global->msgmap, NULL);

    if(we_cancel && F_OFF(F_QUELL_FILTER_MSGS, ps_global))
      cancel_busy_cue(0);

    /*
     * If the stream we got from the open above was already opened earlier
     * for some temporary use, then it wouldn't have been filtered. That's
     * why we need this flag, so that we will filter if needed.
     */
    if(!sp_flagged(ps_global->mail_stream, SP_FILTERED))
      process_filter_patterns(ps_global->mail_stream, ps_global->msgmap, 0L);

    /*
     * If no filtering messages wait until here to cancel the busy cue
     * because the user will be waiting for that filtering with nothing
     * showing the activity otherwise.
     */
    if(we_cancel && F_ON(F_QUELL_FILTER_MSGS, ps_global))
      cancel_busy_cue(0);

    if(!(rflags & SP_MATCH) || !(rflags & SP_LOCKED))
      reset_sort_order(SRT_VRB);
    else if(sp_new_mail_count(ps_global->mail_stream) > 0L
          || sp_unsorted_newmail(ps_global->mail_stream)
          || sp_need_to_rethread(ps_global->mail_stream))
      refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);

    report = new_messages_string(ps_global->mail_stream);
    fname = folder_name_decoded((unsigned char *)newfolder);
    q_status_message7(SM_ORDER, 0, 4,
		    "%s \"%s\" opened with %s message%s%s%s%s",
			IS_NEWS(ps_global->mail_stream)
			  ? "News group" : "Folder",
			open_inbox ? pretty_fn(fname ? (char *) fname : newfolder) 
				   : (fname ? (char *)fname : newfolder),
			comatose(mn_get_total(ps_global->msgmap)),
			plural(mn_get_total(ps_global->msgmap)),
			(!open_inbox
			 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED))
			    ? " (StayOpen)" : "",
			READONLY_FOLDER(ps_global->mail_stream)
						? " READONLY" : "",
			report ? report : "");

   if(fname)
     fs_give((void **)&fname);

   if(report)
     fs_give((void **)&report);

#ifdef	_WINDOWS
    mswin_settitle(pretty_fn(newfolder));
#endif
    /*
     * Set current message number when re-opening Stay-Open or
     * cached folders.
     */
    if(rflags & SP_MATCH){
	if(rflags & SP_LOCKED){
	    if(F_OFF(F_STARTUP_STAYOPEN, ps_global)
	       && (cur = get_msgno_by_msg_id(ps_global->mail_stream,
			     sp_saved_cur_msg_id(ps_global->mail_stream),
			     ps_global->msgmap)) >= 1L
	       && cur <= mn_get_total(ps_global->msgmap)){
	      cur_already_set++;
	      mn_set_cur(ps_global->msgmap, (MsgNo) cur);
	      if(flags & DB_FROMTAB){
		/*
		 * When we TAB to a folder that is a StayOpen folder we try
		 * to increment the current message # by one instead of doing
		 * some search again. Some people probably won't like this
		 * behavior, especially if the new message that has arrived
		 * comes before where we are in the index. That's why we have
		 * the F_STARTUP_STAYOPEN feature above.
		 */
		mn_inc_cur(m, ps_global->msgmap, MH_NONE);
	      }
	      /* else leave it where it is */

	      adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
	    }
	}
	else{
	    /*
	     * If we're reopening a cached open stream that wasn't explicitly
	     * kept open by the user, then the user expects it to act pretty
	     * much like we are re-opening the stream. A problem is that the
	     * recent messages are still recent because we haven't closed the
	     * stream, so we fake a quasi-recentness by remembering the last
	     * uid assigned on the stream when we pine_mail_close. Then when
	     * we come back messages with uids higher than that are recent.
	     *
	     * If uid_validity has changed, then we don't use any special
	     * treatment, but just do the regular search.
	     */
	    if(m->uid_validity == sp_saved_uid_validity(m)){
		long i;

		/*
		 * Because first_sorted_flagged uses sequence numbers, find the
		 * sequence number of the first message after the old last
		 * uid assigned. I.e., the first recent message.
		 */
		for(i = m->nmsgs; i > 0L; i--)
		  if(mail_uid(m, i) <= sp_saved_uid_last(m))
		    break;
		
		if(i > 0L && i < m->nmsgs)
		  pc = i+1L;
	    }
	}
    }


    if(!cur_already_set && mn_get_total(ps_global->msgmap) > 0L){

	perfolder_startup_rule = reset_startup_rule(ps_global->mail_stream);

	if(ps_global->start_entry > 0){
	    mn_set_cur(ps_global->msgmap, mn_get_revsort(ps_global->msgmap)
		       ? first_sorted_flagged(F_NONE, m,
					      ps_global->start_entry,
					      THREADING() ? 0 : FSF_SKIP_CHID)
		       : first_sorted_flagged(F_SRCHBACK, m,
					      ps_global->start_entry,
					      THREADING() ? 0 : FSF_SKIP_CHID));
	    ps_global->start_entry = 0;
        }
	else if(perfolder_startup_rule != IS_NOTSET ||
	        open_inbox ||
		ps_global->context_current->use & CNTXT_INCMNG){
	    unsigned use_this_startup_rule;

	    if(perfolder_startup_rule != IS_NOTSET)
	      use_this_startup_rule = perfolder_startup_rule;
	    else
	      use_this_startup_rule = ps_global->inc_startup_rule;

	    switch(use_this_startup_rule){
	      /*
	       * For news in incoming collection we're doing the same thing
	       * for first-unseen and first-recent. In both those cases you
	       * get first-unseen if FAKE_NEW is off and first-recent if
	       * FAKE_NEW is on. If FAKE_NEW is on, first unseen is the
	       * same as first recent because all recent msgs are unseen
	       * and all unrecent msgs are seen (see pine_mail_open).
	       */
	      case IS_FIRST_UNSEEN:
first_unseen:
		mn_set_cur(ps_global->msgmap,
			(sp_first_unseen(m)
			 && mn_get_sort(ps_global->msgmap) == SortArrival
			 && !mn_get_revsort(ps_global->msgmap)
			 && !get_lflag(ps_global->mail_stream, NULL,
				       sp_first_unseen(m), MN_EXLD)
			 && (n = mn_raw2m(ps_global->msgmap, 
					  sp_first_unseen(m))))
			   ? n
			   : first_sorted_flagged(F_UNSEEN | F_UNDEL, m, pc,
					      THREADING() ? 0 : FSF_SKIP_CHID));
		break;

	      case IS_FIRST_RECENT:
first_recent:
		/*
		 * We could really use recent for news but this is the way
		 * it has always worked, so we'll leave it. That is, if
		 * the FAKE_NEW feature is on, recent and unseen are
		 * equivalent, so it doesn't matter. If the feature isn't
		 * on, all the undeleted messages are unseen and we start
		 * at the first one. User controls with the FAKE_NEW feature.
		 */
		if(IS_NEWS(ps_global->mail_stream)){
		    mn_set_cur(ps_global->msgmap,
			       first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
					       THREADING() ? 0 : FSF_SKIP_CHID));
		}
		else{
		    mn_set_cur(ps_global->msgmap,
			       first_sorted_flagged(F_RECENT | F_UNSEEN
						    | F_UNDEL,
						    m, pc,
					      THREADING() ? 0 : FSF_SKIP_CHID));
		}
		break;

	      case IS_FIRST_IMPORTANT:
		mn_set_cur(ps_global->msgmap,
			   first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
					      THREADING() ? 0 : FSF_SKIP_CHID));
		break;

	      case IS_FIRST_IMPORTANT_OR_UNSEEN:

		if(IS_NEWS(ps_global->mail_stream))
		  goto first_unseen;

		{
		    MsgNo flagged, first_unseen;

		    flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
					       THREADING() ? 0 : FSF_SKIP_CHID);
		    first_unseen = (sp_first_unseen(m)
			     && mn_get_sort(ps_global->msgmap) == SortArrival
			     && !mn_get_revsort(ps_global->msgmap)
			     && !get_lflag(ps_global->mail_stream, NULL,
					   sp_first_unseen(m), MN_EXLD)
			     && (n = mn_raw2m(ps_global->msgmap, 
					      sp_first_unseen(m))))
				? n
				: first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
					       THREADING() ? 0 : FSF_SKIP_CHID);
		    mn_set_cur(ps_global->msgmap,
			  (MsgNo) MIN((int) flagged, (int) first_unseen));

		}

		break;

	      case IS_FIRST_IMPORTANT_OR_RECENT:

		if(IS_NEWS(ps_global->mail_stream))
		  goto first_recent;

		{
		    MsgNo flagged, first_recent;

		    flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
					       THREADING() ? 0 : FSF_SKIP_CHID);
		    first_recent = first_sorted_flagged(F_RECENT | F_UNSEEN
							| F_UNDEL,
							m, pc,
					       THREADING() ? 0 : FSF_SKIP_CHID);
		    mn_set_cur(ps_global->msgmap,
			      (MsgNo) MIN((int) flagged, (int) first_recent));
		}

		break;

	      case IS_FIRST:
		mn_set_cur(ps_global->msgmap,
			   first_sorted_flagged(F_UNDEL, m, pc,
					      THREADING() ? 0 : FSF_SKIP_CHID));
		break;

	      case IS_LAST:
		mn_set_cur(ps_global->msgmap,
			   first_sorted_flagged(F_UNDEL, m, pc,
			         FSF_LAST | (THREADING() ? 0 : FSF_SKIP_CHID)));
		break;

	      default:
		alpine_panic("Unexpected incoming startup case");
		break;

	    }
	}
	else if(IS_NEWS(ps_global->mail_stream)){
	    /*
	     * This will go to two different places depending on the FAKE_NEW
	     * feature (see pine_mail_open).
	     */
	    mn_set_cur(ps_global->msgmap,
		       first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
					      THREADING() ? 0 : FSF_SKIP_CHID));
	}
        else{
	    mn_set_cur(ps_global->msgmap,
		       mn_get_revsort(ps_global->msgmap)
		         ? 1L
			 : mn_get_total(ps_global->msgmap));
	}

	adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
    }
    else if(!(rflags & SP_MATCH)){
	mn_set_cur(ps_global->msgmap, -1L);
    }

    if(ps_global->mail_stream)
      sp_set_first_unseen(ps_global->mail_stream, 0L);

    return(1);
}


/*----------------------------------------------------------------------
      Expand a folder name, taking account of the folders_dir and 
      any home directory reference

  Args: filename -- The name of the file that is the folder
 
 Result: The folder name is expanded in place.  
         Returns 0 and queues status message if unsuccessful.
         Input string is overwritten with expanded name.
         Returns 1 if successful.

  ----*/
int
expand_foldername(char *filename, size_t len)
{
    char temp_filename[MAXPATH+1];

    dprint((5, "=== expand_foldername called (%s) ===\n",
	   filename ? filename : "?"));

    /*
     * We used to check for valid filename chars here if "filename"
     * didn't refer to a remote mailbox.  This has been rethought
     */

    strncpy(temp_filename, filename, sizeof(temp_filename)-1);
    temp_filename[sizeof(temp_filename)-1] = '\0';
    if(strucmp(temp_filename, "inbox") == 0) {
        strncpy(filename, ps_global->VAR_INBOX_PATH == NULL ? "inbox" :
                ps_global->VAR_INBOX_PATH, len-1);
	filename[len-1] = '\0';
    } else if(temp_filename[0] == '{') {
        strncpy(filename, temp_filename, len-1);
	filename[len-1] = '\0';
    } else if(ps_global->restricted && filename_is_restricted(temp_filename)){
	q_status_message(SM_ORDER, 0, 3, "Can only open local folders");
	return(0);
    } else if(temp_filename[0] == '*') {
        strncpy(filename, temp_filename, len-1);
	filename[len-1] = '\0';
    } else if(ps_global->VAR_OPER_DIR && filename_parent_ref(temp_filename)){
	q_status_message(SM_ORDER, 0, 3,
			 "\"..\" not allowed in folder name");
	return(0);
    } else if (is_homedir_path(temp_filename)){
        if(fnexpand(temp_filename, sizeof(temp_filename)) == NULL) {
    	    q_status_message1(SM_ORDER, 3, 3, "Error expanding folder \"%s\"", temp_filename);
    	    return(0);
        }
        strncpy(filename, temp_filename, len-1);
	filename[len-1] = '\0';
    } else if(F_ON(F_USE_CURRENT_DIR, ps_global) || is_absolute_path(temp_filename)){
        strncpy(filename, temp_filename, len-1);
	filename[len-1] = '\0';
    } else if(ps_global->VAR_OPER_DIR){
	build_path(filename, ps_global->VAR_OPER_DIR, temp_filename, len);
    } else {
	build_path(filename,
#ifdef	IS_WINDOWS
		   ps_global->folders_dir,
#else	/* UNIX */
		   ps_global->home_dir,
#endif	/* UNIX */
		   temp_filename, len);
    }

    dprint((5, "returning \"%s\"\n", filename ? filename : "?"));    
    return(1);
}



/*----------------------------------------------------------------------
      Expunge (if confirmed) and close a mail stream

    Args: stream   -- The MAILSTREAM * to close
        final_msg  -- If non-null, this should be set to point to a
		      message to print out in the caller, it is allocated
		      here and freed by the caller.

   Result:  Mail box is expunged and closed. A message is displayed to
             say what happened
 ----*/
void
expunge_and_close(MAILSTREAM *stream, char **final_msg, long unsigned int flags)
{
    long  i, delete_count, seen_not_del;
    char  buff1[MAX_SCREEN_COLS+1], *moved_msg = NULL,
	  buff2[MAX_SCREEN_COLS+1], *folder;
    CONTEXT_S *context;
    struct variable *vars = ps_global->vars;
    int ret, expunge = FALSE, no_close = 0;
    char ing[4];

    no_close = (flags & EC_NO_CLOSE);

    if(!(stream && sp_flagged(stream, SP_LOCKED)))
      stream = NULL;

    /* check for dead stream */
    if(stream && sp_dead_stream(stream)){
	pine_mail_actually_close(stream);
	stream = NULL;
    }

    if(stream != NULL){
	context = sp_context(stream);
	folder  = STREAMNAME(stream);

        dprint((2, "expunge_and_close: \"%s\"%s\n",
                   folder, no_close ? " (NO_CLOSE bit set)" : ""));

	update_folder_unseen_by_stream(stream, UFU_NONE);

	if(final_msg)
	  strncpy(ing, "ed", sizeof(ing));
	else
	  strncpy(ing, "ing", sizeof(ing));

	ing[sizeof(ing)-1] = '\0';

	buff1[0] = '\0';
	buff2[0] = '\0';

        if(!stream->rdonly){

	    if(pith_opt_begin_closing)
	      (*pith_opt_begin_closing)(flags, folder);

	    mail_expunge_prefilter(stream, MI_CLOSING);

	    /*
	     * Be sure to expunge any excluded (filtered) msgs
	     * Do it here so they're not copied into read/archived
	     * folders *AND* to be sure we don't refilter them
	     * next time the folder's opened.
	     */
	    for(i = 1L; i <= stream->nmsgs; i++)
	      if(get_lflag(stream, NULL, i, MN_EXLD)){	/* if there are any */
		  delete_filtered_msgs(stream);		/* delete them all */
		  expunge = TRUE;
		  break;
	      }

	    /* Save read messages? */
	    if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0]
	       && sp_flagged(stream, SP_INBOX)
	       && (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL))){

		if(F_ON(F_AUTO_READ_MSGS,ps_global)
		   || (pith_opt_read_msg_prompt
		       && (*pith_opt_read_msg_prompt)(seen_not_del, VAR_READ_MESSAGE_FOLDER)))
		  /* move inbox's read messages */
		  moved_msg = move_read_msgs(stream, VAR_READ_MESSAGE_FOLDER,
					     buff1, sizeof(buff1), -1L);
	    }
	    else if(VAR_ARCHIVED_FOLDERS)
	      moved_msg = move_read_incoming(stream, context, folder,
					     VAR_ARCHIVED_FOLDERS,
					     buff1, sizeof(buff1));

	    /*
	     * We need the count_flagged to be executed not only to set
	     * delete_count, but also to set the searched bits in all of
	     * the deleted messages. The searched bit is used in the monkey
	     * business section below which undeletes deleted messages
	     * before expunging. It determines which messages are deleted
	     * by examining the searched bit, which had better be set or not
	     * based on this count_flagged call rather than some random
	     * search that happened earlier.
	     */
            delete_count = count_flagged(stream, F_DEL);
	    if(F_ON(F_EXPUNGE_MANUALLY,ps_global))
              delete_count = 0L;

	    ret = 'n';
	    if(!ps_global->noexpunge_on_close && delete_count){

		if(F_ON(F_FULL_AUTO_EXPUNGE,ps_global)
		   || (F_ON(F_AUTO_EXPUNGE, ps_global)
		       && ((!strucmp(folder,ps_global->inbox_name))
			   || (context && (context->use & CNTXT_INCMNG)))
		       && context_isambig(folder))){
		    ret = 'y';
		}
		else if(pith_opt_expunge_prompt){
		  unsigned char *fname = folder_name_decoded((unsigned char *)folder);
		  ret = (*pith_opt_expunge_prompt)(stream, pretty_fn((char *)fname), delete_count);
		  if(fname) fs_give((void **) &fname);
		}

		/* get this message back in queue */
		if(moved_msg)
		  q_status_message(SM_ORDER,
		      F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);

		if(ret == 'y'){
		    long filtered;
		    unsigned char *fname = folder_name_decoded((unsigned char *)folder);

		    filtered = any_lflagged(sp_msgmap(stream), MN_EXLD);

		    snprintf(buff2, sizeof(buff2),
		      "%s%s%s%.30s%s%s %s message%s and remov%s %s.",
			no_close ? "" : "Clos",
			no_close ? "" : ing,
			no_close ? "" : " \"",
	 		no_close ? "" : pretty_fn((char *)fname),
			no_close ? "" : "\". ",
			final_msg ? "Kept" : "Keeping",
			comatose(stream->nmsgs - filtered - delete_count),
			plural(stream->nmsgs - filtered - delete_count),
			ing,
			long2string(delete_count));
		    if(fname) fs_give((void **)&fname);
		    if(final_msg)
		      *final_msg = cpystr(buff2);
		    else
		      q_status_message(SM_ORDER,
				       no_close ? 1 :
				        (F_ON(F_AUTO_EXPUNGE,ps_global)
					 || F_ON(F_FULL_AUTO_EXPUNGE,ps_global))
					         ? 0 : 3,
				       5, buff2);
		      
		    flush_status_messages(1);
		    ps_global->mm_log_error = 0;
		    ps_global->expunge_in_progress = 1;
		    mail_expunge(stream);
		    ps_global->expunge_in_progress = 0;
		    if(ps_global->mm_log_error && final_msg && *final_msg){
			fs_give((void **)final_msg);
			*final_msg = NULL;
		    }
		}
	    }

	    if(ret != 'y'){
		if(!ps_global->noexpunge_on_close && expunge){
		    MESSAGECACHE *mc;
		    char	 *seq;
		    int		  expbits;

		    /*
		     * filtered message monkey business.
		     * The Plan:
		     *   1) light sequence bits for legit deleted msgs
		     *      and store marker in local extension
		     *   2) clear their deleted flag
		     *   3) perform expunge to removed filtered msgs
		     *   4) restore deleted flags for legit msgs
		     *      based on local extension bit
		     */
		    for(i = 1L; i <= stream->nmsgs; i++)
		      if(!get_lflag(stream, NULL, i, MN_EXLD)
			 && (((mc = mail_elt(stream, i)) && mc->valid && mc->deleted)
			     || (mc && !mc->valid && mc->searched))){
			  mc->sequence = 1;
			  expbits      = MSG_EX_DELETE;
			  msgno_exceptions(stream, i, "0", &expbits, TRUE);
		      }
		      else if((mc = mail_elt(stream, i)) != NULL)
			mc->sequence = 0;

		    if((seq = build_sequence(stream, NULL, NULL)) != NULL){
			mail_flag(stream, seq, "\\DELETED", ST_SILENT);
			fs_give((void **) &seq);
		    }

		    ps_global->mm_log_error = 0;
		    ps_global->expunge_in_progress = 1;
		    mail_expunge(stream);
		    ps_global->expunge_in_progress = 0;

		    for(i = 1L; i <= stream->nmsgs; i++)
		      if((mc = mail_elt(stream, i)) != NULL)
		        mc->sequence 
			   = (msgno_exceptions(stream, i, "0", &expbits, FALSE)
			      && (expbits & MSG_EX_DELETE));

		    if((seq = build_sequence(stream, NULL, NULL)) != NULL){
			mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
			fs_give((void **) &seq);
		    }
		}

		if(!no_close){			/* MAX_SCREEN_COLS+1 = sizeof(buff2) */
		    unsigned char *fname = folder_name_decoded((unsigned char *)folder);
		    if(stream->nmsgs){
			snprintf(buff2, sizeof(buff2),
			    "Clos%s folder \"%.*s\". %s%s%s message%s.",
			    ing,
			    MAX_SCREEN_COLS+1-50, pretty_fn((char *) fname), 
			    final_msg ? "Kept" : "Keeping",
			    (stream->nmsgs == 1L) ? " single" : " all ",
			    (stream->nmsgs > 1L)
			      ? comatose(stream->nmsgs) : "",
			    plural(stream->nmsgs));
		    }
		    else{			/* MAX_SCREEN_COLS+1 = sizeof(buff2) */
			snprintf(buff2, sizeof(buff2), "Clos%s empty folder \"%.*s\"",
			    ing, MAX_SCREEN_COLS+1-50, pretty_fn((char *) fname));
		    }
		    if(fname) fs_give((void **)&fname);

		    if(final_msg)
		      *final_msg = cpystr(buff2);
		    else
		      q_status_message(SM_ORDER, 0, 3, buff2);
		}
	    }
        }
	else{
            if(IS_NEWS(stream)){
		/*
		 * Mark the filtered messages deleted so they aren't
		 * filtered next time.
		 */
	        for(i = 1L; i <= stream->nmsgs; i++){
		  int exbits;
		  if(msgno_exceptions(stream, i, "0" , &exbits, FALSE)
		     && (exbits & MSG_EX_FILTERED)){
		    delete_filtered_msgs(stream);
		    break;
		  }
		}
		/* first, look to archive read messages */
		if((moved_msg = move_read_incoming(stream, context, folder,
						  VAR_ARCHIVED_FOLDERS,
						  buff1, sizeof(buff1))) != NULL)
		  q_status_message(SM_ORDER,
		      F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);
						/* MAX_SCREEN_COLS+1 = sizeof(buff2) */
		snprintf(buff2, sizeof(buff2), "Clos%s news group \"%.*s\"",
			ing, MAX_SCREEN_COLS+1-50, pretty_fn(folder));

		if(F_ON(F_NEWS_CATCHUP, ps_global)){
		    MESSAGECACHE *mc;

		    /* count visible messages */
		    (void) count_flagged(stream, F_DEL);
		    for(i = 1L, delete_count = 0L; i <= stream->nmsgs; i++)
		      if(!(get_lflag(stream, NULL, i, MN_EXLD)
			   || ((mc = mail_elt(stream, i)) && mc->valid
				&& mc->deleted)
			   || (mc && !mc->valid && mc->searched)))
			delete_count++;

		    if(delete_count && pith_opt_expunge_prompt){
			unsigned char *fname = folder_name_decoded((unsigned char *)folder);
			ret = (*pith_opt_expunge_prompt)(stream, pretty_fn((char *) fname), delete_count);
			if(fname) fs_give((void **)&fname);
			if(ret == 'y'){
			    char seq[64];

			    snprintf(seq, sizeof(seq), "1:%ld", stream->nmsgs);
			    mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
			}
		    }
		}

		if(F_ON(F_NEWS_CROSS_DELETE, ps_global))
		  cross_delete_crossposts(stream);
	    }
            else{				/* MAX_SCREEN_COLS+1 = sizeof(buff2) */
	      unsigned char *fname = folder_name_decoded((unsigned char *)folder);
	      snprintf(buff2, sizeof(buff2),
			"Clos%s read-only folder \"%.*s\". No changes to save",
			ing, MAX_SCREEN_COLS+1-60, pretty_fn((char *) fname));
	      if(fname) fs_give((void **)&fname);
	    }

	    if(final_msg)
	      *final_msg = cpystr(buff2);
	    else
	      q_status_message(SM_ORDER, 0, 2, buff2);
        }

	/*
	 * Make darn sure any mm_log fallout caused above gets seen...
	 */
	if(!no_close){
	    flush_status_messages(1);
	    pine_mail_close(stream);
	}
    }
}


void
agg_select_all(MAILSTREAM *stream, MSGNO_S *msgmap, long int *diff, int on)
{ 
    long i;
    int  hidden = any_lflagged(msgmap, MN_HIDE) > 0L;

    for(i = 1L; i <= mn_get_total(msgmap); i++){
	if(on){		/* mark 'em all */
	  set_lflag(stream, msgmap, i, MN_SLCT, 1);
	}
	else {		/* unmark 'em all */
	    if(get_lflag(stream, msgmap, i, MN_SLCT)){
		if(diff)
		  (*diff)++;

		set_lflag(stream, msgmap, i, MN_SLCT, 0);
	    }
	    else if(hidden)
	      set_lflag(stream, msgmap, i, MN_HIDE, 0);
	}
    }
}


/*----------------------------------------------------------------------
  Move all read messages from srcfldr to dstfldr
 
  Args: stream -- stream to usr
	dstfldr -- folder to receive moved messages
	buf -- place to write success message

  Returns: success message or NULL for failure
  ----*/
char *
move_read_msgs(MAILSTREAM *stream, char *dstfldr, char *buf, size_t buflen, long int searched)
{
    long	  i, raw;
    int           we_cancel = 0;
    MSGNO_S	 *msgmap = NULL;
    CONTEXT_S	 *save_context = NULL;
    char	  *bufp = NULL;
    MESSAGECACHE *mc;

    if(!is_absolute_path(dstfldr)
       && !(save_context = default_save_context(ps_global->context_list)))
      save_context = ps_global->context_list;

    /*
     * Use the "searched" bit to select the set of messages
     * we want to save.  If searched is non-neg, the message
     * cache already has the necessary "searched" bits set.
     */
    if(searched < 0L)
      searched = count_flagged(stream, F_SEEN | F_UNDEL);

    if(searched){
	/*
	 * We're going to be messing with SLCT flags in order
	 * to do our work. If this stream is a StayOpen stream
	 * we want to restore those flags after we're done
	 * using them. So copy them into STMP so we can put them
	 * back below.
	 */
	msgmap = sp_msgmap(stream);
	if(sp_flagged(stream, SP_PERMLOCKED))
	  copy_lflags(stream, msgmap, MN_SLCT, MN_STMP);

	set_lflags(stream, msgmap, MN_SLCT, 0);

	/* select search results */
	for(i = 1L; i <= mn_get_total(msgmap); i++)
	  if((raw = mn_m2raw(msgmap, i)) > 0L && stream
	     && raw <= stream->nmsgs
	     && (mc = mail_elt(stream,raw))
	     && ((mc->valid && mc->seen && !mc->deleted)
	         || (!mc->valid && mc->searched)))
	    set_lflag(stream, msgmap, i, MN_SLCT, 1);

	pseudo_selected(stream, msgmap);
	snprintf(buf, buflen, "Moving %s read message%s to \"%s\"",
		comatose(searched), plural(searched), dstfldr);
	we_cancel = busy_cue(buf, NULL, 0);
	if(save(ps_global, stream, save_context, dstfldr, msgmap,
		SV_DELETE | SV_FIX_DELS | SV_INBOXWOCNTXT) == searched)
	  strncpy(bufp = buf + 1, "Moved", MIN(5,buflen)); /* change Moving to Moved */

	buf[buflen-1] = '\0';
	if(we_cancel)
	  cancel_busy_cue(bufp ? 0 : -1);

	if(sp_flagged(stream, SP_PERMLOCKED)){
	    restore_selected(msgmap);
	    copy_lflags(stream, msgmap, MN_STMP, MN_SLCT);
	}
    }

    return(bufp);
}


/*----------------------------------------------------------------------
  Move read messages from folder if listed in archive
 
  Args: 

  ----*/
char *
move_read_incoming(MAILSTREAM *stream, CONTEXT_S *context, char *folder,
		   char **archive, char *buf, size_t buflen)
{
    char *s, *d, *f = folder;
    long  seen_undel;

    if(buf && buflen > 0)
      buf[0] = '\0';

    if(archive && !sp_flagged(stream, SP_INBOX)
       && context && (context->use & CNTXT_INCMNG)
       && ((context_isambig(folder)
	    && folder_is_nick(folder, FOLDERS(context), 0))
	   || folder_index(folder, context, FI_FOLDER) > 0)
       && (seen_undel = count_flagged(stream, F_SEEN | F_UNDEL))){

	for(; f && *archive; archive++){
	    char *p;

	    get_pair(*archive, &s, &d, 1, 0);
	    if(s && d
	       && (!strcmp(s, folder)
		   || (context_isambig(folder)
		       && (p = folder_is_nick(folder, FOLDERS(context), 0))
		       && !strcmp(s, p)))){
		if(F_ON(F_AUTO_READ_MSGS,ps_global)
		   || (pith_opt_read_msg_prompt
		       && (*pith_opt_read_msg_prompt)(seen_undel, d)))
		  buf = move_read_msgs(stream, d, buf, buflen, seen_undel);

		f = NULL;		/* bust out after cleaning up */
	    }

	    fs_give((void **)&s);
	    fs_give((void **)&d);
	}
    }

    return((buf && *buf) ? buf : NULL);
}


/*----------------------------------------------------------------------
    Delete all references to a deleted news posting

 
  ---*/
void
cross_delete_crossposts(MAILSTREAM *stream)
{
    if(count_flagged(stream, F_DEL)){
	static char *fields[] = {"Xref", NULL};
	MAILSTREAM  *tstream;
	CONTEXT_S   *fake_context;
	char	    *xref, *p, *group, *uidp,
		    *newgrp, newfolder[MAILTMPLEN];
	long	     i, hostlatch = 0L;
	imapuid_t    uid;
	int	     we_cancel = 0;
	MESSAGECACHE *mc;

	strncpy(newfolder, stream->mailbox, sizeof(newfolder));
	newfolder[sizeof(newfolder)-1] = '\0';
	if(!(newgrp = strstr(newfolder, "#news.")))
	  return;				/* weird mailbox */

	newgrp += 6;

	we_cancel = busy_cue("Busy deleting crosspostings", NULL, 1);

	/* build subscribed list */
	strncpy(newgrp, "[]", sizeof(newfolder)-(newgrp-newfolder));
	newfolder[sizeof(newfolder)-1] = '\0';
	fake_context = new_context(newfolder, 0);
	build_folder_list(NULL, fake_context, "*", NULL, BFL_LSUB);

	for(i = 1L; i <= stream->nmsgs; i++)
	  if(!get_lflag(stream, NULL, i, MN_EXLD)
	     && (mc = mail_elt(stream, i)) && mc->deleted){

	      if((xref = pine_fetchheader_lines(stream, i, NULL, fields)) != NULL){
		  if((p = strstr(xref, ": ")) != NULL){
		      p	     += 2;
		      hostlatch = 0L;
		      while(*p){
			  group = p;
			  uidp  = NULL;

			  /* get server */
			  while(*++p && !isspace((unsigned char) *p))
			    if(*p == ':'){
				*p   = '\0';
				uidp = p + 1;
			    }

			  /* tie off uid/host */
			  if(*p)
			    *p++ = '\0';

			  if(uidp){
			      /*
			       * For the nonce, we're only deleting valid
			       * uid's from outside the current newsgroup
			       * and inside only subscribed newsgroups
			       */
			      if(strcmp(group, stream->mailbox
							+ (newgrp - newfolder))
				 && folder_index(group, fake_context,
						 FI_FOLDER) >= 0){
				  if((uid = strtoul(uidp, NULL, 10)) != 0L){
				      strncpy(newgrp, group, sizeof(newfolder)-(newgrp-newfolder));
				      newfolder[sizeof(newfolder)-1] = '\0';
				      if((tstream = pine_mail_open(NULL,
								  newfolder,
								  SP_USEPOOL,
								  NULL)) != NULL){
					  mail_flag(tstream, ulong2string(uid),
						    "\\DELETED",
						    ST_SET | ST_UID);
					  pine_mail_close(tstream);
				      }
				  }
				  else
				    break;		/* bogus uid */
			      }
			  }
			  else if(!hostlatch++){
			      char *p, *q;

			      if(stream->mailbox[0] == '{'
				 && !((p = strpbrk(stream->mailbox+1, "}:/"))
				      && !struncmp(stream->mailbox + 1,
						   q = canonical_name(group),
						   p - (stream->mailbox + 1))
				      && q[p - (stream->mailbox + 1)] == '\0'))
				break;		/* different server? */
			  }
			  else
			    break;		/* bogus field! */
		      }
		  }
		  
		  fs_give((void **) &xref);
	      }
	  }

	free_context(&fake_context);

	if(we_cancel)
	  cancel_busy_cue(0);
    }
}


/*
 * Original version from Eduardo Chappa.
 *
 * Returns a string describing the number of new/unseen messages
 * for use in the status line. Can return NULL. Caller must free the memory.
 */
char *
new_messages_string(MAILSTREAM *stream)
{
    char message[80] = {'\0'};
    long new = 0L, uns = 0L;
    int i, imapstatus = 0;

    for (i = 0; ps_global->index_disp_format[i].ctype != iNothing
		&& ps_global->index_disp_format[i].ctype != iIStatus
		&& ps_global->index_disp_format[i].ctype != iSIStatus; i++)
      ;

    imapstatus = ps_global->index_disp_format[i].ctype == iIStatus
		 || ps_global->index_disp_format[i].ctype == iSIStatus;

    get_new_message_count(stream, imapstatus, &new, &uns);

    if(imapstatus)
      snprintf(message, sizeof(message), " - %s%s%s%s%s%s%s",
	       uns != 0L ? comatose((long) new) : "",
	       uns != 0L ? " " : "",
	       uns != 0L ? _("recent") : "",
	       uns > 0L  ? ", " : "",
	       uns != -1L ? comatose((long) uns) : "",
	       uns != -1L ? " " : "",
	       uns != -1L ? _("unseen") : "");
    else if(!imapstatus && new > 0L)
      snprintf(message, sizeof(message), " - %s %s",
	       comatose((long) new), _("new"));

    return(*message ? cpystr(message) : NULL);
}


void
get_new_message_count(MAILSTREAM *stream, int imapstatus,
		      long *new, long *unseen)
{
    if(new)
      *new = 0L;

    if(unseen)
      *unseen = 0L;

    if(imapstatus){
	if(new)
	  *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);

	if(!IS_NEWS(stream)){
	    if(unseen)
	      *unseen = count_flagged(stream, F_UNSEEN | F_UNDEL);
	}
	else if(unseen)
	  *unseen = -1L;
    }
    else{
	if(IS_NEWS(stream)){
	    if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
		if(new)
		  *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);
	    }
	}
	else{
	    if(new)
	      *new = count_flagged(stream, F_UNSEEN | F_UNDEL | F_UNANS);
	}
    }
}


/*----------------------------------------------------------------------
  ZOOM the message index (set any and all necessary hidden flag bits)

   Args: state -- usual pine state
	 msgmap -- usual message mapping
   Returns: number of messages zoomed in on

  ----*/
long
zoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int onflag)
{
    long        i, count = 0L, first = 0L, msgno;
    PINETHRD_S *thrd = NULL, *topthrd = NULL, *nthrd;

    if(any_lflagged(msgmap, onflag)){

	if(THREADING() && sp_viewing_a_thread(stream)){
	    /* get top of current thread */
	    thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
	    if(thrd && thrd->top)
	      topthrd = fetch_thread(stream, thrd->top);
	}

	for(i = 1L; i <= mn_get_total(msgmap); i++){
	    if(!get_lflag(stream, msgmap, i, onflag)){
		set_lflag(stream, msgmap, i, MN_HIDE, 1);
	    }
	    else{

		/*
		 * Because subject lines depend on whether or not
		 * other parts of the thread above us are visible or not.
		 */
		if(THREADING() && !THRD_INDX()
		   && ps_global->thread_disp_style == THREAD_MUTTLIKE)
		  clear_index_cache_ent(stream, i, 0);

		/*
		 * If a selected message is hidden beneath a collapsed
		 * thread (not beneath a thread index line, but a collapsed
		 * thread or subthread) then we make it visible. The user
		 * should be able to see the selected messages when they
		 * Zoom. We could get a bit fancier and re-collapse the
		 * thread when the user unzooms, but we don't do that
		 * for now.
		 */
		if(THREADING() && !THRD_INDX()
		   && get_lflag(stream, msgmap, i, MN_CHID)){

		    /*
		     * What we need to do is to unhide this message and
		     * uncollapse any parent above us.
		     * Also, when we uncollapse a parent, we need to
		     * trace back down the tree and unhide until we get
		     * to a collapse point or the end. That's what
		     * set_thread_subtree does.
		     */

		    thrd = fetch_thread(stream, mn_m2raw(msgmap, i));

		    if(thrd && thrd->parent)
		      thrd = fetch_thread(stream, thrd->parent);
		    else
		      thrd = NULL;

		    /* unhide and uncollapse its parents */
		    while(thrd){
			/* if this parent is collapsed */
			if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
			    /* uncollapse this parent and unhide its subtree */
			    msgno = mn_raw2m(msgmap, thrd->rawno);
			    if(msgno > 0L && msgno <= mn_get_total(msgmap)){
				set_lflag(stream, msgmap, msgno,
					  MN_COLL | MN_CHID, 0);
				if(thrd->next &&
				   (nthrd = fetch_thread(stream, thrd->next)))
				  set_thread_subtree(stream, nthrd, msgmap,
						     0, MN_CHID);
			    }

			    /* collapse symbol will be wrong */
			    clear_index_cache_ent(stream, msgno, 0);
			}

			/*
			 * Continue up tree to next parent looking for
			 * more collapse points.
			 */
			if(thrd->parent)
			  thrd = fetch_thread(stream, thrd->parent);
			else
			  thrd = NULL;
		    }
		}

		count++;
		if(!first){
		    if(THRD_INDX()){
			/* find msgno of top of thread for msg i */
			if((thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
			    && thrd->top)
			  first = mn_raw2m(msgmap, thrd->top);
		    }
		    else if(THREADING() && sp_viewing_a_thread(stream)){
			/* want first selected message in this thread */
			if(topthrd
			   && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
			   && thrd->top
			   && topthrd->rawno == thrd->top)
			  first = i;
		    }
		    else
		      first = i;
		}
	    }
	}

	if(THRD_INDX()){
	    thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
	    if(count_lflags_in_thread(stream, thrd, msgmap, onflag) == 0)
	      mn_set_cur(msgmap, first);
	}
	else if((THREADING() && sp_viewing_a_thread(stream))
	        || !get_lflag(stream, msgmap, mn_get_cur(msgmap), onflag)){
	    if(!first){
		int flags = 0;

		/*
		 * Nothing was selected in the thread we were in, so
		 * drop back to the Thread Index instead. Set the current
		 * thread to the first one that has a selection in it.
		 */

		unview_thread(state, stream, msgmap);

		i = next_sorted_flagged(F_UNDEL, stream, 1L, &flags);
		
		if(flags & NSF_FLAG_MATCH
		   && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
		    && thrd->top)
		  first = mn_raw2m(msgmap, thrd->top);
		else
		  first = 1L;	/* can't happen */

		mn_set_cur(msgmap, first);
	    }
	    else{
		if(msgline_hidden(stream, msgmap, mn_get_cur(msgmap), 0))
		  mn_set_cur(msgmap, first);
	    }
	}
    }

    return(count);
}



/*----------------------------------------------------------------------
  UnZOOM the message index (clear any and all hidden flag bits)

   Args: state -- usual pine state
	 msgmap -- usual message mapping
   Returns: 1 if hidden bits to clear and they were, 0 if none to clear

  ----*/
int
unzoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
{
    register long i;

    if(!any_lflagged(msgmap, MN_HIDE))
      return(0);

    for(i = 1L; i <= mn_get_total(msgmap); i++)
      set_lflag(stream, msgmap, i, MN_HIDE, 0);

    return(1);
}


int
agg_text_select(MAILSTREAM *stream, MSGNO_S *msgmap, char type, char *namehdr,
		int not, int check_for_my_addresses,
		char *sstring, char *charset, SEARCHSET **limitsrch)
{
    int		 old_imap, we_cancel;
    int          me_with_regex = 0;
    long         searchflags;
    SEARCHPGM   *srchpgm, *pgm, *secondpgm = NULL, *thirdpgm = NULL;
    SEARCHPGM   *mepgm = NULL;

    if(!stream)
      return(1);

    old_imap = (is_imap_stream(stream) && !modern_imap_stream(stream));

    /*
     * Special case code for matching one of the user's addresses.
     */
    if(check_for_my_addresses){
	char **t, *alt;

	if(F_OFF(F_DISABLE_REGEX, ps_global)){
	    for(t = ps_global->VAR_ALT_ADDRS; !me_with_regex && t && t[0] && t[0][0]; t++){
		alt = (*t);
		if(contains_regex_special_chars(alt))
		  me_with_regex++;
	    }
	}

	/*
	 * In this case we can't use search because it doesn't support
	 * regex. So we have to manually do the whole thing ourselves.
	 * The searching is done in the subroutine and the searched bits
	 * will be set on return.
	 */
	if(me_with_regex){
	    search_for_our_regex_addresses(stream, type, not, limitsrch ? *limitsrch : NULL);
	    return(0);
	}
	else{
	    PATGRP_S *patgrp = NULL;
	    PATTERN_S *p = NULL;
	    PATTERN_S *pattern = NULL, **nextp;
	    char buf[1000];

	    /*
	     * We're going to use the pattern matching machinery to generate
	     * a search program. We build a pattern whose only purpose is
	     * to generate the program.
	     */
	    nextp = &pattern;

	    /* add standard me addresses to list */
	    if(ps_global->VAR_USER_ID){
		if(ps_global->userdomain && ps_global->userdomain[0]){
		    p = (PATTERN_S *) fs_get(sizeof(*p));
		    memset((void *) p, 0, sizeof(*p));
		    snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
			     ps_global->userdomain);
		    p->substring = cpystr(buf);
		    *nextp = p;
		    nextp = &p->next;
		}

		if(!ps_global->userdomain && ps_global->localdomain && ps_global->localdomain[0]){
		    p = (PATTERN_S *) fs_get(sizeof(*p));
		    memset((void *) p, 0, sizeof(*p));
		    snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
			     ps_global->localdomain);
		    p->substring = cpystr(buf);
		    *nextp = p;
		    nextp = &p->next;
		}

		if(!ps_global->userdomain && ps_global->hostname && ps_global->hostname[0]){
		    p = (PATTERN_S *) fs_get(sizeof(*p));
		    memset((void *) p, 0, sizeof(*p));
		    snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
			     ps_global->hostname);
		    p->substring = cpystr(buf);
		    *nextp = p;
		    nextp = &p->next;
		}
	    }

	    /* add user's alternate addresses */
	    for(t = ps_global->VAR_ALT_ADDRS; t && t[0] && t[0][0]; t++){
		alt = (*t);
		if(alt && alt[0]){
		    p = (PATTERN_S *) fs_get(sizeof(*p));
		    memset((void *) p, 0, sizeof(*p));
		    p->substring = cpystr(alt);
		    *nextp = p;
		    nextp = &p->next;
		}
	    }

	    patgrp = (PATGRP_S *) fs_get(sizeof(*patgrp));
	    memset((void *) patgrp, 0, sizeof(*patgrp));

	    switch(type){
	      case 'r' :
		patgrp->recip = pattern;
		break;
	      case 'p' :
		patgrp->partic = pattern;
		break;
	      case 'f' :
		patgrp->from = pattern;
		break;
	      case 'c' :
		patgrp->cc = pattern;
		break;
	      case 't' :
		patgrp->to = pattern;
	        break;
	      default :
		q_status_message(SM_ORDER, 3, 3, "Unhandled case in agg_text_select");
	        break;
	    }

	    mepgm = match_pattern_srchpgm(patgrp, stream, NULL);

	    free_patgrp(&patgrp);
	}
    }

    if(mepgm){
	if(not && !old_imap){
	    srchpgm = mail_newsearchpgm();
	    srchpgm->not = mail_newsearchpgmlist();
	    srchpgm->not->pgm = mepgm;
	}
	else{
	    srchpgm = mepgm;
	}

    }
    else{
	/* create a search program and fill it in */
	srchpgm = pgm = mail_newsearchpgm();
	if(not && !old_imap){
	    srchpgm->not = mail_newsearchpgmlist();
	    srchpgm->not->pgm = mail_newsearchpgm();
	    pgm = srchpgm->not->pgm;
	}
    }

  if(!mepgm){
    switch(type){
      case 'g' :				/* X-GM-EXT-1 */
	pgm->x_gm_ext1 = mail_newstringlist();
	pgm->x_gm_ext1->text.data = (unsigned char *) cpystr(namehdr);
	pgm->x_gm_ext1->text.size = strlen(namehdr);
      break;

      case 'h' :	                        /* Any header */
        pgm->header = mail_newsearchheader (namehdr, sstring);
      break;

      case 'r' :				/* TO or CC */
	if(old_imap){
	    /* No OR on old servers */
	    pgm->to = mail_newstringlist();
	    pgm->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->to->text.size = strlen(sstring);
	    secondpgm = mail_newsearchpgm();
	    secondpgm->cc = mail_newstringlist();
	    secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
	    secondpgm->cc->text.size = strlen(sstring);
	}
	else{
	    pgm->or = mail_newsearchor();
	    pgm->or->first->to = mail_newstringlist();
	    pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->or->first->to->text.size = strlen(sstring);
	    pgm->or->second->cc = mail_newstringlist();
	    pgm->or->second->cc->text.data = (unsigned char *) cpystr(sstring);
	    pgm->or->second->cc->text.size = strlen(sstring);
	}

	break;

      case 'p' :				/* TO or CC or FROM */
	if(old_imap){
	    /* No OR on old servers */
	    pgm->to = mail_newstringlist();
	    pgm->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->to->text.size = strlen(sstring);
	    secondpgm = mail_newsearchpgm();
	    secondpgm->cc = mail_newstringlist();
	    secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
	    secondpgm->cc->text.size = strlen(sstring);
	    thirdpgm = mail_newsearchpgm();
	    thirdpgm->from = mail_newstringlist();
	    thirdpgm->from->text.data = (unsigned char *) cpystr(sstring);
	    thirdpgm->from->text.size = strlen(sstring);
	}
	else{
	    pgm->or = mail_newsearchor();
	    pgm->or->first->to = mail_newstringlist();
	    pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
	    pgm->or->first->to->text.size = strlen(sstring);

	    pgm->or->second->or = mail_newsearchor();
	    pgm->or->second->or->first->cc = mail_newstringlist();
	    pgm->or->second->or->first->cc->text.data =
					    (unsigned char *) cpystr(sstring);
	    pgm->or->second->or->first->cc->text.size = strlen(sstring);
	    pgm->or->second->or->second->from = mail_newstringlist();
	    pgm->or->second->or->second->from->text.data =
					    (unsigned char *) cpystr(sstring);
	    pgm->or->second->or->second->from->text.size = strlen(sstring);
	}

	break;

      case 'f' :				/* FROM */
	pgm->from = mail_newstringlist();
	pgm->from->text.data = (unsigned char *) cpystr(sstring);
	pgm->from->text.size = strlen(sstring);
	break;

      case 'c' :				/* CC */
	pgm->cc = mail_newstringlist();
	pgm->cc->text.data = (unsigned char *) cpystr(sstring);
	pgm->cc->text.size = strlen(sstring);
	break;

      case 't' :				/* TO */
	pgm->to = mail_newstringlist();
	pgm->to->text.data = (unsigned char *) cpystr(sstring);
	pgm->to->text.size = strlen(sstring);
	break;

      case 's' :				/* SUBJECT */
	pgm->subject = mail_newstringlist();
	pgm->subject->text.data = (unsigned char *) cpystr(sstring);
	pgm->subject->text.size = strlen(sstring);
	break;

      case 'a' :				/* ALL TEXT */
	pgm->text = mail_newstringlist();
	pgm->text->text.data = (unsigned char *) cpystr(sstring);
	pgm->text->text.size = strlen(sstring);
	break;

      case 'b' :				/* ALL BODY TEXT */
	pgm->body = mail_newstringlist();
	pgm->body->text.data = (unsigned char *) cpystr(sstring);
	pgm->body->text.size = strlen(sstring);
	break;

      default :
	dprint((1,"\n - BOTCH: select_text unrecognized type\n"));
	return(1);
    }
  }
    /*
     * If we happen to have any messages excluded, make sure we
     * don't waste time searching their text...
     */
    srchpgm->msgno = (limitsrch ? *limitsrch : NULL);

    /* TRANSLATORS: warning to user that we're busy selecting messages */
    we_cancel = busy_cue(_("Busy Selecting"), NULL, 1);

    searchflags = SE_NOPREFETCH | (secondpgm ? 0 : SE_FREE);

    pine_mail_search_full(stream, !old_imap ? charset : NULL, srchpgm,
			  searchflags);
    
    /* search for To or Cc; or To or Cc or From on old imap server */
    if(secondpgm){
	if(srchpgm){
	    srchpgm->msgno = NULL;
	    mail_free_searchpgm(&srchpgm);
	}

	secondpgm->msgno = (limitsrch ? *limitsrch : NULL);
	searchflags |= (SE_RETAIN | (thirdpgm ? 0 : SE_FREE));

	pine_mail_search_full(stream, NULL, secondpgm, searchflags);

	if(thirdpgm){
	    if(secondpgm){
		secondpgm->msgno = NULL;
		mail_free_searchpgm(&secondpgm);
	    }

	    thirdpgm->msgno = (limitsrch ? *limitsrch : NULL);
	    searchflags |= SE_FREE;
	    pine_mail_search_full(stream, NULL, thirdpgm, searchflags);
	}
    }

    /* we know this was freed in mail_search, let caller know */
    if(limitsrch)
      *limitsrch = NULL;

    if(old_imap && not){
	MESSAGECACHE *mc;
	long	      msgno;

	/* 
	 * Old imap server doesn't have a NOT, so we actually searched for
	 * the subject (or whatever) instead of !subject. Flip the searched
	 * bits.
	 */
	for(msgno = 1L; msgno <= mn_get_total(msgmap); msgno++)
	    if(stream && msgno <= stream->nmsgs){
	       if((mc = mail_elt(stream, msgno)) != NULL)
		  mc->searched = mc->searched ? NIL : T;
	    }
    }

    if(we_cancel)
      cancel_busy_cue(0);

    return(0);
}


void
search_for_our_regex_addresses(MAILSTREAM *stream, char type, int not,
			       SEARCHSET *searchset)
{
    long rawno, count = 0L;
    MESSAGECACHE *mc;
    ADDRESS *addr1 = NULL, *addr2 = NULL, *addr3 = NULL;
    ENVELOPE *env;
    SEARCHSET *s, *ss = NULL;
    extern MAILSTREAM *mm_search_stream;
    extern long        mm_search_count;

    mm_search_count = 0L;
    mm_search_stream = stream;

    if(!stream)
      return;

    /* set searched bits to zero */
    for(rawno = 1L; rawno <= stream->nmsgs; rawno++)
      if((mc=mail_elt(stream, rawno)) != NULL)
	mc->searched = NIL;

    /* set sequence bits for envelopes we need */
    for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
	if((mc = mail_elt(stream, rawno)) != NULL){
	    if((!searchset || in_searchset(searchset, (unsigned long) rawno))
	       && !mc->private.msg.env){
		mc->sequence = 1;
		count++;
	    }
	    else
	      mc->sequence = 0;
	}
    }

    /*
     * Set up a searchset that will control the fetch ahead.
     */
    if(count){
	ss = build_searchset(stream);
	if(ss){
	    SEARCHSET **sset = NULL;

	    mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);

	    /* this resets automatically after the first fetch */
	    sset = (SEARCHSET **) mail_parameters(stream,
						  GET_FETCHLOOKAHEAD,
						  (void *) stream);
	    if(sset)
	      *sset = ss;
	}
    }

    for(s = searchset; s; s = s->next){
      for(rawno = s->first; rawno <= s->last; rawno++){
	env = pine_mail_fetchenvelope(stream, rawno);
	addr1 = addr2 = addr3 = NULL;
	switch(type){
	  case 'r' :
	    addr1 = env ? env->to : NULL;
	    addr2 = env ? env->cc : NULL;
	    break;
	  case 'p' :
	    addr1 = env ? env->to : NULL;
	    addr2 = env ? env->cc : NULL;
	    addr3 = env ? env->from : NULL;
	    break;
	  case 'f' :
	    addr1 = env ? env->from : NULL;
	    break;
	  case 'c' :
	    addr1 = env ? env->cc : NULL;
	    break;
	    break;
	  case 't' :
	    addr1 = env ? env->to : NULL;
	    break;
	  default :
	    q_status_message(SM_ORDER, 3, 3, "Unhandled case2 in agg_text_select");
	    break;
	}

	if(addr1 && address_is_us(addr1, ps_global)){
	  if(rawno > 0L && rawno <= stream->nmsgs
	     && (mc=mail_elt(stream, rawno)) != NULL)
	    mm_searched(stream, rawno);
	}
	else if(addr2 && address_is_us(addr2, ps_global)){
	  if(rawno > 0L && rawno <= stream->nmsgs
	     && (mc=mail_elt(stream, rawno)) != NULL)
	    mm_searched(stream, rawno);
	}
	else if(addr3 && address_is_us(addr3, ps_global)){
	  if(rawno > 0L && rawno <= stream->nmsgs
	     && (mc=mail_elt(stream, rawno)) != NULL)
	    mm_searched(stream, rawno);
	}
      }
    }

    if(ss)
      mail_free_searchset(&ss);

    if(not){
	for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
	    if((mc=mail_elt(stream, rawno)) && mc->searched)
	      mc->searched = NIL;
	    else
	      mc->searched = T;
	}
    }
}


int
agg_flag_select(MAILSTREAM *stream, int not, int crit, SEARCHSET **limitsrch)
{
    SEARCHPGM *pgm;

    pgm = mail_newsearchpgm();
    switch(crit){
      case 'n' :
	if(not){
	    SEARCHPGM *notpgm;

	    /* this is the same as seen or deleted or answered */
	    pgm->not = mail_newsearchpgmlist();
	    notpgm = pgm->not->pgm = mail_newsearchpgm();
	    notpgm->unseen = notpgm->undeleted = notpgm->unanswered = 1;
	}
	else
	  pgm->unseen = pgm->undeleted = pgm->unanswered = 1;
	  
	break;

      case 'd' :
	if(not)
	  pgm->undeleted = 1;
	else
	  pgm->deleted = 1;

	break;

      case 'r' :
	if(not)
	  pgm->old = 1;
	else
	  pgm->recent = 1;

	break;

      case 'u' :
	if(not)
	  pgm->seen = 1;
	else
	  pgm->unseen = 1;

	break;

      case 'a':
	/*
	 * Not a true "not", we are implicitly only interested in undeleted.
	 */
	if(not)
	  pgm->unanswered = pgm->undeleted = 1;
	else
	  pgm->answered = pgm->undeleted = 1;
	break;

      case 'f':
        {
	    STRINGLIST **slpp;

	    for(slpp = (not) ? &pgm->unkeyword : &pgm->keyword;
		*slpp;
		slpp = &(*slpp)->next)
	      ;

	    *slpp = mail_newstringlist();
	    (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG);
	    (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG);
	}

	break;

      case '*' :
	if(not)
	  pgm->unflagged = 1;
	else
	  pgm->flagged = 1;
	  
	break;

      default :
	return(1);
	break;
    }

    pgm->msgno = (limitsrch ? *limitsrch : NULL);
    pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
    /* we know this was freed in mail_search, let caller know */
    if(limitsrch)
      *limitsrch = NULL;

    return(0);
}


/*
 * Get the user name from the mailbox portion of an address.
 *
 * Args: mailbox -- the mailbox portion of an address (lhs of address)
 *       target  -- a buffer to put the result in
 *       len     -- length of the target buffer
 *
 * Returns the left most portion up to the first '%', ':' or '@',
 * and to the right of any '!' (as if c-client would give us such a mailbox).
 * Returns NULL if it can't find a username to point to.
 */
char *
get_uname(char *mailbox, char *target, int len)
{
    int i, start, end;

    if(!mailbox || !*mailbox)
      return(NULL);

    end = strlen(mailbox) - 1;
    for(start = end; start > -1 && mailbox[start] != '!'; start--)
        if(strindex("%:@", mailbox[start]))
	    end = start - 1;

    start++;			/* compensate for either case above */

    for(i = start; i <= end && (i-start) < (len-1); i++) /* copy name */
      target[i-start] = isupper((unsigned char)mailbox[i])
					  ? tolower((unsigned char)mailbox[i])
					  : mailbox[i];

    target[i-start] = '\0';	/* tie it off */

    return(*target ? target : NULL);
}
