* Rewrite support for specific SSL encryption protocols, including
[alpine.git] / pith / mailcmd.c
blobc5544f2006ef058eecb1db3ab1a0e272f4b471a5
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailcmd.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2018 Eduardo Chappa
8 * Copyright 2006-2007 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/mailcmd.h"
21 #include "../pith/conf.h"
22 #include "../pith/status.h"
23 #include "../pith/flag.h"
24 #include "../pith/thread.h"
25 #include "../pith/util.h"
26 #include "../pith/folder.h"
27 #include "../pith/sort.h"
28 #include "../pith/newmail.h"
29 #include "../pith/mailview.h"
30 #include "../pith/mailindx.h"
31 #include "../pith/save.h"
32 #include "../pith/news.h"
33 #include "../pith/sequence.h"
34 #include "../pith/stream.h"
35 #include "../pith/ldap.h"
36 #include "../pith/options.h"
37 #include "../pith/busy.h"
38 #include "../pith/icache.h"
39 #include "../pith/ablookup.h"
40 #include "../pith/search.h"
41 #include "../pith/charconv/utf8.h"
43 #ifdef _WINDOWS
44 #include "../pico/osdep/mswin.h"
45 #endif
49 * Internal prototypes
54 * optional function hooks
56 int (*pith_opt_read_msg_prompt)(long, char *);
57 int (*pith_opt_reopen_folder)(struct pine *, int *);
58 int (*pith_opt_expunge_prompt)(MAILSTREAM *, char *, long);
59 void (*pith_opt_begin_closing)(int, char *);
60 void get_new_message_count(MAILSTREAM *, int, long *, long *);
61 char *new_messages_string(MAILSTREAM *);
62 void search_for_our_regex_addresses(MAILSTREAM *stream, char type,
63 int not, SEARCHSET *searchset);
67 /*----------------------------------------------------------------------
68 Complain about command on empty folder
70 Args: map -- msgmap
71 type -- type of message that's missing
72 cmd -- string explaining command attempted
74 ----*/
75 int
76 any_messages(MSGNO_S *map, char *type, char *cmd)
78 if(mn_get_total(map) <= 0L){
79 q_status_message5(SM_ORDER, 0, 2, "No %s%s%s%s%s",
80 type ? type : "",
81 type ? " " : "",
82 THRD_INDX() ? "threads" : "messages",
83 (!cmd || *cmd != '.') ? " " : "",
84 cmd ? cmd : "in folder");
85 return(FALSE);
88 return(TRUE);
92 /*----------------------------------------------------------------------
93 test whether or not we have a valid stream to set flags on
95 Args: state -- pine state containing vital signs
96 cmd -- string explaining command attempted
97 permflag -- associated permanent flag state
99 Result: returns 1 if we can set flags, otw 0 and complains
101 ----*/
103 can_set_flag(struct pine *state, char *cmd, int permflag)
105 if((!permflag && READONLY_FOLDER(state->mail_stream))
106 || sp_dead_stream(state->mail_stream)){
107 q_status_message2(SM_ORDER | (sp_dead_stream(state->mail_stream)
108 ? SM_DING : 0),
109 0, 3,
110 "Can't %s message. Folder is %s.", cmd,
111 (sp_dead_stream(state->mail_stream)) ? "closed" : "read-only");
112 return(FALSE);
115 return(TRUE);
119 /*----------------------------------------------------------------------
120 Complain about command on empty folder
122 Args: type -- type of message that's missing
123 cmd -- string explaining command attempted
125 ----*/
126 void
127 cmd_cancelled(char *cmd)
129 /* TRANSLATORS: Arg is replaced with the command name or the word Command */
130 q_status_message1(SM_INFO, 0, 2, _("%s cancelled"), cmd ? cmd : _("Command"));
134 /*----------------------------------------------------------------------
135 Execute DELETE message command
137 Args: state -- Various satate info
138 msgmap -- map of c-client to local message numbers
140 Result: with side effect of "current" message delete flag set
142 ----*/
144 cmd_delete(struct pine *state, MSGNO_S *msgmap, int copts,
145 char *(*cmd_action_f)(struct pine *, MSGNO_S *))
147 int lastmsg, rv = 0;
148 long msgno, del_count = 0L, new;
149 char *sequence = NULL, prompt[128];
151 dprint((4, "\n - delete message -\n"));
152 if(!(any_messages(msgmap, NULL, "to Delete")
153 && can_set_flag(state, "delete", state->mail_stream->perm_deleted)))
154 return rv;
156 rv++;
158 if(sp_io_error_on_stream(state->mail_stream)){
159 sp_set_io_error_on_stream(state->mail_stream, 0);
160 pine_mail_check(state->mail_stream); /* forces write */
163 if(MCMD_ISAGG(copts)){
164 sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 0);
165 snprintf(prompt, sizeof(prompt), "%ld%s message%s marked for deletion",
166 del_count, (copts & MCMD_AGG_2) ? "" : " selected", plural(del_count));
168 else{
169 long rawno;
171 msgno = mn_get_cur(msgmap);
172 rawno = mn_m2raw(msgmap, msgno);
173 del_count = 1L; /* return current */
174 sequence = cpystr(long2string(rawno));
175 lastmsg = (msgno >= mn_get_total(msgmap));
176 snprintf(prompt, sizeof(prompt), "%s%s marked for deletion",
177 lastmsg ? "Last message" : "Message ",
178 lastmsg ? "" : long2string(msgno));
181 dprint((3, "DELETE: msg %s\n", sequence ? sequence : "?"));
182 new = sp_new_mail_count(state->mail_stream);
183 mail_flag(state->mail_stream, sequence, "\\DELETED", ST_SET);
184 fs_give((void **) &sequence);
185 if(new != sp_new_mail_count(state->mail_stream))
186 process_filter_patterns(state->mail_stream, state->msgmap,
187 sp_new_mail_count(state->mail_stream));
189 if(cmd_action_f){
190 char *rv;
192 if((rv = (*cmd_action_f)(state, msgmap)) != NULL)
193 strncat(prompt, rv, sizeof(prompt) - strlen(prompt)- 1);
196 if(!(copts & MCMD_SILENT))
197 q_status_message(SM_ORDER, 0, 3, prompt);
199 return rv;
203 /*----------------------------------------------------------------------
204 Execute UNDELETE message command
206 Args: state -- Various satate info
207 msgmap -- map of c-client to local message numbers
209 Result: with side effect of "current" message delete flag UNset
211 ----*/
213 cmd_undelete(struct pine *state, MSGNO_S *msgmap, int copts)
215 long del_count;
216 char *sequence;
217 int wasdeleted = FALSE, rv = 0;
218 MESSAGECACHE *mc;
220 dprint((4, "\n - undelete -\n"));
221 if(!(any_messages(msgmap, NULL, "to Undelete")
222 && can_set_flag(state, "undelete", state->mail_stream->perm_deleted)))
223 return rv;
225 rv++;
227 if(MCMD_ISAGG(copts)){
228 del_count = 0L; /* return current */
229 sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 1);
230 wasdeleted = TRUE;
232 else{
233 long rawno;
234 int exbits = 0;
236 del_count = 1L; /* return current */
237 rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
238 sequence = cpystr(long2string(rawno));
239 wasdeleted = (state->mail_stream
240 && rawno > 0L && rawno <= state->mail_stream->nmsgs
241 && (mc = mail_elt(state->mail_stream, rawno))
242 && mc->valid
243 && mc->deleted);
245 * Mark this message manually flagged so we don't re-filter it
246 * with a filter which only sets flags.
248 if(msgno_exceptions(state->mail_stream, rawno, "0", &exbits, FALSE))
249 exbits |= MSG_EX_MANUNDEL;
250 else
251 exbits = MSG_EX_MANUNDEL;
253 msgno_exceptions(state->mail_stream, rawno, "0", &exbits, TRUE);
256 dprint((3, "UNDELETE: msg %s\n", sequence ? sequence : "?"));
258 mail_flag(state->mail_stream, sequence, "\\DELETED", 0L);
259 fs_give((void **) &sequence);
261 if((copts & MCMD_SILENT) == 0){
262 if(del_count == 1L && MCMD_ISAGG(copts) == 0){
263 q_status_message(SM_ORDER, 0, 3,
264 wasdeleted
265 ? _("Deletion mark removed, message won't be deleted")
266 : _("Message not marked for deletion; no action taken"));
268 else
269 q_status_message2(SM_ORDER, 0, 3,
270 _("Deletion mark removed from %s message%s"),
271 comatose(del_count), plural(del_count));
274 if(sp_io_error_on_stream(state->mail_stream)){
275 sp_set_io_error_on_stream(state->mail_stream, 0);
276 pine_mail_check(state->mail_stream); /* forces write */
279 return rv;
284 cmd_expunge_work(MAILSTREAM *stream, MSGNO_S *msgmap, char *seq)
286 long old_max_msgno;
287 int rv = 0;
289 old_max_msgno = mn_get_total(msgmap);
290 delete_filtered_msgs(stream);
291 ps_global->expunge_in_progress = 1;
292 mail_expunge_full(stream, seq, 0);
293 ps_global->expunge_in_progress = 0;
295 dprint((2,"expunge complete cur:%ld max:%ld\n",
296 mn_get_cur(msgmap), mn_get_total(msgmap)));
298 * This is only actually necessary if this causes the width of the
299 * message number field to change. That is, it depends on the
300 * format the user is using as well as on the max_msgno. Since it
301 * should be rare, we'll just do it whenever it happens.
302 * Also have to check for an increase in max_msgno on new mail.
304 if((old_max_msgno >= 1000L && mn_get_total(msgmap) < 1000L)
305 || (old_max_msgno >= 10000L && mn_get_total(msgmap) < 10000L)
306 || (old_max_msgno >= 100000L && mn_get_total(msgmap) < 100000L)){
307 clear_index_cache(stream, 0);
308 rv = 1;
312 * mm_exists and mm_expunge take care of updating max_msgno
313 * and selecting a new message should the selected get removed
317 reset_check_point(stream);
319 return(rv);
323 CONTEXT_S *
324 broach_get_folder(CONTEXT_S *context, int *inbox, char **folder)
326 CONTEXT_S *tc;
328 if(ps_global->goto_default_rule == GOTO_LAST_FLDR){
329 tc = context ? context : ps_global->context_current;
330 *inbox = 1; /* fill in last_folder below */
332 else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN){
333 tc = (ps_global->context_list->use & CNTXT_INCMNG)
334 ? ps_global->context_list->next : ps_global->context_list;
335 ps_global->last_unambig_folder[0] = '\0';
336 *inbox = 1; /* fill in last_folder below */
338 else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX){
339 tc = (ps_global->context_list->use & CNTXT_INCMNG)
340 ? ps_global->context_list->next : ps_global->context_list;
341 tc->last_folder[0] = '\0';
342 *inbox = 0;
343 ps_global->last_unambig_folder[0] = '\0';
345 else{
346 *inbox = (ps_global->cur_folder
347 && ps_global->inbox_name
348 && strucmp(ps_global->cur_folder,ps_global->inbox_name) == 0
349 && (!ps_global->context_current
350 || ps_global->context_current->use & CNTXT_INCMNG
351 || (!(ps_global->context_list->use & CNTXT_INCMNG)
352 && ps_global->context_current == ps_global->context_list)));
353 if(!*inbox)
354 tc = ps_global->context_list; /* inbox's context */
355 else if(ps_global->goto_default_rule == GOTO_INBOX_FIRST_CLCTN){
356 tc = (ps_global->context_list->use & CNTXT_INCMNG)
357 ? ps_global->context_list->next : ps_global->context_list;
358 ps_global->last_unambig_folder[0] = '\0';
360 else
361 tc = context ? context : ps_global->context_current;
364 if(folder){
365 if(!*inbox){
366 *folder = ps_global->inbox_name;
368 else
369 *folder = (ps_global->last_unambig_folder[0])
370 ? ps_global->last_unambig_folder
371 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
374 return(tc);
378 /*----------------------------------------------------------------------
379 Actually attempt to open given folder
381 Args: newfolder -- The folder name to open
382 streamp -- Candidate stream for recycling. This stream will either
383 be re-used, or it will be closed.
385 Result: 1 if the folder was successfully opened
386 0 if the folder open failed and went back to old folder
387 -1 if open failed and no folder is left open
389 Attempt to open the folder name given. If the open of the new folder
390 fails then the previously open folder will remain open, unless
391 something really bad has happened. The designate inbox will always be
392 kept open, and when a request to open it is made the already open
393 stream will be used.
394 ----*/
396 do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp,
397 long unsigned int flags)
399 MAILSTREAM *m, *strm, *stream = streamp ? *streamp : NULL;
400 int open_inbox, rv, old_tros, we_cancel = 0,
401 do_reopen = 0, n, was_dead = 0, cur_already_set = 0;
402 char expanded_file[MAX(MAXPATH,MAILTMPLEN)+1],
403 *old_folder, *old_path, *p, *report;
404 unsigned char *fname;
405 long openmode, rflags = 0L, pc = 0L, cur, raw;
406 ENVELOPE *env = NULL;
407 char status_msg[81];
408 SortOrder old_sort;
409 unsigned perfolder_startup_rule;
410 char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN], *lname, *mname;
412 openmode = SP_USERFLDR;
414 dprint((1, "About to open folder \"%s\" inbox is: \"%s\"\n",
415 newfolder ? newfolder : "?",
416 ps_global->inbox_name ? ps_global->inbox_name : "?"));
419 *--- Set flag that we're opening the inbox, a special case.
421 * We want to know if inbox is being opened either by name OR
422 * fully qualified path...
424 if(strucmp(newfolder, ps_global->inbox_name) == 0)
425 open_inbox = (flags & DB_INBOXWOCNTXT || new_context == ps_global->context_list);
426 else{
427 open_inbox = (strcmp(newfolder, ps_global->VAR_INBOX_PATH) == 0
428 || same_remote_mailboxes(newfolder, ps_global->VAR_INBOX_PATH)
429 || (!IS_REMOTE(newfolder)
430 && (lname=mailboxfile(tmp1,newfolder))
431 && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
432 && !strcmp(lname,mname)));
434 /* further checking for inbox open */
435 if(!open_inbox && new_context && context_isambig(newfolder)){
436 if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
438 * Check for an incoming folder other
439 * than INBOX that also point to INBOX.
441 open_inbox = (strucmp(p, ps_global->inbox_name) == 0
442 || strcmp(p, ps_global->VAR_INBOX_PATH) == 0
443 || same_remote_mailboxes(p, ps_global->VAR_INBOX_PATH)
444 || (!IS_REMOTE(p)
445 && (lname=mailboxfile(tmp1,p))
446 && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
447 && !strcmp(lname,mname)));
449 else if(!(new_context->use & CNTXT_INCMNG)){
450 char tmp3[MAILTMPLEN];
453 * Check to see if we are opening INBOX using the folder name
454 * and a context. We won't have recognized this is the
455 * same as INBOX without applying the context first.
457 context_apply(tmp3, new_context, newfolder, sizeof(tmp3));
458 open_inbox = (strucmp(tmp3, ps_global->inbox_name) == 0
459 || strcmp(tmp3, ps_global->VAR_INBOX_PATH) == 0
460 || same_remote_mailboxes(tmp3, ps_global->VAR_INBOX_PATH)
461 || (!IS_REMOTE(tmp3)
462 && (lname=mailboxfile(tmp1,tmp3))
463 && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
464 && !strcmp(lname,mname)));
469 if(open_inbox)
470 new_context = ps_global->context_list; /* restore first context */
472 was_dead = sp_a_locked_stream_is_dead();
474 /*----- Little to do to if reopening same folder -----*/
475 if(new_context == ps_global->context_current && ps_global->mail_stream
476 && (strcmp(newfolder, ps_global->cur_folder) == 0
477 || (open_inbox && sp_flagged(ps_global->mail_stream, SP_INBOX)))){
478 if(stream){
479 pine_mail_close(stream); /* don't need it */
480 stream = NULL;
483 if(sp_dead_stream(ps_global->mail_stream))
484 do_reopen++;
487 * If it is a stream which could probably discover newmail by
488 * reopening and user has YES set for those streams, or it
489 * is a stream which may discover newmail by reopening and
490 * user has YES set for those stream, then do_reopen.
492 if(!do_reopen
494 (((ps_global->mail_stream->dtb
495 && ((ps_global->mail_stream->dtb->flags & DR_NONEWMAIL)
496 || (ps_global->mail_stream->rdonly
497 && ps_global->mail_stream->dtb->flags
498 & DR_NONEWMAILRONLY)))
499 && (ps_global->reopen_rule == REOPEN_YES_YES
500 || ps_global->reopen_rule == REOPEN_YES_ASK_Y
501 || ps_global->reopen_rule == REOPEN_YES_ASK_N
502 || ps_global->reopen_rule == REOPEN_YES_NO))
504 ((ps_global->mail_stream->dtb
505 && ps_global->mail_stream->rdonly
506 && !(ps_global->mail_stream->dtb->flags & DR_LOCAL))
507 && (ps_global->reopen_rule == REOPEN_YES_YES))))
508 do_reopen++;
511 * If it is a stream which could probably discover newmail by
512 * reopening and user has ASK set for those streams, or it
513 * is a stream which may discover newmail by reopening and
514 * user has ASK set for those stream, then ask.
516 if(!do_reopen
517 && pith_opt_reopen_folder
518 && (*pith_opt_reopen_folder)(ps_global, &do_reopen) < 0){
519 cmd_cancelled(NULL);
520 return(0);
523 if(do_reopen){
525 * If it's not healthy or if the user explicitly wants to
526 * do a reopen, we reset things and fall thru
527 * to actually reopen it.
529 if(sp_dead_stream(ps_global->mail_stream)){
530 dprint((2, "Stream was dead, reopening \"%s\"\n",
531 newfolder ? newfolder : "?"));
534 /* clean up */
535 pine_mail_actually_close(ps_global->mail_stream);
536 ps_global->mangled_header = 1;
537 clear_index_cache(ps_global->mail_stream, 0);
539 else{
540 if(!(flags & DB_NOVISIT))
541 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
543 return(1); /* successful open of same folder! */
548 * If ambiguous foldername (not fully qualified), make sure it's
549 * not a nickname for a folder in the given context...
552 /* might get reset below */
553 strncpy(expanded_file, newfolder, sizeof(expanded_file));
554 expanded_file[sizeof(expanded_file)-1] = '\0';
556 if(!open_inbox && new_context && context_isambig(newfolder)){
557 if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
558 strncpy(expanded_file, p, sizeof(expanded_file));
559 expanded_file[sizeof(expanded_file)-1] = '\0';
560 dprint((2, "broach_folder: nickname for %s is %s\n",
561 expanded_file ? expanded_file : "?",
562 newfolder ? newfolder : "?"));
564 else if((new_context->use & CNTXT_INCMNG)
565 && (folder_index(newfolder, new_context, FI_FOLDER) < 0)
566 && !is_absolute_path(newfolder)){
567 fname = folder_name_decoded((unsigned char *)newfolder);
568 q_status_message1(SM_ORDER, 3, 4,
569 _("Can't find Incoming Folder %s."), fname ? (char *) fname : newfolder);
570 if(stream)
571 pine_mail_close(stream);
573 if(fname)
574 fs_give((void **)&fname);
576 return(0);
580 /*--- Opening inbox, inbox has been already opened, the easy case ---*/
582 * [ It is probably true that we could eliminate most of this special ]
583 * [ inbox stuff and just get the inbox stream back when we do the ]
584 * [ context_open below, but figuring that out hasn't been done. ]
586 if(open_inbox && (strm=sp_inbox_stream())){
587 if(sp_dead_stream(strm)){
589 * if dead INBOX, just close it and let it be reopened.
590 * This is different from the do_reopen case above,
591 * because we're going from another open mail folder to the
592 * dead INBOX.
594 dprint((2, "INBOX was dead, closing before reopening\n"));
595 pine_mail_actually_close(strm);
597 else{
599 * Clean up the mail_stream we're leaving.
601 if(ps_global->mail_stream
602 && (!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
603 || (sp_flagged(ps_global->mail_stream, SP_INBOX)
604 && F_ON(F_EXPUNGE_INBOX, ps_global))
605 || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
606 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
607 && F_ON(F_EXPUNGE_STAYOPENS, ps_global))))
608 expunge_and_close(ps_global->mail_stream, NULL,
609 sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
610 ? EC_NO_CLOSE : EC_NONE);
611 else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
612 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
614 * We want to save our position in the folder so that when we
615 * come back to this folder again, we can place the cursor on
616 * a reasonable message number.
618 sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);
620 if(ps_global->mail_stream->nmsgs > 0L){
621 cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
622 raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
623 if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
624 env = pine_mail_fetchstructure(ps_global->mail_stream,
625 raw, NULL);
627 if(env && env->message_id && env->message_id[0])
628 sp_set_saved_cur_msg_id(ps_global->mail_stream,
629 env->message_id);
634 * Make the already open inbox the current mailbox.
636 ps_global->mail_stream = strm;
637 ps_global->msgmap = sp_msgmap(strm);
639 if(was_dead && pith_opt_icon_text)
640 (*pith_opt_icon_text)(NULL, IT_MCLOSED);
642 dprint((7, "%ld %ld %x\n",
643 mn_get_cur(ps_global->msgmap),
644 mn_get_total(ps_global->msgmap),
645 ps_global->mail_stream));
647 * remember last context and folder
649 if(context_isambig(ps_global->cur_folder)){
650 ps_global->context_last = ps_global->context_current;
651 snprintf(ps_global->context_current->last_folder,
652 sizeof(ps_global->context_current->last_folder),
653 "%s", ps_global->cur_folder);
654 ps_global->last_unambig_folder[0] = '\0';
656 else{
657 ps_global->context_last = NULL;
658 snprintf(ps_global->last_unambig_folder,
659 sizeof(ps_global->last_unambig_folder),
660 "%s", ps_global->cur_folder);
663 p = sp_fldr(ps_global->mail_stream) ? sp_fldr(ps_global->mail_stream)
664 : ps_global->inbox_name;
665 strncpy(ps_global->cur_folder, p, sizeof(ps_global->cur_folder)-1);
666 ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
667 ps_global->context_current = ps_global->context_list;
668 reset_index_format();
669 clear_index_cache(ps_global->mail_stream, 0);
670 /* MUST sort before restoring msgno! */
671 refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);
672 report = new_messages_string(ps_global->mail_stream);
673 q_status_message3(SM_ORDER, 0, 3,
674 (mn_get_total(ps_global->msgmap) > 1)
675 ? _("Opened folder \"%s\" with %s messages%s")
676 : _("Opened folder \"%s\" with %s message%s"),
677 ps_global->inbox_name,
678 long2string(mn_get_total(ps_global->msgmap)),
679 report ? report : "");
680 if(report)
681 fs_give((void **)&report);
683 #ifdef _WINDOWS
684 mswin_settitle(ps_global->inbox_name);
685 #endif
686 if(stream)
687 pine_mail_close(stream);
689 if(!(flags & DB_NOVISIT))
690 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
692 return(1);
696 if(!new_context && !expand_foldername(expanded_file,sizeof(expanded_file))){
697 if(stream)
698 pine_mail_close(stream);
700 return(0);
704 * This is a safe time to clean up dead streams because nothing should
705 * be referencing them right now.
707 sp_cleanup_dead_streams();
709 old_folder = NULL;
710 old_path = NULL;
711 old_sort = SortArrival; /* old sort */
712 old_tros = 0; /* old reverse sort ? */
713 /*---- now close the old one we had open if there was one ----*/
714 if(ps_global->mail_stream != NULL){
715 old_folder = cpystr(ps_global->cur_folder);
716 old_path = cpystr(ps_global->mail_stream->original_mailbox
717 ? ps_global->mail_stream->original_mailbox
718 : ps_global->mail_stream->mailbox);
719 old_sort = mn_get_sort(ps_global->msgmap);
720 old_tros = mn_get_revsort(ps_global->msgmap);
721 if(!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
722 || (sp_flagged(ps_global->mail_stream, SP_INBOX)
723 && F_ON(F_EXPUNGE_INBOX, ps_global))
724 || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
725 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
726 && F_ON(F_EXPUNGE_STAYOPENS, ps_global)))
727 expunge_and_close(ps_global->mail_stream, NULL,
728 sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
729 ? EC_NO_CLOSE : EC_NONE);
730 else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
731 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
733 * We want to save our position in the folder so that when we
734 * come back to this folder again, we can place the cursor on
735 * a reasonable message number.
738 sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);
740 if(ps_global->mail_stream->nmsgs > 0L){
741 cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
742 raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
743 if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
744 env = pine_mail_fetchstructure(ps_global->mail_stream,
745 raw, NULL);
747 if(env && env->message_id && env->message_id[0])
748 sp_set_saved_cur_msg_id(ps_global->mail_stream,
749 env->message_id);
753 ps_global->mail_stream = NULL;
756 snprintf(status_msg, sizeof(status_msg), "%sOpening \"", do_reopen ? "Re-" : "");
757 fname = folder_name_decoded((unsigned char *)newfolder);
758 strncat(status_msg, pretty_fn(fname ? (char *) fname : newfolder),
759 sizeof(status_msg)-strlen(status_msg) - 2);
760 if(fname) fs_give((void **)&fname);
761 status_msg[sizeof(status_msg)-2] = '\0';
762 strncat(status_msg, "\"", sizeof(status_msg)-strlen(status_msg) - 1);
763 status_msg[sizeof(status_msg)-1] = '\0';
764 we_cancel = busy_cue(status_msg, NULL, 0);
767 * if requested, make access to folder readonly (only once)
769 if(ps_global->open_readonly_on_startup){
770 openmode |= OP_READONLY;
771 ps_global->open_readonly_on_startup = 0;
774 if(!(flags & DB_NOVISIT))
775 ps_global->first_open_was_attempted = 1;
777 openmode |= SP_USEPOOL;
779 if(stream)
780 sp_set_first_unseen(stream, 0L);
782 /* in case we closed the old stream by cancelling the connection, do
783 * not let that interfere with opening the new stream.
785 ps_global->user_says_cancel = 0;
787 m = context_open((new_context && !open_inbox) ? new_context : NULL,
788 stream,
789 open_inbox ? ps_global->VAR_INBOX_PATH : expanded_file,
790 openmode | (open_inbox ? SP_INBOX : 0),
791 &rflags);
794 * We aren't in a situation where we want a single cancel to
795 * apply to multiple opens.
797 ps_global->user_says_cancel = 0;
799 if(streamp)
800 *streamp = m;
803 dprint((8, "Opened folder %p \"%s\" (context: \"%s\")\n",
804 m, (m && m->mailbox) ? m->mailbox : "nil",
805 (new_context && new_context->context)
806 ? new_context->context : "nil"));
809 /* Can get m != NULL if correct passwd for remote, but wrong name */
810 if(m == NULL || m->halfopen){
811 /*-- non-existent local mailbox, or wrong passwd for remote mailbox--*/
812 /* fall back to currently open mailbox */
813 if(we_cancel)
814 cancel_busy_cue(-1);
816 ps_global->mail_stream = NULL;
817 if(m)
818 pine_mail_actually_close(m);
820 rv = 0;
821 dprint((8, "Old folder: \"%s\"\n",
822 old_folder == NULL ? "" : old_folder));
823 if(old_folder != NULL){
824 if(strcmp(old_folder, ps_global->inbox_name) == 0){
825 ps_global->mail_stream = sp_inbox_stream();
826 ps_global->msgmap = sp_msgmap(ps_global->mail_stream);
828 dprint((8, "Reactivate inbox %ld %ld %p\n",
829 mn_get_cur(ps_global->msgmap),
830 mn_get_total(ps_global->msgmap),
831 ps_global->mail_stream));
832 p = sp_fldr(ps_global->mail_stream)
833 ? sp_fldr(ps_global->mail_stream)
834 : ps_global->inbox_name;
835 strncpy(ps_global->cur_folder, p,
836 sizeof(ps_global->cur_folder)-1);
837 ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
839 else{
840 ps_global->mail_stream = pine_mail_open(NULL, old_path,
841 openmode, &rflags);
842 /* mm_log will take care of error message here */
843 if(ps_global->mail_stream == NULL){
844 rv = -1;
846 else{
847 ps_global->msgmap = sp_msgmap(ps_global->mail_stream);
848 mn_set_sort(ps_global->msgmap, old_sort);
849 mn_set_revsort(ps_global->msgmap, old_tros);
850 ps_global->mangled_header = 1;
851 reset_index_format();
852 clear_index_cache(ps_global->mail_stream, 0);
854 if(!(rflags & SP_MATCH)){
855 sp_set_expunge_count(ps_global->mail_stream, 0L);
856 sp_set_new_mail_count(ps_global->mail_stream, 0L);
857 sp_set_dead_stream(ps_global->mail_stream, 0);
858 sp_set_noticed_dead_stream(ps_global->mail_stream, 0);
860 reset_check_point(ps_global->mail_stream);
861 if(IS_NEWS(ps_global->mail_stream)
862 && ps_global->mail_stream->rdonly)
863 msgno_exclude_deleted(ps_global->mail_stream,
864 sp_msgmap(ps_global->mail_stream),
865 NULL);
867 if(mn_get_total(ps_global->msgmap) > 0)
868 mn_set_cur(ps_global->msgmap,
869 first_sorted_flagged(F_NONE,
870 ps_global->mail_stream,
872 THREADING()
873 ? 0 : FSF_SKIP_CHID));
875 if(!(mn_get_sort(ps_global->msgmap) == SortArrival
876 && !mn_get_revsort(ps_global->msgmap)))
877 refresh_sort(ps_global->mail_stream,
878 ps_global->msgmap, SRT_NON);
881 fname = folder_name_decoded((unsigned char *)old_folder);
882 q_status_message1(SM_ORDER, 0, 3,
883 "Folder \"%s\" reopened", fname ? (char *)fname : old_folder);
884 if(fname)
885 fs_give((void **)&fname);
889 if(rv == 0)
890 mn_set_cur(ps_global->msgmap,
891 MIN(mn_get_cur(ps_global->msgmap),
892 mn_get_total(ps_global->msgmap)));
894 fs_give((void **)&old_folder);
895 fs_give((void **)&old_path);
897 else
898 rv = -1;
900 if(rv == -1){
901 q_status_message(SM_ORDER | SM_DING, 0, 4, _("No folder opened"));
902 mn_set_total(ps_global->msgmap, 0L);
903 mn_set_nmsgs(ps_global->msgmap, 0L);
904 mn_set_cur(ps_global->msgmap, -1L);
905 ps_global->cur_folder[0] = '\0';
908 if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
909 (*pith_opt_icon_text)(NULL, IT_MCLOSED);
911 if(ps_global->mail_stream && !(flags & DB_NOVISIT))
912 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
914 if(ps_global->mail_stream)
915 sp_set_first_unseen(ps_global->mail_stream, 0L);
917 return(rv);
919 else{
920 if(old_folder != NULL){
921 fs_give((void **)&old_folder);
922 fs_give((void **)&old_path);
926 update_folder_unseen_by_stream(m, UFU_NONE);
928 /*----- success in opening the new folder ----*/
929 dprint((2, "Opened folder \"%s\" with %ld messages\n",
930 m->mailbox ? m->mailbox : "?", m->nmsgs));
933 /*--- A Little house keeping ---*/
935 ps_global->mail_stream = m;
936 if(!(flags & DB_NOVISIT))
937 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
939 ps_global->msgmap = sp_msgmap(m);
940 if(!(rflags & SP_MATCH)){
941 sp_set_expunge_count(m, 0L);
942 sp_set_new_mail_count(m, 0L);
943 sp_set_dead_stream(m, 0);
944 sp_set_noticed_dead_stream(m, 0);
945 sp_set_mail_box_changed(m, 0);
946 reset_check_point(m);
949 if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
950 (*pith_opt_icon_text)(NULL, IT_MCLOSED);
952 ps_global->last_unambig_folder[0] = '\0';
955 * remember old folder and context...
957 if(context_isambig(ps_global->cur_folder)){
958 ps_global->context_last = ps_global->context_current;
959 snprintf(ps_global->context_current->last_folder,
960 sizeof(ps_global->context_current->last_folder),
961 "%s", ps_global->cur_folder);
962 ps_global->last_unambig_folder[0] = '\0';
964 else{
965 ps_global->context_last = NULL;
966 snprintf(ps_global->last_unambig_folder,
967 sizeof(ps_global->last_unambig_folder),
968 "%s", ps_global->cur_folder);
971 /* folder in a subdir of context? */
972 if(ps_global->context_current->dir->prev)
973 snprintf(ps_global->cur_folder, sizeof(ps_global->cur_folder), "%s%s",
974 ps_global->context_current->dir->ref, newfolder);
975 else{
976 strncpy(ps_global->cur_folder,
977 (open_inbox) ? ps_global->inbox_name : newfolder,
978 sizeof(ps_global->cur_folder)-1);
979 ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
982 sp_set_fldr(ps_global->mail_stream, ps_global->cur_folder);
984 if(new_context){
985 ps_global->context_last = ps_global->context_current;
986 ps_global->context_current = new_context;
988 if(!open_inbox)
989 sp_set_context(ps_global->mail_stream, ps_global->context_current);
992 clear_index_cache(ps_global->mail_stream, 0);
993 reset_index_format();
996 * Start news reading with messages the user's marked deleted
997 * hidden from view...
999 if(IS_NEWS(ps_global->mail_stream) && ps_global->mail_stream->rdonly)
1000 msgno_exclude_deleted(ps_global->mail_stream, ps_global->msgmap, NULL);
1002 if(we_cancel && F_OFF(F_QUELL_FILTER_MSGS, ps_global))
1003 cancel_busy_cue(0);
1006 * If the stream we got from the open above was already opened earlier
1007 * for some temporary use, then it wouldn't have been filtered. That's
1008 * why we need this flag, so that we will filter if needed.
1010 if(!sp_flagged(ps_global->mail_stream, SP_FILTERED))
1011 process_filter_patterns(ps_global->mail_stream, ps_global->msgmap, 0L);
1014 * If no filtering messages wait until here to cancel the busy cue
1015 * because the user will be waiting for that filtering with nothing
1016 * showing the activity otherwise.
1018 if(we_cancel && F_ON(F_QUELL_FILTER_MSGS, ps_global))
1019 cancel_busy_cue(0);
1021 if(!(rflags & SP_MATCH) || !(rflags & SP_LOCKED))
1022 reset_sort_order(SRT_VRB);
1023 else if(sp_new_mail_count(ps_global->mail_stream) > 0L
1024 || sp_unsorted_newmail(ps_global->mail_stream)
1025 || sp_need_to_rethread(ps_global->mail_stream))
1026 refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);
1028 report = new_messages_string(ps_global->mail_stream);
1029 fname = folder_name_decoded((unsigned char *)newfolder);
1030 q_status_message7(SM_ORDER, 0, 4,
1031 "%s \"%s\" opened with %s message%s%s%s%s",
1032 IS_NEWS(ps_global->mail_stream)
1033 ? "News group" : "Folder",
1034 open_inbox ? pretty_fn(fname ? (char *) fname : newfolder)
1035 : (fname ? (char *)fname : newfolder),
1036 comatose(mn_get_total(ps_global->msgmap)),
1037 plural(mn_get_total(ps_global->msgmap)),
1038 (!open_inbox
1039 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED))
1040 ? " (StayOpen)" : "",
1041 READONLY_FOLDER(ps_global->mail_stream)
1042 ? " READONLY" : "",
1043 report ? report : "");
1045 if(fname)
1046 fs_give((void **)&fname);
1048 if(report)
1049 fs_give((void **)&report);
1051 #ifdef _WINDOWS
1052 mswin_settitle(pretty_fn(newfolder));
1053 #endif
1055 * Set current message number when re-opening Stay-Open or
1056 * cached folders.
1058 if(rflags & SP_MATCH){
1059 if(rflags & SP_LOCKED){
1060 if(F_OFF(F_STARTUP_STAYOPEN, ps_global)
1061 && (cur = get_msgno_by_msg_id(ps_global->mail_stream,
1062 sp_saved_cur_msg_id(ps_global->mail_stream),
1063 ps_global->msgmap)) >= 1L
1064 && cur <= mn_get_total(ps_global->msgmap)){
1065 cur_already_set++;
1066 mn_set_cur(ps_global->msgmap, (MsgNo) cur);
1067 if(flags & DB_FROMTAB){
1069 * When we TAB to a folder that is a StayOpen folder we try
1070 * to increment the current message # by one instead of doing
1071 * some search again. Some people probably won't like this
1072 * behavior, especially if the new message that has arrived
1073 * comes before where we are in the index. That's why we have
1074 * the F_STARTUP_STAYOPEN feature above.
1076 mn_inc_cur(m, ps_global->msgmap, MH_NONE);
1078 /* else leave it where it is */
1080 adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
1083 else{
1085 * If we're reopening a cached open stream that wasn't explicitly
1086 * kept open by the user, then the user expects it to act pretty
1087 * much like we are re-opening the stream. A problem is that the
1088 * recent messages are still recent because we haven't closed the
1089 * stream, so we fake a quasi-recentness by remembering the last
1090 * uid assigned on the stream when we pine_mail_close. Then when
1091 * we come back messages with uids higher than that are recent.
1093 * If uid_validity has changed, then we don't use any special
1094 * treatment, but just do the regular search.
1096 if(m->uid_validity == sp_saved_uid_validity(m)){
1097 long i;
1100 * Because first_sorted_flagged uses sequence numbers, find the
1101 * sequence number of the first message after the old last
1102 * uid assigned. I.e., the first recent message.
1104 for(i = m->nmsgs; i > 0L; i--)
1105 if(mail_uid(m, i) <= sp_saved_uid_last(m))
1106 break;
1108 if(i > 0L && i < m->nmsgs)
1109 pc = i+1L;
1115 if(!cur_already_set && mn_get_total(ps_global->msgmap) > 0L){
1117 perfolder_startup_rule = reset_startup_rule(ps_global->mail_stream);
1119 if(ps_global->start_entry > 0){
1120 mn_set_cur(ps_global->msgmap, mn_get_revsort(ps_global->msgmap)
1121 ? first_sorted_flagged(F_NONE, m,
1122 ps_global->start_entry,
1123 THREADING() ? 0 : FSF_SKIP_CHID)
1124 : first_sorted_flagged(F_SRCHBACK, m,
1125 ps_global->start_entry,
1126 THREADING() ? 0 : FSF_SKIP_CHID));
1127 ps_global->start_entry = 0;
1129 else if(perfolder_startup_rule != IS_NOTSET ||
1130 open_inbox ||
1131 ps_global->context_current->use & CNTXT_INCMNG){
1132 unsigned use_this_startup_rule;
1134 if(perfolder_startup_rule != IS_NOTSET)
1135 use_this_startup_rule = perfolder_startup_rule;
1136 else
1137 use_this_startup_rule = ps_global->inc_startup_rule;
1139 switch(use_this_startup_rule){
1141 * For news in incoming collection we're doing the same thing
1142 * for first-unseen and first-recent. In both those cases you
1143 * get first-unseen if FAKE_NEW is off and first-recent if
1144 * FAKE_NEW is on. If FAKE_NEW is on, first unseen is the
1145 * same as first recent because all recent msgs are unseen
1146 * and all unrecent msgs are seen (see pine_mail_open).
1148 case IS_FIRST_UNSEEN:
1149 first_unseen:
1150 mn_set_cur(ps_global->msgmap,
1151 (sp_first_unseen(m)
1152 && mn_get_sort(ps_global->msgmap) == SortArrival
1153 && !mn_get_revsort(ps_global->msgmap)
1154 && !get_lflag(ps_global->mail_stream, NULL,
1155 sp_first_unseen(m), MN_EXLD)
1156 && (n = mn_raw2m(ps_global->msgmap,
1157 sp_first_unseen(m))))
1159 : first_sorted_flagged(F_UNSEEN | F_UNDEL, m, pc,
1160 THREADING() ? 0 : FSF_SKIP_CHID));
1161 break;
1163 case IS_FIRST_RECENT:
1164 first_recent:
1166 * We could really use recent for news but this is the way
1167 * it has always worked, so we'll leave it. That is, if
1168 * the FAKE_NEW feature is on, recent and unseen are
1169 * equivalent, so it doesn't matter. If the feature isn't
1170 * on, all the undeleted messages are unseen and we start
1171 * at the first one. User controls with the FAKE_NEW feature.
1173 if(IS_NEWS(ps_global->mail_stream)){
1174 mn_set_cur(ps_global->msgmap,
1175 first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
1176 THREADING() ? 0 : FSF_SKIP_CHID));
1178 else{
1179 mn_set_cur(ps_global->msgmap,
1180 first_sorted_flagged(F_RECENT | F_UNSEEN
1181 | F_UNDEL,
1182 m, pc,
1183 THREADING() ? 0 : FSF_SKIP_CHID));
1185 break;
1187 case IS_FIRST_IMPORTANT:
1188 mn_set_cur(ps_global->msgmap,
1189 first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
1190 THREADING() ? 0 : FSF_SKIP_CHID));
1191 break;
1193 case IS_FIRST_IMPORTANT_OR_UNSEEN:
1195 if(IS_NEWS(ps_global->mail_stream))
1196 goto first_unseen;
1199 MsgNo flagged, first_unseen;
1201 flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
1202 THREADING() ? 0 : FSF_SKIP_CHID);
1203 first_unseen = (sp_first_unseen(m)
1204 && mn_get_sort(ps_global->msgmap) == SortArrival
1205 && !mn_get_revsort(ps_global->msgmap)
1206 && !get_lflag(ps_global->mail_stream, NULL,
1207 sp_first_unseen(m), MN_EXLD)
1208 && (n = mn_raw2m(ps_global->msgmap,
1209 sp_first_unseen(m))))
1211 : first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
1212 THREADING() ? 0 : FSF_SKIP_CHID);
1213 mn_set_cur(ps_global->msgmap,
1214 (MsgNo) MIN((int) flagged, (int) first_unseen));
1218 break;
1220 case IS_FIRST_IMPORTANT_OR_RECENT:
1222 if(IS_NEWS(ps_global->mail_stream))
1223 goto first_recent;
1226 MsgNo flagged, first_recent;
1228 flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
1229 THREADING() ? 0 : FSF_SKIP_CHID);
1230 first_recent = first_sorted_flagged(F_RECENT | F_UNSEEN
1231 | F_UNDEL,
1232 m, pc,
1233 THREADING() ? 0 : FSF_SKIP_CHID);
1234 mn_set_cur(ps_global->msgmap,
1235 (MsgNo) MIN((int) flagged, (int) first_recent));
1238 break;
1240 case IS_FIRST:
1241 mn_set_cur(ps_global->msgmap,
1242 first_sorted_flagged(F_UNDEL, m, pc,
1243 THREADING() ? 0 : FSF_SKIP_CHID));
1244 break;
1246 case IS_LAST:
1247 mn_set_cur(ps_global->msgmap,
1248 first_sorted_flagged(F_UNDEL, m, pc,
1249 FSF_LAST | (THREADING() ? 0 : FSF_SKIP_CHID)));
1250 break;
1252 default:
1253 alpine_panic("Unexpected incoming startup case");
1254 break;
1258 else if(IS_NEWS(ps_global->mail_stream)){
1260 * This will go to two different places depending on the FAKE_NEW
1261 * feature (see pine_mail_open).
1263 mn_set_cur(ps_global->msgmap,
1264 first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
1265 THREADING() ? 0 : FSF_SKIP_CHID));
1267 else{
1268 mn_set_cur(ps_global->msgmap,
1269 mn_get_revsort(ps_global->msgmap)
1270 ? 1L
1271 : mn_get_total(ps_global->msgmap));
1274 adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
1276 else if(!(rflags & SP_MATCH)){
1277 mn_set_cur(ps_global->msgmap, -1L);
1280 if(ps_global->mail_stream)
1281 sp_set_first_unseen(ps_global->mail_stream, 0L);
1283 return(1);
1287 /*----------------------------------------------------------------------
1288 Expand a folder name, taking account of the folders_dir and
1289 any home directory reference
1291 Args: filename -- The name of the file that is the folder
1293 Result: The folder name is expanded in place.
1294 Returns 0 and queues status message if unsuccessful.
1295 Input string is overwritten with expanded name.
1296 Returns 1 if successful.
1298 ----*/
1300 expand_foldername(char *filename, size_t len)
1302 char temp_filename[MAXPATH+1];
1304 dprint((5, "=== expand_foldername called (%s) ===\n",
1305 filename ? filename : "?"));
1308 * We used to check for valid filename chars here if "filename"
1309 * didn't refer to a remote mailbox. This has been rethought
1312 strncpy(temp_filename, filename, sizeof(temp_filename)-1);
1313 temp_filename[sizeof(temp_filename)-1] = '\0';
1314 if(strucmp(temp_filename, "inbox") == 0) {
1315 strncpy(filename, ps_global->VAR_INBOX_PATH == NULL ? "inbox" :
1316 ps_global->VAR_INBOX_PATH, len-1);
1317 filename[len-1] = '\0';
1318 } else if(temp_filename[0] == '{') {
1319 strncpy(filename, temp_filename, len-1);
1320 filename[len-1] = '\0';
1321 } else if(ps_global->restricted && filename_is_restricted(temp_filename)){
1322 q_status_message(SM_ORDER, 0, 3, "Can only open local folders");
1323 return(0);
1324 } else if(temp_filename[0] == '*') {
1325 strncpy(filename, temp_filename, len-1);
1326 filename[len-1] = '\0';
1327 } else if(ps_global->VAR_OPER_DIR && filename_parent_ref(temp_filename)){
1328 q_status_message(SM_ORDER, 0, 3,
1329 "\"..\" not allowed in folder name");
1330 return(0);
1331 } else if (is_homedir_path(temp_filename)){
1332 if(fnexpand(temp_filename, sizeof(temp_filename)) == NULL) {
1333 q_status_message1(SM_ORDER, 3, 3, "Error expanding folder \"%s\"", temp_filename);
1334 return(0);
1336 strncpy(filename, temp_filename, len-1);
1337 filename[len-1] = '\0';
1338 } else if(F_ON(F_USE_CURRENT_DIR, ps_global) || is_absolute_path(temp_filename)){
1339 strncpy(filename, temp_filename, len-1);
1340 filename[len-1] = '\0';
1341 } else if(ps_global->VAR_OPER_DIR){
1342 build_path(filename, ps_global->VAR_OPER_DIR, temp_filename, len);
1343 } else {
1344 build_path(filename,
1345 #ifdef IS_WINDOWS
1346 ps_global->folders_dir,
1347 #else /* UNIX */
1348 ps_global->home_dir,
1349 #endif /* UNIX */
1350 temp_filename, len);
1353 dprint((5, "returning \"%s\"\n", filename ? filename : "?"));
1354 return(1);
1359 /*----------------------------------------------------------------------
1360 Expunge (if confirmed) and close a mail stream
1362 Args: stream -- The MAILSTREAM * to close
1363 final_msg -- If non-null, this should be set to point to a
1364 message to print out in the caller, it is allocated
1365 here and freed by the caller.
1367 Result: Mail box is expunged and closed. A message is displayed to
1368 say what happened
1369 ----*/
1370 void
1371 expunge_and_close(MAILSTREAM *stream, char **final_msg, long unsigned int flags)
1373 long i, delete_count, seen_not_del;
1374 char buff1[MAX_SCREEN_COLS+1], *moved_msg = NULL,
1375 buff2[MAX_SCREEN_COLS+1], *folder;
1376 CONTEXT_S *context;
1377 struct variable *vars = ps_global->vars;
1378 int ret, expunge = FALSE, no_close = 0;
1379 char ing[4];
1381 no_close = (flags & EC_NO_CLOSE);
1383 if(!(stream && sp_flagged(stream, SP_LOCKED)))
1384 stream = NULL;
1386 /* check for dead stream */
1387 if(stream && sp_dead_stream(stream)){
1388 pine_mail_actually_close(stream);
1389 stream = NULL;
1392 if(stream != NULL){
1393 context = sp_context(stream);
1394 folder = STREAMNAME(stream);
1396 dprint((2, "expunge_and_close: \"%s\"%s\n",
1397 folder, no_close ? " (NO_CLOSE bit set)" : ""));
1399 update_folder_unseen_by_stream(stream, UFU_NONE);
1401 if(final_msg)
1402 strncpy(ing, "ed", sizeof(ing));
1403 else
1404 strncpy(ing, "ing", sizeof(ing));
1406 ing[sizeof(ing)-1] = '\0';
1408 buff1[0] = '\0';
1409 buff2[0] = '\0';
1411 if(!stream->rdonly){
1413 if(pith_opt_begin_closing)
1414 (*pith_opt_begin_closing)(flags, folder);
1416 mail_expunge_prefilter(stream, MI_CLOSING);
1419 * Be sure to expunge any excluded (filtered) msgs
1420 * Do it here so they're not copied into read/archived
1421 * folders *AND* to be sure we don't refilter them
1422 * next time the folder's opened.
1424 for(i = 1L; i <= stream->nmsgs; i++)
1425 if(get_lflag(stream, NULL, i, MN_EXLD)){ /* if there are any */
1426 delete_filtered_msgs(stream); /* delete them all */
1427 expunge = TRUE;
1428 break;
1431 /* Save read messages? */
1432 if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0]
1433 && sp_flagged(stream, SP_INBOX)
1434 && (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL))){
1436 if(F_ON(F_AUTO_READ_MSGS,ps_global)
1437 || (pith_opt_read_msg_prompt
1438 && (*pith_opt_read_msg_prompt)(seen_not_del, VAR_READ_MESSAGE_FOLDER)))
1439 /* move inbox's read messages */
1440 moved_msg = move_read_msgs(stream, VAR_READ_MESSAGE_FOLDER,
1441 buff1, sizeof(buff1), -1L);
1443 else if(VAR_ARCHIVED_FOLDERS)
1444 moved_msg = move_read_incoming(stream, context, folder,
1445 VAR_ARCHIVED_FOLDERS,
1446 buff1, sizeof(buff1));
1449 * We need the count_flagged to be executed not only to set
1450 * delete_count, but also to set the searched bits in all of
1451 * the deleted messages. The searched bit is used in the monkey
1452 * business section below which undeletes deleted messages
1453 * before expunging. It determines which messages are deleted
1454 * by examining the searched bit, which had better be set or not
1455 * based on this count_flagged call rather than some random
1456 * search that happened earlier.
1458 delete_count = count_flagged(stream, F_DEL);
1459 if(F_ON(F_EXPUNGE_MANUALLY,ps_global))
1460 delete_count = 0L;
1462 ret = 'n';
1463 if(!ps_global->noexpunge_on_close && delete_count){
1465 if(F_ON(F_FULL_AUTO_EXPUNGE,ps_global)
1466 || (F_ON(F_AUTO_EXPUNGE, ps_global)
1467 && ((!strucmp(folder,ps_global->inbox_name))
1468 || (context && (context->use & CNTXT_INCMNG)))
1469 && context_isambig(folder))){
1470 ret = 'y';
1472 else if(pith_opt_expunge_prompt){
1473 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1474 ret = (*pith_opt_expunge_prompt)(stream, pretty_fn((char *)fname), delete_count);
1475 if(fname) fs_give((void **) &fname);
1478 /* get this message back in queue */
1479 if(moved_msg)
1480 q_status_message(SM_ORDER,
1481 F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);
1483 if(ret == 'y'){
1484 long filtered;
1485 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1487 filtered = any_lflagged(sp_msgmap(stream), MN_EXLD);
1489 snprintf(buff2, sizeof(buff2),
1490 "%s%s%s%.30s%s%s %s message%s and remov%s %s.",
1491 no_close ? "" : "Clos",
1492 no_close ? "" : ing,
1493 no_close ? "" : " \"",
1494 no_close ? "" : pretty_fn((char *)fname),
1495 no_close ? "" : "\". ",
1496 final_msg ? "Kept" : "Keeping",
1497 comatose(stream->nmsgs - filtered - delete_count),
1498 plural(stream->nmsgs - filtered - delete_count),
1499 ing,
1500 long2string(delete_count));
1501 if(fname) fs_give((void **)&fname);
1502 if(final_msg)
1503 *final_msg = cpystr(buff2);
1504 else
1505 q_status_message(SM_ORDER,
1506 no_close ? 1 :
1507 (F_ON(F_AUTO_EXPUNGE,ps_global)
1508 || F_ON(F_FULL_AUTO_EXPUNGE,ps_global))
1509 ? 0 : 3,
1510 5, buff2);
1512 flush_status_messages(1);
1513 ps_global->mm_log_error = 0;
1514 ps_global->expunge_in_progress = 1;
1515 mail_expunge(stream);
1516 ps_global->expunge_in_progress = 0;
1517 if(ps_global->mm_log_error && final_msg && *final_msg){
1518 fs_give((void **)final_msg);
1519 *final_msg = NULL;
1524 if(ret != 'y'){
1525 if(!ps_global->noexpunge_on_close && expunge){
1526 MESSAGECACHE *mc;
1527 char *seq;
1528 int expbits;
1531 * filtered message monkey business.
1532 * The Plan:
1533 * 1) light sequence bits for legit deleted msgs
1534 * and store marker in local extension
1535 * 2) clear their deleted flag
1536 * 3) perform expunge to removed filtered msgs
1537 * 4) restore deleted flags for legit msgs
1538 * based on local extension bit
1540 for(i = 1L; i <= stream->nmsgs; i++)
1541 if(!get_lflag(stream, NULL, i, MN_EXLD)
1542 && (((mc = mail_elt(stream, i)) && mc->valid && mc->deleted)
1543 || (mc && !mc->valid && mc->searched))){
1544 mc->sequence = 1;
1545 expbits = MSG_EX_DELETE;
1546 msgno_exceptions(stream, i, "0", &expbits, TRUE);
1548 else if((mc = mail_elt(stream, i)) != NULL)
1549 mc->sequence = 0;
1551 if((seq = build_sequence(stream, NULL, NULL)) != NULL){
1552 mail_flag(stream, seq, "\\DELETED", ST_SILENT);
1553 fs_give((void **) &seq);
1556 ps_global->mm_log_error = 0;
1557 ps_global->expunge_in_progress = 1;
1558 mail_expunge(stream);
1559 ps_global->expunge_in_progress = 0;
1561 for(i = 1L; i <= stream->nmsgs; i++)
1562 if((mc = mail_elt(stream, i)) != NULL)
1563 mc->sequence
1564 = (msgno_exceptions(stream, i, "0", &expbits, FALSE)
1565 && (expbits & MSG_EX_DELETE));
1567 if((seq = build_sequence(stream, NULL, NULL)) != NULL){
1568 mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
1569 fs_give((void **) &seq);
1573 if(!no_close){ /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1574 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1575 if(stream->nmsgs){
1576 snprintf(buff2, sizeof(buff2),
1577 "Clos%s folder \"%.*s\". %s%s%s message%s.",
1578 ing,
1579 MAX_SCREEN_COLS+1-50, pretty_fn((char *) fname),
1580 final_msg ? "Kept" : "Keeping",
1581 (stream->nmsgs == 1L) ? " single" : " all ",
1582 (stream->nmsgs > 1L)
1583 ? comatose(stream->nmsgs) : "",
1584 plural(stream->nmsgs));
1586 else{ /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1587 snprintf(buff2, sizeof(buff2), "Clos%s empty folder \"%.*s\"",
1588 ing, MAX_SCREEN_COLS+1-50, pretty_fn((char *) fname));
1590 if(fname) fs_give((void **)&fname);
1592 if(final_msg)
1593 *final_msg = cpystr(buff2);
1594 else
1595 q_status_message(SM_ORDER, 0, 3, buff2);
1599 else{
1600 if(IS_NEWS(stream)){
1602 * Mark the filtered messages deleted so they aren't
1603 * filtered next time.
1605 for(i = 1L; i <= stream->nmsgs; i++){
1606 int exbits;
1607 if(msgno_exceptions(stream, i, "0" , &exbits, FALSE)
1608 && (exbits & MSG_EX_FILTERED)){
1609 delete_filtered_msgs(stream);
1610 break;
1613 /* first, look to archive read messages */
1614 if((moved_msg = move_read_incoming(stream, context, folder,
1615 VAR_ARCHIVED_FOLDERS,
1616 buff1, sizeof(buff1))) != NULL)
1617 q_status_message(SM_ORDER,
1618 F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);
1619 /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1620 snprintf(buff2, sizeof(buff2), "Clos%s news group \"%.*s\"",
1621 ing, MAX_SCREEN_COLS+1-50, pretty_fn(folder));
1623 if(F_ON(F_NEWS_CATCHUP, ps_global)){
1624 MESSAGECACHE *mc;
1626 /* count visible messages */
1627 (void) count_flagged(stream, F_DEL);
1628 for(i = 1L, delete_count = 0L; i <= stream->nmsgs; i++)
1629 if(!(get_lflag(stream, NULL, i, MN_EXLD)
1630 || ((mc = mail_elt(stream, i)) && mc->valid
1631 && mc->deleted)
1632 || (mc && !mc->valid && mc->searched)))
1633 delete_count++;
1635 if(delete_count && pith_opt_expunge_prompt){
1636 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1637 ret = (*pith_opt_expunge_prompt)(stream, pretty_fn((char *) fname), delete_count);
1638 if(fname) fs_give((void **)&fname);
1639 if(ret == 'y'){
1640 char seq[64];
1642 snprintf(seq, sizeof(seq), "1:%ld", stream->nmsgs);
1643 mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
1648 if(F_ON(F_NEWS_CROSS_DELETE, ps_global))
1649 cross_delete_crossposts(stream);
1651 else{ /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1652 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1653 snprintf(buff2, sizeof(buff2),
1654 "Clos%s read-only folder \"%.*s\". No changes to save",
1655 ing, MAX_SCREEN_COLS+1-60, pretty_fn((char *) fname));
1656 if(fname) fs_give((void **)&fname);
1659 if(final_msg)
1660 *final_msg = cpystr(buff2);
1661 else
1662 q_status_message(SM_ORDER, 0, 2, buff2);
1666 * Make darn sure any mm_log fallout caused above get's seen...
1668 if(!no_close){
1669 flush_status_messages(1);
1670 pine_mail_close(stream);
1676 void
1677 agg_select_all(MAILSTREAM *stream, MSGNO_S *msgmap, long int *diff, int on)
1679 long i;
1680 int hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
1682 for(i = 1L; i <= mn_get_total(msgmap); i++){
1683 if(on){ /* mark 'em all */
1684 set_lflag(stream, msgmap, i, MN_SLCT, 1);
1686 else { /* unmark 'em all */
1687 if(get_lflag(stream, msgmap, i, MN_SLCT)){
1688 if(diff)
1689 (*diff)++;
1691 set_lflag(stream, msgmap, i, MN_SLCT, 0);
1693 else if(hidden)
1694 set_lflag(stream, msgmap, i, MN_HIDE, 0);
1700 /*----------------------------------------------------------------------
1701 Move all read messages from srcfldr to dstfldr
1703 Args: stream -- stream to usr
1704 dstfldr -- folder to receive moved messages
1705 buf -- place to write success message
1707 Returns: success message or NULL for failure
1708 ----*/
1709 char *
1710 move_read_msgs(MAILSTREAM *stream, char *dstfldr, char *buf, size_t buflen, long int searched)
1712 long i, raw;
1713 int we_cancel = 0;
1714 MSGNO_S *msgmap = NULL;
1715 CONTEXT_S *save_context = NULL;
1716 char *bufp = NULL;
1717 MESSAGECACHE *mc;
1719 if(!is_absolute_path(dstfldr)
1720 && !(save_context = default_save_context(ps_global->context_list)))
1721 save_context = ps_global->context_list;
1724 * Use the "searched" bit to select the set of messages
1725 * we want to save. If searched is non-neg, the message
1726 * cache already has the necessary "searched" bits set.
1728 if(searched < 0L)
1729 searched = count_flagged(stream, F_SEEN | F_UNDEL);
1731 if(searched){
1733 * We're going to be messing with SLCT flags in order
1734 * to do our work. If this stream is a StayOpen stream
1735 * we want to restore those flags after we're done
1736 * using them. So copy them into STMP so we can put them
1737 * back below.
1739 msgmap = sp_msgmap(stream);
1740 if(sp_flagged(stream, SP_PERMLOCKED))
1741 copy_lflags(stream, msgmap, MN_SLCT, MN_STMP);
1743 set_lflags(stream, msgmap, MN_SLCT, 0);
1745 /* select search results */
1746 for(i = 1L; i <= mn_get_total(msgmap); i++)
1747 if((raw = mn_m2raw(msgmap, i)) > 0L && stream
1748 && raw <= stream->nmsgs
1749 && (mc = mail_elt(stream,raw))
1750 && ((mc->valid && mc->seen && !mc->deleted)
1751 || (!mc->valid && mc->searched)))
1752 set_lflag(stream, msgmap, i, MN_SLCT, 1);
1754 pseudo_selected(stream, msgmap);
1755 snprintf(buf, buflen, "Moving %s read message%s to \"%s\"",
1756 comatose(searched), plural(searched), dstfldr);
1757 we_cancel = busy_cue(buf, NULL, 0);
1758 if(save(ps_global, stream, save_context, dstfldr, msgmap,
1759 SV_DELETE | SV_FIX_DELS | SV_INBOXWOCNTXT) == searched)
1760 strncpy(bufp = buf + 1, "Moved", MIN(5,buflen)); /* change Moving to Moved */
1762 buf[buflen-1] = '\0';
1763 if(we_cancel)
1764 cancel_busy_cue(bufp ? 0 : -1);
1766 if(sp_flagged(stream, SP_PERMLOCKED)){
1767 restore_selected(msgmap);
1768 copy_lflags(stream, msgmap, MN_STMP, MN_SLCT);
1772 return(bufp);
1776 /*----------------------------------------------------------------------
1777 Move read messages from folder if listed in archive
1779 Args:
1781 ----*/
1782 char *
1783 move_read_incoming(MAILSTREAM *stream, CONTEXT_S *context, char *folder,
1784 char **archive, char *buf, size_t buflen)
1786 char *s, *d, *f = folder;
1787 long seen_undel;
1789 if(buf && buflen > 0)
1790 buf[0] = '\0';
1792 if(archive && !sp_flagged(stream, SP_INBOX)
1793 && context && (context->use & CNTXT_INCMNG)
1794 && ((context_isambig(folder)
1795 && folder_is_nick(folder, FOLDERS(context), 0))
1796 || folder_index(folder, context, FI_FOLDER) > 0)
1797 && (seen_undel = count_flagged(stream, F_SEEN | F_UNDEL))){
1799 for(; f && *archive; archive++){
1800 char *p;
1802 get_pair(*archive, &s, &d, 1, 0);
1803 if(s && d
1804 && (!strcmp(s, folder)
1805 || (context_isambig(folder)
1806 && (p = folder_is_nick(folder, FOLDERS(context), 0))
1807 && !strcmp(s, p)))){
1808 if(F_ON(F_AUTO_READ_MSGS,ps_global)
1809 || (pith_opt_read_msg_prompt
1810 && (*pith_opt_read_msg_prompt)(seen_undel, d)))
1811 buf = move_read_msgs(stream, d, buf, buflen, seen_undel);
1813 f = NULL; /* bust out after cleaning up */
1816 fs_give((void **)&s);
1817 fs_give((void **)&d);
1821 return((buf && *buf) ? buf : NULL);
1825 /*----------------------------------------------------------------------
1826 Delete all references to a deleted news posting
1829 ---*/
1830 void
1831 cross_delete_crossposts(MAILSTREAM *stream)
1833 if(count_flagged(stream, F_DEL)){
1834 static char *fields[] = {"Xref", NULL};
1835 MAILSTREAM *tstream;
1836 CONTEXT_S *fake_context;
1837 char *xref, *p, *group, *uidp,
1838 *newgrp, newfolder[MAILTMPLEN];
1839 long i, hostlatch = 0L;
1840 imapuid_t uid;
1841 int we_cancel = 0;
1842 MESSAGECACHE *mc;
1844 strncpy(newfolder, stream->mailbox, sizeof(newfolder));
1845 newfolder[sizeof(newfolder)-1] = '\0';
1846 if(!(newgrp = strstr(newfolder, "#news.")))
1847 return; /* weird mailbox */
1849 newgrp += 6;
1851 we_cancel = busy_cue("Busy deleting crosspostings", NULL, 1);
1853 /* build subscribed list */
1854 strncpy(newgrp, "[]", sizeof(newfolder)-(newgrp-newfolder));
1855 newfolder[sizeof(newfolder)-1] = '\0';
1856 fake_context = new_context(newfolder, 0);
1857 build_folder_list(NULL, fake_context, "*", NULL, BFL_LSUB);
1859 for(i = 1L; i <= stream->nmsgs; i++)
1860 if(!get_lflag(stream, NULL, i, MN_EXLD)
1861 && (mc = mail_elt(stream, i)) && mc->deleted){
1863 if((xref = pine_fetchheader_lines(stream, i, NULL, fields)) != NULL){
1864 if((p = strstr(xref, ": ")) != NULL){
1865 p += 2;
1866 hostlatch = 0L;
1867 while(*p){
1868 group = p;
1869 uidp = NULL;
1871 /* get server */
1872 while(*++p && !isspace((unsigned char) *p))
1873 if(*p == ':'){
1874 *p = '\0';
1875 uidp = p + 1;
1878 /* tie off uid/host */
1879 if(*p)
1880 *p++ = '\0';
1882 if(uidp){
1884 * For the nonce, we're only deleting valid
1885 * uid's from outside the current newsgroup
1886 * and inside only subscribed newsgroups
1888 if(strcmp(group, stream->mailbox
1889 + (newgrp - newfolder))
1890 && folder_index(group, fake_context,
1891 FI_FOLDER) >= 0){
1892 if((uid = strtoul(uidp, NULL, 10)) != 0L){
1893 strncpy(newgrp, group, sizeof(newfolder)-(newgrp-newfolder));
1894 newfolder[sizeof(newfolder)-1] = '\0';
1895 if((tstream = pine_mail_open(NULL,
1896 newfolder,
1897 SP_USEPOOL,
1898 NULL)) != NULL){
1899 mail_flag(tstream, ulong2string(uid),
1900 "\\DELETED",
1901 ST_SET | ST_UID);
1902 pine_mail_close(tstream);
1905 else
1906 break; /* bogus uid */
1909 else if(!hostlatch++){
1910 char *p, *q;
1912 if(stream->mailbox[0] == '{'
1913 && !((p = strpbrk(stream->mailbox+1, "}:/"))
1914 && !struncmp(stream->mailbox + 1,
1915 q = canonical_name(group),
1916 p - (stream->mailbox + 1))
1917 && q[p - (stream->mailbox + 1)] == '\0'))
1918 break; /* different server? */
1920 else
1921 break; /* bogus field! */
1925 fs_give((void **) &xref);
1929 free_context(&fake_context);
1931 if(we_cancel)
1932 cancel_busy_cue(0);
1938 * Original version from Eduardo Chappa.
1940 * Returns a string describing the number of new/unseen messages
1941 * for use in the status line. Can return NULL. Caller must free the memory.
1943 char *
1944 new_messages_string(MAILSTREAM *stream)
1946 char message[80] = {'\0'};
1947 long new = 0L, uns = 0L;
1948 int i, imapstatus = 0;
1950 for (i = 0; ps_global->index_disp_format[i].ctype != iNothing
1951 && ps_global->index_disp_format[i].ctype != iIStatus
1952 && ps_global->index_disp_format[i].ctype != iSIStatus; i++)
1955 imapstatus = ps_global->index_disp_format[i].ctype == iIStatus
1956 || ps_global->index_disp_format[i].ctype == iSIStatus;
1958 get_new_message_count(stream, imapstatus, &new, &uns);
1960 if(imapstatus)
1961 snprintf(message, sizeof(message), " - %s%s%s%s%s%s%s",
1962 uns != 0L ? comatose((long) new) : "",
1963 uns != 0L ? " " : "",
1964 uns != 0L ? _("recent") : "",
1965 uns > 0L ? ", " : "",
1966 uns != -1L ? comatose((long) uns) : "",
1967 uns != -1L ? " " : "",
1968 uns != -1L ? _("unseen") : "");
1969 else if(!imapstatus && new > 0L)
1970 snprintf(message, sizeof(message), " - %s %s",
1971 comatose((long) new), _("new"));
1973 return(*message ? cpystr(message) : NULL);
1977 void
1978 get_new_message_count(MAILSTREAM *stream, int imapstatus,
1979 long *new, long *unseen)
1981 if(new)
1982 *new = 0L;
1984 if(unseen)
1985 *unseen = 0L;
1987 if(imapstatus){
1988 if(new)
1989 *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);
1991 if(!IS_NEWS(stream)){
1992 if(unseen)
1993 *unseen = count_flagged(stream, F_UNSEEN | F_UNDEL);
1995 else if(unseen)
1996 *unseen = -1L;
1998 else{
1999 if(IS_NEWS(stream)){
2000 if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
2001 if(new)
2002 *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);
2005 else{
2006 if(new)
2007 *new = count_flagged(stream, F_UNSEEN | F_UNDEL | F_UNANS);
2013 /*----------------------------------------------------------------------
2014 ZOOM the message index (set any and all necessary hidden flag bits)
2016 Args: state -- usual pine state
2017 msgmap -- usual message mapping
2018 Returns: number of messages zoomed in on
2020 ----*/
2021 long
2022 zoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int onflag)
2024 long i, count = 0L, first = 0L, msgno;
2025 PINETHRD_S *thrd = NULL, *topthrd = NULL, *nthrd;
2027 if(any_lflagged(msgmap, onflag)){
2029 if(THREADING() && sp_viewing_a_thread(stream)){
2030 /* get top of current thread */
2031 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
2032 if(thrd && thrd->top)
2033 topthrd = fetch_thread(stream, thrd->top);
2036 for(i = 1L; i <= mn_get_total(msgmap); i++){
2037 if(!get_lflag(stream, msgmap, i, onflag)){
2038 set_lflag(stream, msgmap, i, MN_HIDE, 1);
2040 else{
2043 * Because subject lines depend on whether or not
2044 * other parts of the thread above us are visible or not.
2046 if(THREADING() && !THRD_INDX()
2047 && ps_global->thread_disp_style == THREAD_MUTTLIKE)
2048 clear_index_cache_ent(stream, i, 0);
2051 * If a selected message is hidden beneath a collapsed
2052 * thread (not beneath a thread index line, but a collapsed
2053 * thread or subthread) then we make it visible. The user
2054 * should be able to see the selected messages when they
2055 * Zoom. We could get a bit fancier and re-collapse the
2056 * thread when the user unzooms, but we don't do that
2057 * for now.
2059 if(THREADING() && !THRD_INDX()
2060 && get_lflag(stream, msgmap, i, MN_CHID)){
2063 * What we need to do is to unhide this message and
2064 * uncollapse any parent above us.
2065 * Also, when we uncollapse a parent, we need to
2066 * trace back down the tree and unhide until we get
2067 * to a collapse point or the end. That's what
2068 * set_thread_subtree does.
2071 thrd = fetch_thread(stream, mn_m2raw(msgmap, i));
2073 if(thrd && thrd->parent)
2074 thrd = fetch_thread(stream, thrd->parent);
2075 else
2076 thrd = NULL;
2078 /* unhide and uncollapse its parents */
2079 while(thrd){
2080 /* if this parent is collapsed */
2081 if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
2082 /* uncollapse this parent and unhide its subtree */
2083 msgno = mn_raw2m(msgmap, thrd->rawno);
2084 if(msgno > 0L && msgno <= mn_get_total(msgmap)){
2085 set_lflag(stream, msgmap, msgno,
2086 MN_COLL | MN_CHID, 0);
2087 if(thrd->next &&
2088 (nthrd = fetch_thread(stream, thrd->next)))
2089 set_thread_subtree(stream, nthrd, msgmap,
2090 0, MN_CHID);
2093 /* collapse symbol will be wrong */
2094 clear_index_cache_ent(stream, msgno, 0);
2098 * Continue up tree to next parent looking for
2099 * more collapse points.
2101 if(thrd->parent)
2102 thrd = fetch_thread(stream, thrd->parent);
2103 else
2104 thrd = NULL;
2108 count++;
2109 if(!first){
2110 if(THRD_INDX()){
2111 /* find msgno of top of thread for msg i */
2112 if((thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
2113 && thrd->top)
2114 first = mn_raw2m(msgmap, thrd->top);
2116 else if(THREADING() && sp_viewing_a_thread(stream)){
2117 /* want first selected message in this thread */
2118 if(topthrd
2119 && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
2120 && thrd->top
2121 && topthrd->rawno == thrd->top)
2122 first = i;
2124 else
2125 first = i;
2130 if(THRD_INDX()){
2131 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
2132 if(count_lflags_in_thread(stream, thrd, msgmap, onflag) == 0)
2133 mn_set_cur(msgmap, first);
2135 else if((THREADING() && sp_viewing_a_thread(stream))
2136 || !get_lflag(stream, msgmap, mn_get_cur(msgmap), onflag)){
2137 if(!first){
2138 int flags = 0;
2141 * Nothing was selected in the thread we were in, so
2142 * drop back to the Thread Index instead. Set the current
2143 * thread to the first one that has a selection in it.
2146 unview_thread(state, stream, msgmap);
2148 i = next_sorted_flagged(F_UNDEL, stream, 1L, &flags);
2150 if(flags & NSF_FLAG_MATCH
2151 && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
2152 && thrd->top)
2153 first = mn_raw2m(msgmap, thrd->top);
2154 else
2155 first = 1L; /* can't happen */
2157 mn_set_cur(msgmap, first);
2159 else{
2160 if(msgline_hidden(stream, msgmap, mn_get_cur(msgmap), 0))
2161 mn_set_cur(msgmap, first);
2166 return(count);
2171 /*----------------------------------------------------------------------
2172 UnZOOM the message index (clear any and all hidden flag bits)
2174 Args: state -- usual pine state
2175 msgmap -- usual message mapping
2176 Returns: 1 if hidden bits to clear and they were, 0 if none to clear
2178 ----*/
2180 unzoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
2182 register long i;
2184 if(!any_lflagged(msgmap, MN_HIDE))
2185 return(0);
2187 for(i = 1L; i <= mn_get_total(msgmap); i++)
2188 set_lflag(stream, msgmap, i, MN_HIDE, 0);
2190 return(1);
2195 agg_text_select(MAILSTREAM *stream, MSGNO_S *msgmap, char type, char *namehdr,
2196 int not, int check_for_my_addresses,
2197 char *sstring, char *charset, SEARCHSET **limitsrch)
2199 int old_imap, we_cancel;
2200 int me_with_regex = 0;
2201 long searchflags;
2202 SEARCHPGM *srchpgm, *pgm, *secondpgm = NULL, *thirdpgm = NULL;
2203 SEARCHPGM *mepgm = NULL;
2205 if(!stream)
2206 return(1);
2208 old_imap = (is_imap_stream(stream) && !modern_imap_stream(stream));
2211 * Special case code for matching one of the user's addresses.
2213 if(check_for_my_addresses){
2214 char **t, *alt;
2216 if(F_OFF(F_DISABLE_REGEX, ps_global)){
2217 for(t = ps_global->VAR_ALT_ADDRS; !me_with_regex && t && t[0] && t[0][0]; t++){
2218 alt = (*t);
2219 if(contains_regex_special_chars(alt))
2220 me_with_regex++;
2225 * In this case we can't use search because it doesn't support
2226 * regex. So we have to manually do the whole thing ourselves.
2227 * The searching is done in the subroutine and the searched bits
2228 * will be set on return.
2230 if(me_with_regex){
2231 search_for_our_regex_addresses(stream, type, not, limitsrch ? *limitsrch : NULL);
2232 return(0);
2234 else{
2235 PATGRP_S *patgrp = NULL;
2236 PATTERN_S *p = NULL;
2237 PATTERN_S *pattern = NULL, **nextp;
2238 char buf[1000];
2241 * We're going to use the pattern matching machinery to generate
2242 * a search program. We build a pattern whose only purpose is
2243 * to generate the program.
2245 nextp = &pattern;
2247 /* add standard me addresses to list */
2248 if(ps_global->VAR_USER_ID){
2249 if(ps_global->userdomain && ps_global->userdomain[0]){
2250 p = (PATTERN_S *) fs_get(sizeof(*p));
2251 memset((void *) p, 0, sizeof(*p));
2252 snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
2253 ps_global->userdomain);
2254 p->substring = cpystr(buf);
2255 *nextp = p;
2256 nextp = &p->next;
2259 if(!ps_global->userdomain && ps_global->localdomain && ps_global->localdomain[0]){
2260 p = (PATTERN_S *) fs_get(sizeof(*p));
2261 memset((void *) p, 0, sizeof(*p));
2262 snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
2263 ps_global->localdomain);
2264 p->substring = cpystr(buf);
2265 *nextp = p;
2266 nextp = &p->next;
2269 if(!ps_global->userdomain && ps_global->hostname && ps_global->hostname[0]){
2270 p = (PATTERN_S *) fs_get(sizeof(*p));
2271 memset((void *) p, 0, sizeof(*p));
2272 snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
2273 ps_global->hostname);
2274 p->substring = cpystr(buf);
2275 *nextp = p;
2276 nextp = &p->next;
2280 /* add user's alternate addresses */
2281 for(t = ps_global->VAR_ALT_ADDRS; t && t[0] && t[0][0]; t++){
2282 alt = (*t);
2283 if(alt && alt[0]){
2284 p = (PATTERN_S *) fs_get(sizeof(*p));
2285 memset((void *) p, 0, sizeof(*p));
2286 p->substring = cpystr(alt);
2287 *nextp = p;
2288 nextp = &p->next;
2292 patgrp = (PATGRP_S *) fs_get(sizeof(*patgrp));
2293 memset((void *) patgrp, 0, sizeof(*patgrp));
2295 switch(type){
2296 case 'r' :
2297 patgrp->recip = pattern;
2298 break;
2299 case 'p' :
2300 patgrp->partic = pattern;
2301 break;
2302 case 'f' :
2303 patgrp->from = pattern;
2304 break;
2305 case 'c' :
2306 patgrp->cc = pattern;
2307 break;
2308 case 't' :
2309 patgrp->to = pattern;
2310 break;
2311 default :
2312 q_status_message(SM_ORDER, 3, 3, "Unhandled case in agg_text_select");
2313 break;
2316 mepgm = match_pattern_srchpgm(patgrp, stream, NULL);
2318 free_patgrp(&patgrp);
2322 if(mepgm){
2323 if(not && !old_imap){
2324 srchpgm = mail_newsearchpgm();
2325 srchpgm->not = mail_newsearchpgmlist();
2326 srchpgm->not->pgm = mepgm;
2328 else{
2329 srchpgm = mepgm;
2333 else{
2334 /* create a search program and fill it in */
2335 srchpgm = pgm = mail_newsearchpgm();
2336 if(not && !old_imap){
2337 srchpgm->not = mail_newsearchpgmlist();
2338 srchpgm->not->pgm = mail_newsearchpgm();
2339 pgm = srchpgm->not->pgm;
2343 if(!mepgm)
2344 switch(type){
2345 case 'g' : /* X-GM-EXT-1 */
2346 pgm->x_gm_ext1 = mail_newstringlist();
2347 pgm->x_gm_ext1->text.data = (unsigned char *) cpystr(namehdr);
2348 pgm->x_gm_ext1->text.size = strlen(namehdr);
2349 break;
2351 case 'h' : /* Any header */
2352 pgm->header = mail_newsearchheader (namehdr, sstring);
2353 break;
2355 case 'r' : /* TO or CC */
2356 if(old_imap){
2357 /* No OR on old servers */
2358 pgm->to = mail_newstringlist();
2359 pgm->to->text.data = (unsigned char *) cpystr(sstring);
2360 pgm->to->text.size = strlen(sstring);
2361 secondpgm = mail_newsearchpgm();
2362 secondpgm->cc = mail_newstringlist();
2363 secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
2364 secondpgm->cc->text.size = strlen(sstring);
2366 else{
2367 pgm->or = mail_newsearchor();
2368 pgm->or->first->to = mail_newstringlist();
2369 pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
2370 pgm->or->first->to->text.size = strlen(sstring);
2371 pgm->or->second->cc = mail_newstringlist();
2372 pgm->or->second->cc->text.data = (unsigned char *) cpystr(sstring);
2373 pgm->or->second->cc->text.size = strlen(sstring);
2376 break;
2378 case 'p' : /* TO or CC or FROM */
2379 if(old_imap){
2380 /* No OR on old servers */
2381 pgm->to = mail_newstringlist();
2382 pgm->to->text.data = (unsigned char *) cpystr(sstring);
2383 pgm->to->text.size = strlen(sstring);
2384 secondpgm = mail_newsearchpgm();
2385 secondpgm->cc = mail_newstringlist();
2386 secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
2387 secondpgm->cc->text.size = strlen(sstring);
2388 thirdpgm = mail_newsearchpgm();
2389 thirdpgm->from = mail_newstringlist();
2390 thirdpgm->from->text.data = (unsigned char *) cpystr(sstring);
2391 thirdpgm->from->text.size = strlen(sstring);
2393 else{
2394 pgm->or = mail_newsearchor();
2395 pgm->or->first->to = mail_newstringlist();
2396 pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
2397 pgm->or->first->to->text.size = strlen(sstring);
2399 pgm->or->second->or = mail_newsearchor();
2400 pgm->or->second->or->first->cc = mail_newstringlist();
2401 pgm->or->second->or->first->cc->text.data =
2402 (unsigned char *) cpystr(sstring);
2403 pgm->or->second->or->first->cc->text.size = strlen(sstring);
2404 pgm->or->second->or->second->from = mail_newstringlist();
2405 pgm->or->second->or->second->from->text.data =
2406 (unsigned char *) cpystr(sstring);
2407 pgm->or->second->or->second->from->text.size = strlen(sstring);
2410 break;
2412 case 'f' : /* FROM */
2413 pgm->from = mail_newstringlist();
2414 pgm->from->text.data = (unsigned char *) cpystr(sstring);
2415 pgm->from->text.size = strlen(sstring);
2416 break;
2418 case 'c' : /* CC */
2419 pgm->cc = mail_newstringlist();
2420 pgm->cc->text.data = (unsigned char *) cpystr(sstring);
2421 pgm->cc->text.size = strlen(sstring);
2422 break;
2424 case 't' : /* TO */
2425 pgm->to = mail_newstringlist();
2426 pgm->to->text.data = (unsigned char *) cpystr(sstring);
2427 pgm->to->text.size = strlen(sstring);
2428 break;
2430 case 's' : /* SUBJECT */
2431 pgm->subject = mail_newstringlist();
2432 pgm->subject->text.data = (unsigned char *) cpystr(sstring);
2433 pgm->subject->text.size = strlen(sstring);
2434 break;
2436 case 'a' : /* ALL TEXT */
2437 pgm->text = mail_newstringlist();
2438 pgm->text->text.data = (unsigned char *) cpystr(sstring);
2439 pgm->text->text.size = strlen(sstring);
2440 break;
2442 case 'b' : /* ALL BODY TEXT */
2443 pgm->body = mail_newstringlist();
2444 pgm->body->text.data = (unsigned char *) cpystr(sstring);
2445 pgm->body->text.size = strlen(sstring);
2446 break;
2448 default :
2449 dprint((1,"\n - BOTCH: select_text unrecognized type\n"));
2450 return(1);
2454 * If we happen to have any messages excluded, make sure we
2455 * don't waste time searching their text...
2457 srchpgm->msgno = (limitsrch ? *limitsrch : NULL);
2459 /* TRANSLATORS: warning to user that we're busy selecting messages */
2460 we_cancel = busy_cue(_("Busy Selecting"), NULL, 1);
2462 searchflags = SE_NOPREFETCH | (secondpgm ? 0 : SE_FREE);
2464 pine_mail_search_full(stream, !old_imap ? charset : NULL, srchpgm,
2465 searchflags);
2467 /* search for To or Cc; or To or Cc or From on old imap server */
2468 if(secondpgm){
2469 if(srchpgm){
2470 srchpgm->msgno = NULL;
2471 mail_free_searchpgm(&srchpgm);
2474 secondpgm->msgno = (limitsrch ? *limitsrch : NULL);
2475 searchflags |= (SE_RETAIN | (thirdpgm ? 0 : SE_FREE));
2477 pine_mail_search_full(stream, NULL, secondpgm, searchflags);
2479 if(thirdpgm){
2480 if(secondpgm){
2481 secondpgm->msgno = NULL;
2482 mail_free_searchpgm(&secondpgm);
2485 thirdpgm->msgno = (limitsrch ? *limitsrch : NULL);
2486 searchflags |= SE_FREE;
2487 pine_mail_search_full(stream, NULL, thirdpgm, searchflags);
2491 /* we know this was freed in mail_search, let caller know */
2492 if(limitsrch)
2493 *limitsrch = NULL;
2495 if(old_imap && not){
2496 MESSAGECACHE *mc;
2497 long msgno;
2500 * Old imap server doesn't have a NOT, so we actually searched for
2501 * the subject (or whatever) instead of !subject. Flip the searched
2502 * bits.
2504 for(msgno = 1L; msgno <= mn_get_total(msgmap); msgno++)
2505 if(stream && msgno <= stream->nmsgs
2506 && (mc=mail_elt(stream, msgno)) && mc->searched)
2507 mc->searched = NIL;
2508 else
2509 mc->searched = T;
2512 if(we_cancel)
2513 cancel_busy_cue(0);
2515 return(0);
2519 void
2520 search_for_our_regex_addresses(MAILSTREAM *stream, char type, int not,
2521 SEARCHSET *searchset)
2523 long rawno, count = 0L;
2524 MESSAGECACHE *mc;
2525 ADDRESS *addr1 = NULL, *addr2 = NULL, *addr3 = NULL;
2526 ENVELOPE *env;
2527 SEARCHSET *s, *ss = NULL;
2528 extern MAILSTREAM *mm_search_stream;
2529 extern long mm_search_count;
2531 mm_search_count = 0L;
2532 mm_search_stream = stream;
2534 if(!stream)
2535 return;
2537 /* set searched bits to zero */
2538 for(rawno = 1L; rawno <= stream->nmsgs; rawno++)
2539 if((mc=mail_elt(stream, rawno)) != NULL)
2540 mc->searched = NIL;
2542 /* set sequence bits for envelopes we need */
2543 for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
2544 if((mc = mail_elt(stream, rawno)) != NULL){
2545 if((!searchset || in_searchset(searchset, (unsigned long) rawno))
2546 && !mc->private.msg.env){
2547 mc->sequence = 1;
2548 count++;
2550 else
2551 mc->sequence = 0;
2556 * Set up a searchset that will control the fetch ahead.
2558 if(count){
2559 ss = build_searchset(stream);
2560 if(ss){
2561 SEARCHSET **sset = NULL;
2563 mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
2565 /* this resets automatically after the first fetch */
2566 sset = (SEARCHSET **) mail_parameters(stream,
2567 GET_FETCHLOOKAHEAD,
2568 (void *) stream);
2569 if(sset)
2570 *sset = ss;
2574 for(s = searchset; s; s = s->next){
2575 for(rawno = s->first; rawno <= s->last; rawno++){
2576 env = pine_mail_fetchenvelope(stream, rawno);
2577 addr1 = addr2 = addr3 = NULL;
2578 switch(type){
2579 case 'r' :
2580 addr1 = env ? env->to : NULL;
2581 addr2 = env ? env->cc : NULL;
2582 break;
2583 case 'p' :
2584 addr1 = env ? env->to : NULL;
2585 addr2 = env ? env->cc : NULL;
2586 addr3 = env ? env->from : NULL;
2587 break;
2588 case 'f' :
2589 addr1 = env ? env->from : NULL;
2590 break;
2591 case 'c' :
2592 addr1 = env ? env->cc : NULL;
2593 break;
2594 break;
2595 case 't' :
2596 addr1 = env ? env->to : NULL;
2597 break;
2598 default :
2599 q_status_message(SM_ORDER, 3, 3, "Unhandled case2 in agg_text_select");
2600 break;
2603 if(addr1 && address_is_us(addr1, ps_global)){
2604 if(rawno > 0L && rawno <= stream->nmsgs
2605 && (mc=mail_elt(stream, rawno)) != NULL)
2606 mm_searched(stream, rawno);
2608 else if(addr2 && address_is_us(addr2, ps_global)){
2609 if(rawno > 0L && rawno <= stream->nmsgs
2610 && (mc=mail_elt(stream, rawno)) != NULL)
2611 mm_searched(stream, rawno);
2613 else if(addr3 && address_is_us(addr3, ps_global)){
2614 if(rawno > 0L && rawno <= stream->nmsgs
2615 && (mc=mail_elt(stream, rawno)) != NULL)
2616 mm_searched(stream, rawno);
2621 if(ss)
2622 mail_free_searchset(&ss);
2624 if(not){
2625 for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
2626 if((mc=mail_elt(stream, rawno)) && mc->searched)
2627 mc->searched = NIL;
2628 else
2629 mc->searched = T;
2636 agg_flag_select(MAILSTREAM *stream, int not, int crit, SEARCHSET **limitsrch)
2638 SEARCHPGM *pgm;
2640 pgm = mail_newsearchpgm();
2641 switch(crit){
2642 case 'n' :
2643 if(not){
2644 SEARCHPGM *notpgm;
2646 /* this is the same as seen or deleted or answered */
2647 pgm->not = mail_newsearchpgmlist();
2648 notpgm = pgm->not->pgm = mail_newsearchpgm();
2649 notpgm->unseen = notpgm->undeleted = notpgm->unanswered = 1;
2651 else
2652 pgm->unseen = pgm->undeleted = pgm->unanswered = 1;
2654 break;
2656 case 'd' :
2657 if(not)
2658 pgm->undeleted = 1;
2659 else
2660 pgm->deleted = 1;
2662 break;
2664 case 'r' :
2665 if(not)
2666 pgm->old = 1;
2667 else
2668 pgm->recent = 1;
2670 break;
2672 case 'u' :
2673 if(not)
2674 pgm->seen = 1;
2675 else
2676 pgm->unseen = 1;
2678 break;
2680 case 'a':
2682 * Not a true "not", we are implicitly only interested in undeleted.
2684 if(not)
2685 pgm->unanswered = pgm->undeleted = 1;
2686 else
2687 pgm->answered = pgm->undeleted = 1;
2688 break;
2690 case 'f':
2692 STRINGLIST **slpp;
2694 for(slpp = (not) ? &pgm->unkeyword : &pgm->keyword;
2695 *slpp;
2696 slpp = &(*slpp)->next)
2699 *slpp = mail_newstringlist();
2700 (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG);
2701 (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG);
2704 break;
2706 case '*' :
2707 if(not)
2708 pgm->unflagged = 1;
2709 else
2710 pgm->flagged = 1;
2712 break;
2714 default :
2715 return(1);
2716 break;
2719 pgm->msgno = (limitsrch ? *limitsrch : NULL);
2720 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
2721 /* we know this was freed in mail_search, let caller know */
2722 if(limitsrch)
2723 *limitsrch = NULL;
2725 return(0);
2730 * Get the user name from the mailbox portion of an address.
2732 * Args: mailbox -- the mailbox portion of an address (lhs of address)
2733 * target -- a buffer to put the result in
2734 * len -- length of the target buffer
2736 * Returns the left most portion up to the first '%', ':' or '@',
2737 * and to the right of any '!' (as if c-client would give us such a mailbox).
2738 * Returns NULL if it can't find a username to point to.
2740 char *
2741 get_uname(char *mailbox, char *target, int len)
2743 int i, start, end;
2745 if(!mailbox || !*mailbox)
2746 return(NULL);
2748 end = strlen(mailbox) - 1;
2749 for(start = end; start > -1 && mailbox[start] != '!'; start--)
2750 if(strindex("%:@", mailbox[start]))
2751 end = start - 1;
2753 start++; /* compensate for either case above */
2755 for(i = start; i <= end && (i-start) < (len-1); i++) /* copy name */
2756 target[i-start] = isupper((unsigned char)mailbox[i])
2757 ? tolower((unsigned char)mailbox[i])
2758 : mailbox[i];
2760 target[i-start] = '\0'; /* tie it off */
2762 return(*target ? target : NULL);