summaryrefslogtreecommitdiff
path: root/pith/mailcmd.c
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/mailcmd.c
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'pith/mailcmd.c')
-rw-r--r--pith/mailcmd.c2741
1 files changed, 2741 insertions, 0 deletions
diff --git a/pith/mailcmd.c b/pith/mailcmd.c
new file mode 100644
index 00000000..78ba98ad
--- /dev/null
+++ b/pith/mailcmd.c
@@ -0,0 +1,2741 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: mailcmd.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#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)
+{
+ 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(stream);
+ 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", 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);
+
+ /*
+ * We aren't in a situation where we want a single cancel to
+ * apply to multiple opens.
+ */
+ ps_global->user_says_cancel = 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(strcmp(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));
+
+ 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", 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);
+
+ 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:
+ 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)
+ ret = (*pith_opt_expunge_prompt)(stream, pretty_fn(folder), delete_count);
+
+ /* 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;
+
+ 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(folder),
+ no_close ? "" : "\". ",
+ final_msg ? "Kept" : "Keeping",
+ comatose(stream->nmsgs - filtered - delete_count),
+ plural(stream->nmsgs - filtered - delete_count),
+ ing,
+ long2string(delete_count));
+ 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){
+ if(stream->nmsgs){
+ snprintf(buff2, sizeof(buff2),
+ "Clos%s folder \"%.*s\". %s%s%s message%s.",
+ ing,
+ sizeof(buff2)-50, pretty_fn(folder),
+ final_msg ? "Kept" : "Keeping",
+ (stream->nmsgs == 1L) ? " single" : " all ",
+ (stream->nmsgs > 1L)
+ ? comatose(stream->nmsgs) : "",
+ plural(stream->nmsgs));
+ }
+ else{
+ snprintf(buff2, sizeof(buff2), "Clos%s empty folder \"%.*s\"",
+ ing, sizeof(buff2)-50, pretty_fn(folder));
+ }
+
+ 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);
+
+ snprintf(buff2, sizeof(buff2), "Clos%s news group \"%.*s\"",
+ ing, sizeof(buff2)-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){
+ ret = (*pith_opt_expunge_prompt)(stream, pretty_fn(folder), delete_count);
+ 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
+ snprintf(buff2, sizeof(buff2),
+ "Clos%s read-only folder \"%.*s\". No changes to save",
+ ing, sizeof(buff2)-60, pretty_fn(folder));
+
+ 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 get's 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 '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
+ && (mc=mail_elt(stream, msgno)) && mc->searched)
+ mc->searched = NIL;
+ else
+ mc->searched = 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((mc=mail_elt(stream, rawno)) != NULL)
+ mm_searched(stream, rawno);
+ }
+ else if(addr2 && address_is_us(addr2, ps_global)){
+ if((mc=mail_elt(stream, rawno)) != NULL)
+ mm_searched(stream, rawno);
+ }
+ else if(addr3 && address_is_us(addr3, ps_global)){
+ if((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);
+}