* Implement a different way to delete a password from the cache.
[alpine.git] / pith / mailcmd.c
blobf1f34180278b292e95ce43237559bb8f18449ec0
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2007 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/mailcmd.h"
17 #include "../pith/conf.h"
18 #include "../pith/status.h"
19 #include "../pith/flag.h"
20 #include "../pith/thread.h"
21 #include "../pith/util.h"
22 #include "../pith/folder.h"
23 #include "../pith/sort.h"
24 #include "../pith/newmail.h"
25 #include "../pith/mailview.h"
26 #include "../pith/mailindx.h"
27 #include "../pith/save.h"
28 #include "../pith/news.h"
29 #include "../pith/sequence.h"
30 #include "../pith/stream.h"
31 #include "../pith/ldap.h"
32 #include "../pith/options.h"
33 #include "../pith/busy.h"
34 #include "../pith/icache.h"
35 #include "../pith/ablookup.h"
36 #include "../pith/search.h"
37 #include "../pith/charconv/utf8.h"
39 #ifdef _WINDOWS
40 #include "../pico/osdep/mswin.h"
41 #endif
45 * Internal prototypes
50 * optional function hooks
52 int (*pith_opt_read_msg_prompt)(long, char *);
53 int (*pith_opt_reopen_folder)(struct pine *, int *);
54 int (*pith_opt_expunge_prompt)(MAILSTREAM *, char *, long);
55 void (*pith_opt_begin_closing)(int, char *);
56 void get_new_message_count(MAILSTREAM *, int, long *, long *);
57 char *new_messages_string(MAILSTREAM *);
58 void search_for_our_regex_addresses(MAILSTREAM *stream, char type,
59 int not, SEARCHSET *searchset);
63 /*----------------------------------------------------------------------
64 Complain about command on empty folder
66 Args: map -- msgmap
67 type -- type of message that's missing
68 cmd -- string explaining command attempted
70 ----*/
71 int
72 any_messages(MSGNO_S *map, char *type, char *cmd)
74 if(mn_get_total(map) <= 0L){
75 q_status_message5(SM_ORDER, 0, 2, "No %s%s%s%s%s",
76 type ? type : "",
77 type ? " " : "",
78 THRD_INDX() ? "threads" : "messages",
79 (!cmd || *cmd != '.') ? " " : "",
80 cmd ? cmd : "in folder");
81 return(FALSE);
84 return(TRUE);
88 /*----------------------------------------------------------------------
89 test whether or not we have a valid stream to set flags on
91 Args: state -- pine state containing vital signs
92 cmd -- string explaining command attempted
93 permflag -- associated permanent flag state
95 Result: returns 1 if we can set flags, otw 0 and complains
97 ----*/
98 int
99 can_set_flag(struct pine *state, char *cmd, int permflag)
101 if((!permflag && READONLY_FOLDER(state->mail_stream))
102 || sp_dead_stream(state->mail_stream)){
103 q_status_message2(SM_ORDER | (sp_dead_stream(state->mail_stream)
104 ? SM_DING : 0),
105 0, 3,
106 "Can't %s message. Folder is %s.", cmd,
107 (sp_dead_stream(state->mail_stream)) ? "closed" : "read-only");
108 return(FALSE);
111 return(TRUE);
115 /*----------------------------------------------------------------------
116 Complain about command on empty folder
118 Args: type -- type of message that's missing
119 cmd -- string explaining command attempted
121 ----*/
122 void
123 cmd_cancelled(char *cmd)
125 /* TRANSLATORS: Arg is replaced with the command name or the word Command */
126 q_status_message1(SM_INFO, 0, 2, _("%s cancelled"), cmd ? cmd : _("Command"));
130 /*----------------------------------------------------------------------
131 Execute DELETE message command
133 Args: state -- Various satate info
134 msgmap -- map of c-client to local message numbers
136 Result: with side effect of "current" message delete flag set
138 ----*/
140 cmd_delete(struct pine *state, MSGNO_S *msgmap, int copts,
141 char *(*cmd_action_f)(struct pine *, MSGNO_S *))
143 int lastmsg, rv = 0;
144 long msgno, del_count = 0L, new;
145 char *sequence = NULL, prompt[128];
147 dprint((4, "\n - delete message -\n"));
148 if(!(any_messages(msgmap, NULL, "to Delete")
149 && can_set_flag(state, "delete", state->mail_stream->perm_deleted)))
150 return rv;
152 rv++;
154 if(sp_io_error_on_stream(state->mail_stream)){
155 sp_set_io_error_on_stream(state->mail_stream, 0);
156 pine_mail_check(state->mail_stream); /* forces write */
159 if(MCMD_ISAGG(copts)){
160 sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 0);
161 snprintf(prompt, sizeof(prompt), "%ld%s message%s marked for deletion",
162 del_count, (copts & MCMD_AGG_2) ? "" : " selected", plural(del_count));
164 else{
165 long rawno;
167 msgno = mn_get_cur(msgmap);
168 rawno = mn_m2raw(msgmap, msgno);
169 del_count = 1L; /* return current */
170 sequence = cpystr(long2string(rawno));
171 lastmsg = (msgno >= mn_get_total(msgmap));
172 snprintf(prompt, sizeof(prompt), "%s%s marked for deletion",
173 lastmsg ? "Last message" : "Message ",
174 lastmsg ? "" : long2string(msgno));
177 dprint((3, "DELETE: msg %s\n", sequence ? sequence : "?"));
178 new = sp_new_mail_count(state->mail_stream);
179 mail_flag(state->mail_stream, sequence, "\\DELETED", ST_SET);
180 fs_give((void **) &sequence);
181 if(new != sp_new_mail_count(state->mail_stream))
182 process_filter_patterns(state->mail_stream, state->msgmap,
183 sp_new_mail_count(state->mail_stream));
185 if(cmd_action_f){
186 char *rv;
188 if((rv = (*cmd_action_f)(state, msgmap)) != NULL)
189 strncat(prompt, rv, sizeof(prompt) - strlen(prompt)- 1);
192 if(!(copts & MCMD_SILENT))
193 q_status_message(SM_ORDER, 0, 3, prompt);
195 return rv;
199 /*----------------------------------------------------------------------
200 Execute UNDELETE message command
202 Args: state -- Various satate info
203 msgmap -- map of c-client to local message numbers
205 Result: with side effect of "current" message delete flag UNset
207 ----*/
209 cmd_undelete(struct pine *state, MSGNO_S *msgmap, int copts)
211 long del_count;
212 char *sequence;
213 int wasdeleted = FALSE, rv = 0;
214 MESSAGECACHE *mc;
216 dprint((4, "\n - undelete -\n"));
217 if(!(any_messages(msgmap, NULL, "to Undelete")
218 && can_set_flag(state, "undelete", state->mail_stream->perm_deleted)))
219 return rv;
221 rv++;
223 if(MCMD_ISAGG(copts)){
224 del_count = 0L; /* return current */
225 sequence = selected_sequence(state->mail_stream, msgmap, &del_count, 1);
226 wasdeleted = TRUE;
228 else{
229 long rawno;
230 int exbits = 0;
232 del_count = 1L; /* return current */
233 rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
234 sequence = cpystr(long2string(rawno));
235 wasdeleted = (state->mail_stream
236 && rawno > 0L && rawno <= state->mail_stream->nmsgs
237 && (mc = mail_elt(state->mail_stream, rawno))
238 && mc->valid
239 && mc->deleted);
241 * Mark this message manually flagged so we don't re-filter it
242 * with a filter which only sets flags.
244 if(msgno_exceptions(state->mail_stream, rawno, "0", &exbits, FALSE))
245 exbits |= MSG_EX_MANUNDEL;
246 else
247 exbits = MSG_EX_MANUNDEL;
249 msgno_exceptions(state->mail_stream, rawno, "0", &exbits, TRUE);
252 dprint((3, "UNDELETE: msg %s\n", sequence ? sequence : "?"));
254 mail_flag(state->mail_stream, sequence, "\\DELETED", 0L);
255 fs_give((void **) &sequence);
257 if((copts & MCMD_SILENT) == 0){
258 if(del_count == 1L && MCMD_ISAGG(copts) == 0){
259 q_status_message(SM_ORDER, 0, 3,
260 wasdeleted
261 ? _("Deletion mark removed, message won't be deleted")
262 : _("Message not marked for deletion; no action taken"));
264 else
265 q_status_message2(SM_ORDER, 0, 3,
266 _("Deletion mark removed from %s message%s"),
267 comatose(del_count), plural(del_count));
270 if(sp_io_error_on_stream(state->mail_stream)){
271 sp_set_io_error_on_stream(state->mail_stream, 0);
272 pine_mail_check(state->mail_stream); /* forces write */
275 return rv;
280 cmd_expunge_work(MAILSTREAM *stream, MSGNO_S *msgmap, char *seq)
282 long old_max_msgno;
283 int rv = 0;
285 old_max_msgno = mn_get_total(msgmap);
286 delete_filtered_msgs(stream);
287 ps_global->expunge_in_progress = 1;
288 mail_expunge_full(stream, seq, 0);
289 ps_global->expunge_in_progress = 0;
291 dprint((2,"expunge complete cur:%ld max:%ld\n",
292 mn_get_cur(msgmap), mn_get_total(msgmap)));
294 * This is only actually necessary if this causes the width of the
295 * message number field to change. That is, it depends on the
296 * format the user is using as well as on the max_msgno. Since it
297 * should be rare, we'll just do it whenever it happens.
298 * Also have to check for an increase in max_msgno on new mail.
300 if((old_max_msgno >= 1000L && mn_get_total(msgmap) < 1000L)
301 || (old_max_msgno >= 10000L && mn_get_total(msgmap) < 10000L)
302 || (old_max_msgno >= 100000L && mn_get_total(msgmap) < 100000L)){
303 clear_index_cache(stream, 0);
304 rv = 1;
308 * mm_exists and mm_expunge take care of updating max_msgno
309 * and selecting a new message should the selected get removed
313 reset_check_point(stream);
315 return(rv);
319 CONTEXT_S *
320 broach_get_folder(CONTEXT_S *context, int *inbox, char **folder)
322 CONTEXT_S *tc;
324 if(ps_global->goto_default_rule == GOTO_LAST_FLDR){
325 tc = context ? context : ps_global->context_current;
326 *inbox = 1; /* fill in last_folder below */
328 else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN){
329 tc = (ps_global->context_list->use & CNTXT_INCMNG)
330 ? ps_global->context_list->next : ps_global->context_list;
331 ps_global->last_unambig_folder[0] = '\0';
332 *inbox = 1; /* fill in last_folder below */
334 else if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX){
335 tc = (ps_global->context_list->use & CNTXT_INCMNG)
336 ? ps_global->context_list->next : ps_global->context_list;
337 tc->last_folder[0] = '\0';
338 *inbox = 0;
339 ps_global->last_unambig_folder[0] = '\0';
341 else{
342 *inbox = (ps_global->cur_folder
343 && ps_global->inbox_name
344 && strucmp(ps_global->cur_folder,ps_global->inbox_name) == 0
345 && (!ps_global->context_current
346 || ps_global->context_current->use & CNTXT_INCMNG
347 || (!(ps_global->context_list->use & CNTXT_INCMNG)
348 && ps_global->context_current == ps_global->context_list)));
349 if(!*inbox)
350 tc = ps_global->context_list; /* inbox's context */
351 else if(ps_global->goto_default_rule == GOTO_INBOX_FIRST_CLCTN){
352 tc = (ps_global->context_list->use & CNTXT_INCMNG)
353 ? ps_global->context_list->next : ps_global->context_list;
354 ps_global->last_unambig_folder[0] = '\0';
356 else
357 tc = context ? context : ps_global->context_current;
360 if(folder){
361 if(!*inbox){
362 *folder = ps_global->inbox_name;
364 else
365 *folder = (ps_global->last_unambig_folder[0])
366 ? ps_global->last_unambig_folder
367 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
370 return(tc);
374 /*----------------------------------------------------------------------
375 Actually attempt to open given folder
377 Args: newfolder -- The folder name to open
378 streamp -- Candidate stream for recycling. This stream will either
379 be re-used, or it will be closed.
381 Result: 1 if the folder was successfully opened
382 0 if the folder open failed and went back to old folder
383 -1 if open failed and no folder is left open
385 Attempt to open the folder name given. If the open of the new folder
386 fails then the previously open folder will remain open, unless
387 something really bad has happened. The designate inbox will always be
388 kept open, and when a request to open it is made the already open
389 stream will be used.
390 ----*/
392 do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp,
393 long unsigned int flags)
395 MAILSTREAM *m, *strm, *stream = streamp ? *streamp : NULL;
396 int open_inbox, rv, old_tros, we_cancel = 0,
397 do_reopen = 0, n, was_dead = 0, cur_already_set = 0;
398 char expanded_file[MAX(MAXPATH,MAILTMPLEN)+1],
399 *old_folder, *old_path, *p, *report;
400 unsigned char *fname;
401 long openmode, rflags = 0L, pc = 0L, cur, raw;
402 ENVELOPE *env = NULL;
403 char status_msg[81];
404 SortOrder old_sort;
405 unsigned perfolder_startup_rule;
406 char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN], *lname, *mname;
408 openmode = SP_USERFLDR;
410 dprint((1, "About to open folder \"%s\" inbox is: \"%s\"\n",
411 newfolder ? newfolder : "?",
412 ps_global->inbox_name ? ps_global->inbox_name : "?"));
415 *--- Set flag that we're opening the inbox, a special case.
417 * We want to know if inbox is being opened either by name OR
418 * fully qualified path...
420 if(strucmp(newfolder, ps_global->inbox_name) == 0)
421 open_inbox = (flags & DB_INBOXWOCNTXT || new_context == ps_global->context_list);
422 else{
423 open_inbox = (strcmp(newfolder, ps_global->VAR_INBOX_PATH) == 0
424 || same_remote_mailboxes(newfolder, ps_global->VAR_INBOX_PATH)
425 || (!IS_REMOTE(newfolder)
426 && (lname=mailboxfile(tmp1,newfolder))
427 && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
428 && !strcmp(lname,mname)));
430 /* further checking for inbox open */
431 if(!open_inbox && new_context && context_isambig(newfolder)){
432 if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
434 * Check for an incoming folder other
435 * than INBOX that also point to INBOX.
437 open_inbox = (strucmp(p, ps_global->inbox_name) == 0
438 || strcmp(p, ps_global->VAR_INBOX_PATH) == 0
439 || same_remote_mailboxes(p, ps_global->VAR_INBOX_PATH)
440 || (!IS_REMOTE(p)
441 && (lname=mailboxfile(tmp1,p))
442 && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
443 && !strcmp(lname,mname)));
445 else if(!(new_context->use & CNTXT_INCMNG)){
446 char tmp3[MAILTMPLEN];
449 * Check to see if we are opening INBOX using the folder name
450 * and a context. We won't have recognized this is the
451 * same as INBOX without applying the context first.
453 context_apply(tmp3, new_context, newfolder, sizeof(tmp3));
454 open_inbox = (strucmp(tmp3, ps_global->inbox_name) == 0
455 || strcmp(tmp3, ps_global->VAR_INBOX_PATH) == 0
456 || same_remote_mailboxes(tmp3, ps_global->VAR_INBOX_PATH)
457 || (!IS_REMOTE(tmp3)
458 && (lname=mailboxfile(tmp1,tmp3))
459 && (mname=mailboxfile(tmp2,ps_global->VAR_INBOX_PATH))
460 && !strcmp(lname,mname)));
465 if(open_inbox)
466 new_context = ps_global->context_list; /* restore first context */
468 was_dead = sp_a_locked_stream_is_dead();
470 /*----- Little to do to if reopening same folder -----*/
471 if(new_context == ps_global->context_current && ps_global->mail_stream
472 && (strcmp(newfolder, ps_global->cur_folder) == 0
473 || (open_inbox && sp_flagged(ps_global->mail_stream, SP_INBOX)))){
474 if(stream){
475 pine_mail_close(stream); /* don't need it */
476 stream = NULL;
479 if(sp_dead_stream(ps_global->mail_stream))
480 do_reopen++;
483 * If it is a stream which could probably discover newmail by
484 * reopening and user has YES set for those streams, or it
485 * is a stream which may discover newmail by reopening and
486 * user has YES set for those stream, then do_reopen.
488 if(!do_reopen
490 (((ps_global->mail_stream->dtb
491 && ((ps_global->mail_stream->dtb->flags & DR_NONEWMAIL)
492 || (ps_global->mail_stream->rdonly
493 && ps_global->mail_stream->dtb->flags
494 & DR_NONEWMAILRONLY)))
495 && (ps_global->reopen_rule == REOPEN_YES_YES
496 || ps_global->reopen_rule == REOPEN_YES_ASK_Y
497 || ps_global->reopen_rule == REOPEN_YES_ASK_N
498 || ps_global->reopen_rule == REOPEN_YES_NO))
500 ((ps_global->mail_stream->dtb
501 && ps_global->mail_stream->rdonly
502 && !(ps_global->mail_stream->dtb->flags & DR_LOCAL))
503 && (ps_global->reopen_rule == REOPEN_YES_YES))))
504 do_reopen++;
507 * If it is a stream which could probably discover newmail by
508 * reopening and user has ASK set for those streams, or it
509 * is a stream which may discover newmail by reopening and
510 * user has ASK set for those stream, then ask.
512 if(!do_reopen
513 && pith_opt_reopen_folder
514 && (*pith_opt_reopen_folder)(ps_global, &do_reopen) < 0){
515 cmd_cancelled(NULL);
516 return(0);
519 if(do_reopen){
521 * If it's not healthy or if the user explicitly wants to
522 * do a reopen, we reset things and fall thru
523 * to actually reopen it.
525 if(sp_dead_stream(ps_global->mail_stream)){
526 dprint((2, "Stream was dead, reopening \"%s\"\n",
527 newfolder ? newfolder : "?"));
530 /* clean up */
531 pine_mail_actually_close(ps_global->mail_stream);
532 ps_global->mangled_header = 1;
533 clear_index_cache(ps_global->mail_stream, 0);
535 else{
536 if(!(flags & DB_NOVISIT))
537 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
539 return(1); /* successful open of same folder! */
544 * If ambiguous foldername (not fully qualified), make sure it's
545 * not a nickname for a folder in the given context...
548 /* might get reset below */
549 strncpy(expanded_file, newfolder, sizeof(expanded_file));
550 expanded_file[sizeof(expanded_file)-1] = '\0';
552 if(!open_inbox && new_context && context_isambig(newfolder)){
553 if((p = folder_is_nick(newfolder, FOLDERS(new_context), FN_WHOLE_NAME)) != NULL){
554 strncpy(expanded_file, p, sizeof(expanded_file));
555 expanded_file[sizeof(expanded_file)-1] = '\0';
556 dprint((2, "broach_folder: nickname for %s is %s\n",
557 expanded_file ? expanded_file : "?",
558 newfolder ? newfolder : "?"));
560 else if((new_context->use & CNTXT_INCMNG)
561 && (folder_index(newfolder, new_context, FI_FOLDER) < 0)
562 && !is_absolute_path(newfolder)){
563 fname = folder_name_decoded((unsigned char *)newfolder);
564 q_status_message1(SM_ORDER, 3, 4,
565 _("Can't find Incoming Folder %s."), fname ? (char *) fname : newfolder);
566 if(stream)
567 pine_mail_close(stream);
569 if(fname)
570 fs_give((void **)&fname);
572 return(0);
576 /*--- Opening inbox, inbox has been already opened, the easy case ---*/
578 * [ It is probably true that we could eliminate most of this special ]
579 * [ inbox stuff and just get the inbox stream back when we do the ]
580 * [ context_open below, but figuring that out hasn't been done. ]
582 if(open_inbox && (strm=sp_inbox_stream())){
583 if(sp_dead_stream(strm)){
585 * if dead INBOX, just close it and let it be reopened.
586 * This is different from the do_reopen case above,
587 * because we're going from another open mail folder to the
588 * dead INBOX.
590 dprint((2, "INBOX was dead, closing before reopening\n"));
591 pine_mail_actually_close(strm);
593 else{
595 * Clean up the mail_stream we're leaving.
597 if(ps_global->mail_stream
598 && (!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
599 || (sp_flagged(ps_global->mail_stream, SP_INBOX)
600 && F_ON(F_EXPUNGE_INBOX, ps_global))
601 || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
602 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
603 && F_ON(F_EXPUNGE_STAYOPENS, ps_global))))
604 expunge_and_close(ps_global->mail_stream, NULL,
605 sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
606 ? EC_NO_CLOSE : EC_NONE);
607 else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
608 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
610 * We want to save our position in the folder so that when we
611 * come back to this folder again, we can place the cursor on
612 * a reasonable message number.
614 sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);
616 if(ps_global->mail_stream->nmsgs > 0L){
617 cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
618 raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
619 if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
620 env = pine_mail_fetchstructure(ps_global->mail_stream,
621 raw, NULL);
623 if(env && env->message_id && env->message_id[0])
624 sp_set_saved_cur_msg_id(ps_global->mail_stream,
625 env->message_id);
630 * Make the already open inbox the current mailbox.
632 ps_global->mail_stream = strm;
633 ps_global->msgmap = sp_msgmap(strm);
635 if(was_dead && pith_opt_icon_text)
636 (*pith_opt_icon_text)(NULL, IT_MCLOSED);
638 dprint((7, "%ld %ld %x\n",
639 mn_get_cur(ps_global->msgmap),
640 mn_get_total(ps_global->msgmap),
641 ps_global->mail_stream));
643 * remember last context and folder
645 if(context_isambig(ps_global->cur_folder)){
646 ps_global->context_last = ps_global->context_current;
647 snprintf(ps_global->context_current->last_folder,
648 sizeof(ps_global->context_current->last_folder),
649 "%.*s", (int) sizeof(ps_global->context_current->last_folder) - 1,ps_global->cur_folder);
650 ps_global->last_unambig_folder[0] = '\0';
652 else{
653 ps_global->context_last = NULL;
654 snprintf(ps_global->last_unambig_folder,
655 sizeof(ps_global->last_unambig_folder),
656 "%s", ps_global->cur_folder);
659 p = sp_fldr(ps_global->mail_stream) ? sp_fldr(ps_global->mail_stream)
660 : ps_global->inbox_name;
661 strncpy(ps_global->cur_folder, p, sizeof(ps_global->cur_folder)-1);
662 ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
663 ps_global->context_current = ps_global->context_list;
664 reset_index_format();
665 clear_index_cache(ps_global->mail_stream, 0);
666 /* MUST sort before restoring msgno! */
667 refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);
668 report = new_messages_string(ps_global->mail_stream);
669 q_status_message3(SM_ORDER, 0, 3,
670 (mn_get_total(ps_global->msgmap) > 1)
671 ? _("Opened folder \"%s\" with %s messages%s")
672 : _("Opened folder \"%s\" with %s message%s"),
673 ps_global->inbox_name,
674 long2string(mn_get_total(ps_global->msgmap)),
675 report ? report : "");
676 if(report)
677 fs_give((void **)&report);
679 #ifdef _WINDOWS
680 mswin_settitle(ps_global->inbox_name);
681 #endif
682 if(stream)
683 pine_mail_close(stream);
685 if(!(flags & DB_NOVISIT))
686 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
688 return(1);
692 if(!new_context && !expand_foldername(expanded_file,sizeof(expanded_file))){
693 if(stream)
694 pine_mail_close(stream);
696 return(0);
700 * This is a safe time to clean up dead streams because nothing should
701 * be referencing them right now.
703 sp_cleanup_dead_streams();
705 old_folder = NULL;
706 old_path = NULL;
707 old_sort = SortArrival; /* old sort */
708 old_tros = 0; /* old reverse sort ? */
709 /*---- now close the old one we had open if there was one ----*/
710 if(ps_global->mail_stream != NULL){
711 old_folder = cpystr(ps_global->cur_folder);
712 old_path = cpystr(ps_global->mail_stream->original_mailbox
713 ? ps_global->mail_stream->original_mailbox
714 : ps_global->mail_stream->mailbox);
715 old_sort = mn_get_sort(ps_global->msgmap);
716 old_tros = mn_get_revsort(ps_global->msgmap);
717 if(!sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
718 || (sp_flagged(ps_global->mail_stream, SP_INBOX)
719 && F_ON(F_EXPUNGE_INBOX, ps_global))
720 || (!sp_flagged(ps_global->mail_stream, SP_INBOX)
721 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
722 && F_ON(F_EXPUNGE_STAYOPENS, ps_global)))
723 expunge_and_close(ps_global->mail_stream, NULL,
724 sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)
725 ? EC_NO_CLOSE : EC_NONE);
726 else if(!sp_flagged(ps_global->mail_stream, SP_INBOX)
727 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED)){
729 * We want to save our position in the folder so that when we
730 * come back to this folder again, we can place the cursor on
731 * a reasonable message number.
734 sp_set_saved_cur_msg_id(ps_global->mail_stream, NULL);
736 if(ps_global->mail_stream->nmsgs > 0L){
737 cur = mn_get_cur(sp_msgmap(ps_global->mail_stream));
738 raw = mn_m2raw(sp_msgmap(ps_global->mail_stream), cur);
739 if(raw > 0L && raw <= ps_global->mail_stream->nmsgs)
740 env = pine_mail_fetchstructure(ps_global->mail_stream,
741 raw, NULL);
743 if(env && env->message_id && env->message_id[0])
744 sp_set_saved_cur_msg_id(ps_global->mail_stream,
745 env->message_id);
749 ps_global->mail_stream = NULL;
752 snprintf(status_msg, sizeof(status_msg), "%sOpening \"", do_reopen ? "Re-" : "");
753 fname = folder_name_decoded((unsigned char *)newfolder);
754 strncat(status_msg, pretty_fn(fname ? (char *) fname : newfolder),
755 sizeof(status_msg)-strlen(status_msg) - 2);
756 if(fname) fs_give((void **)&fname);
757 status_msg[sizeof(status_msg)-2] = '\0';
758 strncat(status_msg, "\"", sizeof(status_msg)-strlen(status_msg) - 1);
759 status_msg[sizeof(status_msg)-1] = '\0';
760 we_cancel = busy_cue(status_msg, NULL, 0);
763 * if requested, make access to folder readonly (only once)
765 if(ps_global->open_readonly_on_startup){
766 openmode |= OP_READONLY;
767 ps_global->open_readonly_on_startup = 0;
770 if(!(flags & DB_NOVISIT))
771 ps_global->first_open_was_attempted = 1;
773 openmode |= SP_USEPOOL;
775 if(stream)
776 sp_set_first_unseen(stream, 0L);
778 /* in case we closed the old stream by cancelling the connection, do
779 * not let that interfere with opening the new stream.
781 ps_global->user_says_cancel = 0;
783 m = context_open((new_context && !open_inbox) ? new_context : NULL,
784 stream,
785 open_inbox ? ps_global->VAR_INBOX_PATH : expanded_file,
786 openmode | (open_inbox ? SP_INBOX : 0),
787 &rflags);
789 if(m == NULL
790 && ps_global->in_xoauth2_auth
791 && ps_global->preserve_password){
792 ps_global->in_xoauth2_auth = ps_global->user_says_cancel = ps_global->preserve_password = 0;
793 m = context_open((new_context && !open_inbox) ? new_context : NULL,
794 stream,
795 open_inbox ? ps_global->VAR_INBOX_PATH : expanded_file,
796 openmode | (open_inbox ? SP_INBOX : 0),
797 &rflags);
801 * We aren't in a situation where we want a single cancel to
802 * apply to multiple opens.
804 ps_global->user_says_cancel = 0;
806 /* we are not in authentication mode anymore */
807 ps_global->preserve_password = ps_global->in_xoauth2_auth = 0;
809 if(streamp)
810 *streamp = m;
813 dprint((8, "Opened folder %p \"%s\" (context: \"%s\")\n",
814 m, (m && m->mailbox) ? m->mailbox : "nil",
815 (new_context && new_context->context)
816 ? new_context->context : "nil"));
819 /* Can get m != NULL if correct passwd for remote, but wrong name */
820 if(m == NULL || m->halfopen){
821 /*-- non-existent local mailbox, or wrong passwd for remote mailbox--*/
822 /* fall back to currently open mailbox */
823 if(we_cancel)
824 cancel_busy_cue(-1);
826 ps_global->mail_stream = NULL;
827 if(m)
828 pine_mail_actually_close(m);
830 rv = 0;
831 dprint((8, "Old folder: \"%s\"\n",
832 old_folder == NULL ? "" : old_folder));
833 if(old_folder != NULL){
834 if(strucmp(old_folder, ps_global->inbox_name) == 0){
835 ps_global->mail_stream = sp_inbox_stream();
836 ps_global->msgmap = sp_msgmap(ps_global->mail_stream);
838 dprint((8, "Reactivate inbox %ld %ld %p\n",
839 mn_get_cur(ps_global->msgmap),
840 mn_get_total(ps_global->msgmap),
841 ps_global->mail_stream));
842 p = sp_fldr(ps_global->mail_stream)
843 ? sp_fldr(ps_global->mail_stream)
844 : ps_global->inbox_name;
845 strncpy(ps_global->cur_folder, p,
846 sizeof(ps_global->cur_folder)-1);
847 ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
849 else{
850 ps_global->mail_stream = pine_mail_open(NULL, old_path,
851 openmode, &rflags);
852 /* mm_log will take care of error message here */
853 if(ps_global->mail_stream == NULL){
854 rv = -1;
856 else{
857 ps_global->msgmap = sp_msgmap(ps_global->mail_stream);
858 mn_set_sort(ps_global->msgmap, old_sort);
859 mn_set_revsort(ps_global->msgmap, old_tros);
860 ps_global->mangled_header = 1;
861 reset_index_format();
862 clear_index_cache(ps_global->mail_stream, 0);
864 if(!(rflags & SP_MATCH)){
865 sp_set_expunge_count(ps_global->mail_stream, 0L);
866 sp_set_new_mail_count(ps_global->mail_stream, 0L);
867 sp_set_dead_stream(ps_global->mail_stream, 0);
868 sp_set_noticed_dead_stream(ps_global->mail_stream, 0);
870 reset_check_point(ps_global->mail_stream);
871 if(IS_NEWS(ps_global->mail_stream)
872 && ps_global->mail_stream->rdonly)
873 msgno_exclude_deleted(ps_global->mail_stream,
874 sp_msgmap(ps_global->mail_stream),
875 NULL);
877 if(mn_get_total(ps_global->msgmap) > 0)
878 mn_set_cur(ps_global->msgmap,
879 first_sorted_flagged(F_NONE,
880 ps_global->mail_stream,
882 THREADING()
883 ? 0 : FSF_SKIP_CHID));
885 if(!(mn_get_sort(ps_global->msgmap) == SortArrival
886 && !mn_get_revsort(ps_global->msgmap)))
887 refresh_sort(ps_global->mail_stream,
888 ps_global->msgmap, SRT_NON);
891 fname = folder_name_decoded((unsigned char *)old_folder);
892 q_status_message1(SM_ORDER, 0, 3,
893 "Folder \"%s\" reopened", fname ? (char *)fname : old_folder);
894 if(fname)
895 fs_give((void **)&fname);
899 if(rv == 0)
900 mn_set_cur(ps_global->msgmap,
901 MIN(mn_get_cur(ps_global->msgmap),
902 mn_get_total(ps_global->msgmap)));
904 fs_give((void **)&old_folder);
905 fs_give((void **)&old_path);
907 else
908 rv = -1;
910 if(rv == -1){
911 q_status_message(SM_ORDER | SM_DING, 0, 4, _("No folder opened"));
912 mn_set_total(ps_global->msgmap, 0L);
913 mn_set_nmsgs(ps_global->msgmap, 0L);
914 mn_set_cur(ps_global->msgmap, -1L);
915 ps_global->cur_folder[0] = '\0';
918 if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
919 (*pith_opt_icon_text)(NULL, IT_MCLOSED);
921 if(ps_global->mail_stream && !(flags & DB_NOVISIT))
922 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
924 if(ps_global->mail_stream)
925 sp_set_first_unseen(ps_global->mail_stream, 0L);
927 return(rv);
929 else{
930 if(old_folder != NULL){
931 fs_give((void **)&old_folder);
932 fs_give((void **)&old_path);
936 update_folder_unseen_by_stream(m, UFU_NONE);
938 /*----- success in opening the new folder ----*/
939 dprint((2, "Opened folder \"%s\" with %ld messages\n",
940 m->mailbox ? m->mailbox : "?", m->nmsgs));
943 /*--- A Little house keeping ---*/
945 ps_global->mail_stream = m;
946 if(!(flags & DB_NOVISIT))
947 sp_set_recent_since_visited(ps_global->mail_stream, 0L);
949 ps_global->msgmap = sp_msgmap(m);
950 if(!(rflags & SP_MATCH)){
951 sp_set_expunge_count(m, 0L);
952 sp_set_new_mail_count(m, 0L);
953 sp_set_dead_stream(m, 0);
954 sp_set_noticed_dead_stream(m, 0);
955 sp_set_mail_box_changed(m, 0);
956 reset_check_point(m);
959 if(was_dead && !sp_a_locked_stream_is_dead() && pith_opt_icon_text)
960 (*pith_opt_icon_text)(NULL, IT_MCLOSED);
962 ps_global->last_unambig_folder[0] = '\0';
965 * remember old folder and context...
967 if(context_isambig(ps_global->cur_folder)){
968 ps_global->context_last = ps_global->context_current;
969 snprintf(ps_global->context_current->last_folder,
970 sizeof(ps_global->context_current->last_folder),
971 "%.*s", (int) sizeof(ps_global->context_current->last_folder) - 1, ps_global->cur_folder);
972 ps_global->last_unambig_folder[0] = '\0';
974 else{
975 ps_global->context_last = NULL;
976 snprintf(ps_global->last_unambig_folder,
977 sizeof(ps_global->last_unambig_folder),
978 "%s", ps_global->cur_folder);
981 /* folder in a subdir of context? */
982 if(ps_global->context_current->dir->prev)
983 snprintf(ps_global->cur_folder, sizeof(ps_global->cur_folder), "%s%s",
984 ps_global->context_current->dir->ref, newfolder);
985 else{
986 strncpy(ps_global->cur_folder,
987 (open_inbox) ? ps_global->inbox_name : newfolder,
988 sizeof(ps_global->cur_folder)-1);
989 ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0';
992 sp_set_fldr(ps_global->mail_stream, ps_global->cur_folder);
994 if(new_context){
995 ps_global->context_last = ps_global->context_current;
996 ps_global->context_current = new_context;
998 if(!open_inbox)
999 sp_set_context(ps_global->mail_stream, ps_global->context_current);
1002 clear_index_cache(ps_global->mail_stream, 0);
1003 reset_index_format();
1006 * Start news reading with messages the user's marked deleted
1007 * hidden from view...
1009 if(IS_NEWS(ps_global->mail_stream) && ps_global->mail_stream->rdonly)
1010 msgno_exclude_deleted(ps_global->mail_stream, ps_global->msgmap, NULL);
1012 if(we_cancel && F_OFF(F_QUELL_FILTER_MSGS, ps_global))
1013 cancel_busy_cue(0);
1016 * If the stream we got from the open above was already opened earlier
1017 * for some temporary use, then it wouldn't have been filtered. That's
1018 * why we need this flag, so that we will filter if needed.
1020 if(!sp_flagged(ps_global->mail_stream, SP_FILTERED))
1021 process_filter_patterns(ps_global->mail_stream, ps_global->msgmap, 0L);
1024 * If no filtering messages wait until here to cancel the busy cue
1025 * because the user will be waiting for that filtering with nothing
1026 * showing the activity otherwise.
1028 if(we_cancel && F_ON(F_QUELL_FILTER_MSGS, ps_global))
1029 cancel_busy_cue(0);
1031 if(!(rflags & SP_MATCH) || !(rflags & SP_LOCKED))
1032 reset_sort_order(SRT_VRB);
1033 else if(sp_new_mail_count(ps_global->mail_stream) > 0L
1034 || sp_unsorted_newmail(ps_global->mail_stream)
1035 || sp_need_to_rethread(ps_global->mail_stream))
1036 refresh_sort(ps_global->mail_stream, ps_global->msgmap, SRT_NON);
1038 report = new_messages_string(ps_global->mail_stream);
1039 fname = folder_name_decoded((unsigned char *)newfolder);
1040 q_status_message7(SM_ORDER, 0, 4,
1041 "%s \"%s\" opened with %s message%s%s%s%s",
1042 IS_NEWS(ps_global->mail_stream)
1043 ? "News group" : "Folder",
1044 open_inbox ? pretty_fn(fname ? (char *) fname : newfolder)
1045 : (fname ? (char *)fname : newfolder),
1046 comatose(mn_get_total(ps_global->msgmap)),
1047 plural(mn_get_total(ps_global->msgmap)),
1048 (!open_inbox
1049 && sp_flagged(ps_global->mail_stream, SP_PERMLOCKED))
1050 ? " (StayOpen)" : "",
1051 READONLY_FOLDER(ps_global->mail_stream)
1052 ? " READONLY" : "",
1053 report ? report : "");
1055 if(fname)
1056 fs_give((void **)&fname);
1058 if(report)
1059 fs_give((void **)&report);
1061 #ifdef _WINDOWS
1062 mswin_settitle(pretty_fn(newfolder));
1063 #endif
1065 * Set current message number when re-opening Stay-Open or
1066 * cached folders.
1068 if(rflags & SP_MATCH){
1069 if(rflags & SP_LOCKED){
1070 if(F_OFF(F_STARTUP_STAYOPEN, ps_global)
1071 && (cur = get_msgno_by_msg_id(ps_global->mail_stream,
1072 sp_saved_cur_msg_id(ps_global->mail_stream),
1073 ps_global->msgmap)) >= 1L
1074 && cur <= mn_get_total(ps_global->msgmap)){
1075 cur_already_set++;
1076 mn_set_cur(ps_global->msgmap, (MsgNo) cur);
1077 if(flags & DB_FROMTAB){
1079 * When we TAB to a folder that is a StayOpen folder we try
1080 * to increment the current message # by one instead of doing
1081 * some search again. Some people probably won't like this
1082 * behavior, especially if the new message that has arrived
1083 * comes before where we are in the index. That's why we have
1084 * the F_STARTUP_STAYOPEN feature above.
1086 mn_inc_cur(m, ps_global->msgmap, MH_NONE);
1088 /* else leave it where it is */
1090 adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
1093 else{
1095 * If we're reopening a cached open stream that wasn't explicitly
1096 * kept open by the user, then the user expects it to act pretty
1097 * much like we are re-opening the stream. A problem is that the
1098 * recent messages are still recent because we haven't closed the
1099 * stream, so we fake a quasi-recentness by remembering the last
1100 * uid assigned on the stream when we pine_mail_close. Then when
1101 * we come back messages with uids higher than that are recent.
1103 * If uid_validity has changed, then we don't use any special
1104 * treatment, but just do the regular search.
1106 if(m->uid_validity == sp_saved_uid_validity(m)){
1107 long i;
1110 * Because first_sorted_flagged uses sequence numbers, find the
1111 * sequence number of the first message after the old last
1112 * uid assigned. I.e., the first recent message.
1114 for(i = m->nmsgs; i > 0L; i--)
1115 if(mail_uid(m, i) <= sp_saved_uid_last(m))
1116 break;
1118 if(i > 0L && i < m->nmsgs)
1119 pc = i+1L;
1125 if(!cur_already_set && mn_get_total(ps_global->msgmap) > 0L){
1127 perfolder_startup_rule = reset_startup_rule(ps_global->mail_stream);
1129 if(ps_global->start_entry > 0){
1130 mn_set_cur(ps_global->msgmap, mn_get_revsort(ps_global->msgmap)
1131 ? first_sorted_flagged(F_NONE, m,
1132 ps_global->start_entry,
1133 THREADING() ? 0 : FSF_SKIP_CHID)
1134 : first_sorted_flagged(F_SRCHBACK, m,
1135 ps_global->start_entry,
1136 THREADING() ? 0 : FSF_SKIP_CHID));
1137 ps_global->start_entry = 0;
1139 else if(perfolder_startup_rule != IS_NOTSET ||
1140 open_inbox ||
1141 ps_global->context_current->use & CNTXT_INCMNG){
1142 unsigned use_this_startup_rule;
1144 if(perfolder_startup_rule != IS_NOTSET)
1145 use_this_startup_rule = perfolder_startup_rule;
1146 else
1147 use_this_startup_rule = ps_global->inc_startup_rule;
1149 switch(use_this_startup_rule){
1151 * For news in incoming collection we're doing the same thing
1152 * for first-unseen and first-recent. In both those cases you
1153 * get first-unseen if FAKE_NEW is off and first-recent if
1154 * FAKE_NEW is on. If FAKE_NEW is on, first unseen is the
1155 * same as first recent because all recent msgs are unseen
1156 * and all unrecent msgs are seen (see pine_mail_open).
1158 case IS_FIRST_UNSEEN:
1159 first_unseen:
1160 mn_set_cur(ps_global->msgmap,
1161 (sp_first_unseen(m)
1162 && mn_get_sort(ps_global->msgmap) == SortArrival
1163 && !mn_get_revsort(ps_global->msgmap)
1164 && !get_lflag(ps_global->mail_stream, NULL,
1165 sp_first_unseen(m), MN_EXLD)
1166 && (n = mn_raw2m(ps_global->msgmap,
1167 sp_first_unseen(m))))
1169 : first_sorted_flagged(F_UNSEEN | F_UNDEL, m, pc,
1170 THREADING() ? 0 : FSF_SKIP_CHID));
1171 break;
1173 case IS_FIRST_RECENT:
1174 first_recent:
1176 * We could really use recent for news but this is the way
1177 * it has always worked, so we'll leave it. That is, if
1178 * the FAKE_NEW feature is on, recent and unseen are
1179 * equivalent, so it doesn't matter. If the feature isn't
1180 * on, all the undeleted messages are unseen and we start
1181 * at the first one. User controls with the FAKE_NEW feature.
1183 if(IS_NEWS(ps_global->mail_stream)){
1184 mn_set_cur(ps_global->msgmap,
1185 first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
1186 THREADING() ? 0 : FSF_SKIP_CHID));
1188 else{
1189 mn_set_cur(ps_global->msgmap,
1190 first_sorted_flagged(F_RECENT | F_UNSEEN
1191 | F_UNDEL,
1192 m, pc,
1193 THREADING() ? 0 : FSF_SKIP_CHID));
1195 break;
1197 case IS_FIRST_IMPORTANT:
1198 mn_set_cur(ps_global->msgmap,
1199 first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
1200 THREADING() ? 0 : FSF_SKIP_CHID));
1201 break;
1203 case IS_FIRST_IMPORTANT_OR_UNSEEN:
1205 if(IS_NEWS(ps_global->mail_stream))
1206 goto first_unseen;
1209 MsgNo flagged, first_unseen;
1211 flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
1212 THREADING() ? 0 : FSF_SKIP_CHID);
1213 first_unseen = (sp_first_unseen(m)
1214 && mn_get_sort(ps_global->msgmap) == SortArrival
1215 && !mn_get_revsort(ps_global->msgmap)
1216 && !get_lflag(ps_global->mail_stream, NULL,
1217 sp_first_unseen(m), MN_EXLD)
1218 && (n = mn_raw2m(ps_global->msgmap,
1219 sp_first_unseen(m))))
1221 : first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
1222 THREADING() ? 0 : FSF_SKIP_CHID);
1223 mn_set_cur(ps_global->msgmap,
1224 (MsgNo) MIN((int) flagged, (int) first_unseen));
1228 break;
1230 case IS_FIRST_IMPORTANT_OR_RECENT:
1232 if(IS_NEWS(ps_global->mail_stream))
1233 goto first_recent;
1236 MsgNo flagged, first_recent;
1238 flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc,
1239 THREADING() ? 0 : FSF_SKIP_CHID);
1240 first_recent = first_sorted_flagged(F_RECENT | F_UNSEEN
1241 | F_UNDEL,
1242 m, pc,
1243 THREADING() ? 0 : FSF_SKIP_CHID);
1244 mn_set_cur(ps_global->msgmap,
1245 (MsgNo) MIN((int) flagged, (int) first_recent));
1248 break;
1250 case IS_FIRST:
1251 mn_set_cur(ps_global->msgmap,
1252 first_sorted_flagged(F_UNDEL, m, pc,
1253 THREADING() ? 0 : FSF_SKIP_CHID));
1254 break;
1256 case IS_LAST:
1257 mn_set_cur(ps_global->msgmap,
1258 first_sorted_flagged(F_UNDEL, m, pc,
1259 FSF_LAST | (THREADING() ? 0 : FSF_SKIP_CHID)));
1260 break;
1262 default:
1263 alpine_panic("Unexpected incoming startup case");
1264 break;
1268 else if(IS_NEWS(ps_global->mail_stream)){
1270 * This will go to two different places depending on the FAKE_NEW
1271 * feature (see pine_mail_open).
1273 mn_set_cur(ps_global->msgmap,
1274 first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc,
1275 THREADING() ? 0 : FSF_SKIP_CHID));
1277 else{
1278 mn_set_cur(ps_global->msgmap,
1279 mn_get_revsort(ps_global->msgmap)
1280 ? 1L
1281 : mn_get_total(ps_global->msgmap));
1284 adjust_cur_to_visible(ps_global->mail_stream, ps_global->msgmap);
1286 else if(!(rflags & SP_MATCH)){
1287 mn_set_cur(ps_global->msgmap, -1L);
1290 if(ps_global->mail_stream)
1291 sp_set_first_unseen(ps_global->mail_stream, 0L);
1293 return(1);
1297 /*----------------------------------------------------------------------
1298 Expand a folder name, taking account of the folders_dir and
1299 any home directory reference
1301 Args: filename -- The name of the file that is the folder
1303 Result: The folder name is expanded in place.
1304 Returns 0 and queues status message if unsuccessful.
1305 Input string is overwritten with expanded name.
1306 Returns 1 if successful.
1308 ----*/
1310 expand_foldername(char *filename, size_t len)
1312 char temp_filename[MAXPATH+1];
1314 dprint((5, "=== expand_foldername called (%s) ===\n",
1315 filename ? filename : "?"));
1318 * We used to check for valid filename chars here if "filename"
1319 * didn't refer to a remote mailbox. This has been rethought
1322 strncpy(temp_filename, filename, sizeof(temp_filename)-1);
1323 temp_filename[sizeof(temp_filename)-1] = '\0';
1324 if(strucmp(temp_filename, "inbox") == 0) {
1325 strncpy(filename, ps_global->VAR_INBOX_PATH == NULL ? "inbox" :
1326 ps_global->VAR_INBOX_PATH, len-1);
1327 filename[len-1] = '\0';
1328 } else if(temp_filename[0] == '{') {
1329 strncpy(filename, temp_filename, len-1);
1330 filename[len-1] = '\0';
1331 } else if(ps_global->restricted && filename_is_restricted(temp_filename)){
1332 q_status_message(SM_ORDER, 0, 3, "Can only open local folders");
1333 return(0);
1334 } else if(temp_filename[0] == '*') {
1335 strncpy(filename, temp_filename, len-1);
1336 filename[len-1] = '\0';
1337 } else if(ps_global->VAR_OPER_DIR && filename_parent_ref(temp_filename)){
1338 q_status_message(SM_ORDER, 0, 3,
1339 "\"..\" not allowed in folder name");
1340 return(0);
1341 } else if (is_homedir_path(temp_filename)){
1342 if(fnexpand(temp_filename, sizeof(temp_filename)) == NULL) {
1343 q_status_message1(SM_ORDER, 3, 3, "Error expanding folder \"%s\"", temp_filename);
1344 return(0);
1346 strncpy(filename, temp_filename, len-1);
1347 filename[len-1] = '\0';
1348 } else if(F_ON(F_USE_CURRENT_DIR, ps_global) || is_absolute_path(temp_filename)){
1349 strncpy(filename, temp_filename, len-1);
1350 filename[len-1] = '\0';
1351 } else if(ps_global->VAR_OPER_DIR){
1352 build_path(filename, ps_global->VAR_OPER_DIR, temp_filename, len);
1353 } else {
1354 build_path(filename,
1355 #ifdef IS_WINDOWS
1356 ps_global->folders_dir,
1357 #else /* UNIX */
1358 ps_global->home_dir,
1359 #endif /* UNIX */
1360 temp_filename, len);
1363 dprint((5, "returning \"%s\"\n", filename ? filename : "?"));
1364 return(1);
1369 /*----------------------------------------------------------------------
1370 Expunge (if confirmed) and close a mail stream
1372 Args: stream -- The MAILSTREAM * to close
1373 final_msg -- If non-null, this should be set to point to a
1374 message to print out in the caller, it is allocated
1375 here and freed by the caller.
1377 Result: Mail box is expunged and closed. A message is displayed to
1378 say what happened
1379 ----*/
1380 void
1381 expunge_and_close(MAILSTREAM *stream, char **final_msg, long unsigned int flags)
1383 long i, delete_count, seen_not_del;
1384 char buff1[MAX_SCREEN_COLS+1], *moved_msg = NULL,
1385 buff2[MAX_SCREEN_COLS+1], *folder;
1386 CONTEXT_S *context;
1387 struct variable *vars = ps_global->vars;
1388 int ret, expunge = FALSE, no_close = 0;
1389 char ing[4];
1391 no_close = (flags & EC_NO_CLOSE);
1393 if(!(stream && sp_flagged(stream, SP_LOCKED)))
1394 stream = NULL;
1396 /* check for dead stream */
1397 if(stream && sp_dead_stream(stream)){
1398 pine_mail_actually_close(stream);
1399 stream = NULL;
1402 if(stream != NULL){
1403 context = sp_context(stream);
1404 folder = STREAMNAME(stream);
1406 dprint((2, "expunge_and_close: \"%s\"%s\n",
1407 folder, no_close ? " (NO_CLOSE bit set)" : ""));
1409 update_folder_unseen_by_stream(stream, UFU_NONE);
1411 if(final_msg)
1412 strncpy(ing, "ed", sizeof(ing));
1413 else
1414 strncpy(ing, "ing", sizeof(ing));
1416 ing[sizeof(ing)-1] = '\0';
1418 buff1[0] = '\0';
1419 buff2[0] = '\0';
1421 if(!stream->rdonly){
1423 if(pith_opt_begin_closing)
1424 (*pith_opt_begin_closing)(flags, folder);
1426 mail_expunge_prefilter(stream, MI_CLOSING);
1429 * Be sure to expunge any excluded (filtered) msgs
1430 * Do it here so they're not copied into read/archived
1431 * folders *AND* to be sure we don't refilter them
1432 * next time the folder's opened.
1434 for(i = 1L; i <= stream->nmsgs; i++)
1435 if(get_lflag(stream, NULL, i, MN_EXLD)){ /* if there are any */
1436 delete_filtered_msgs(stream); /* delete them all */
1437 expunge = TRUE;
1438 break;
1441 /* Save read messages? */
1442 if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0]
1443 && sp_flagged(stream, SP_INBOX)
1444 && (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL))){
1446 if(F_ON(F_AUTO_READ_MSGS,ps_global)
1447 || (pith_opt_read_msg_prompt
1448 && (*pith_opt_read_msg_prompt)(seen_not_del, VAR_READ_MESSAGE_FOLDER)))
1449 /* move inbox's read messages */
1450 moved_msg = move_read_msgs(stream, VAR_READ_MESSAGE_FOLDER,
1451 buff1, sizeof(buff1), -1L);
1453 else if(VAR_ARCHIVED_FOLDERS)
1454 moved_msg = move_read_incoming(stream, context, folder,
1455 VAR_ARCHIVED_FOLDERS,
1456 buff1, sizeof(buff1));
1459 * We need the count_flagged to be executed not only to set
1460 * delete_count, but also to set the searched bits in all of
1461 * the deleted messages. The searched bit is used in the monkey
1462 * business section below which undeletes deleted messages
1463 * before expunging. It determines which messages are deleted
1464 * by examining the searched bit, which had better be set or not
1465 * based on this count_flagged call rather than some random
1466 * search that happened earlier.
1468 delete_count = count_flagged(stream, F_DEL);
1469 if(F_ON(F_EXPUNGE_MANUALLY,ps_global))
1470 delete_count = 0L;
1472 ret = 'n';
1473 if(!ps_global->noexpunge_on_close && delete_count){
1475 if(F_ON(F_FULL_AUTO_EXPUNGE,ps_global)
1476 || (F_ON(F_AUTO_EXPUNGE, ps_global)
1477 && ((!strucmp(folder,ps_global->inbox_name))
1478 || (context && (context->use & CNTXT_INCMNG)))
1479 && context_isambig(folder))){
1480 ret = 'y';
1482 else if(pith_opt_expunge_prompt){
1483 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1484 ret = (*pith_opt_expunge_prompt)(stream, pretty_fn((char *)fname), delete_count);
1485 if(fname) fs_give((void **) &fname);
1488 /* get this message back in queue */
1489 if(moved_msg)
1490 q_status_message(SM_ORDER,
1491 F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);
1493 if(ret == 'y'){
1494 long filtered;
1495 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1497 filtered = any_lflagged(sp_msgmap(stream), MN_EXLD);
1499 snprintf(buff2, sizeof(buff2),
1500 "%s%s%s%.30s%s%s %s message%s and remov%s %s.",
1501 no_close ? "" : "Clos",
1502 no_close ? "" : ing,
1503 no_close ? "" : " \"",
1504 no_close ? "" : pretty_fn((char *)fname),
1505 no_close ? "" : "\". ",
1506 final_msg ? "Kept" : "Keeping",
1507 comatose(stream->nmsgs - filtered - delete_count),
1508 plural(stream->nmsgs - filtered - delete_count),
1509 ing,
1510 long2string(delete_count));
1511 if(fname) fs_give((void **)&fname);
1512 if(final_msg)
1513 *final_msg = cpystr(buff2);
1514 else
1515 q_status_message(SM_ORDER,
1516 no_close ? 1 :
1517 (F_ON(F_AUTO_EXPUNGE,ps_global)
1518 || F_ON(F_FULL_AUTO_EXPUNGE,ps_global))
1519 ? 0 : 3,
1520 5, buff2);
1522 flush_status_messages(1);
1523 ps_global->mm_log_error = 0;
1524 ps_global->expunge_in_progress = 1;
1525 mail_expunge(stream);
1526 ps_global->expunge_in_progress = 0;
1527 if(ps_global->mm_log_error && final_msg && *final_msg){
1528 fs_give((void **)final_msg);
1529 *final_msg = NULL;
1534 if(ret != 'y'){
1535 if(!ps_global->noexpunge_on_close && expunge){
1536 MESSAGECACHE *mc;
1537 char *seq;
1538 int expbits;
1541 * filtered message monkey business.
1542 * The Plan:
1543 * 1) light sequence bits for legit deleted msgs
1544 * and store marker in local extension
1545 * 2) clear their deleted flag
1546 * 3) perform expunge to removed filtered msgs
1547 * 4) restore deleted flags for legit msgs
1548 * based on local extension bit
1550 for(i = 1L; i <= stream->nmsgs; i++)
1551 if(!get_lflag(stream, NULL, i, MN_EXLD)
1552 && (((mc = mail_elt(stream, i)) && mc->valid && mc->deleted)
1553 || (mc && !mc->valid && mc->searched))){
1554 mc->sequence = 1;
1555 expbits = MSG_EX_DELETE;
1556 msgno_exceptions(stream, i, "0", &expbits, TRUE);
1558 else if((mc = mail_elt(stream, i)) != NULL)
1559 mc->sequence = 0;
1561 if((seq = build_sequence(stream, NULL, NULL)) != NULL){
1562 mail_flag(stream, seq, "\\DELETED", ST_SILENT);
1563 fs_give((void **) &seq);
1566 ps_global->mm_log_error = 0;
1567 ps_global->expunge_in_progress = 1;
1568 mail_expunge(stream);
1569 ps_global->expunge_in_progress = 0;
1571 for(i = 1L; i <= stream->nmsgs; i++)
1572 if((mc = mail_elt(stream, i)) != NULL)
1573 mc->sequence
1574 = (msgno_exceptions(stream, i, "0", &expbits, FALSE)
1575 && (expbits & MSG_EX_DELETE));
1577 if((seq = build_sequence(stream, NULL, NULL)) != NULL){
1578 mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
1579 fs_give((void **) &seq);
1583 if(!no_close){ /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1584 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1585 if(stream->nmsgs){
1586 snprintf(buff2, sizeof(buff2),
1587 "Clos%s folder \"%.*s\". %s%s%s message%s.",
1588 ing,
1589 MAX_SCREEN_COLS+1-50, pretty_fn((char *) fname),
1590 final_msg ? "Kept" : "Keeping",
1591 (stream->nmsgs == 1L) ? " single" : " all ",
1592 (stream->nmsgs > 1L)
1593 ? comatose(stream->nmsgs) : "",
1594 plural(stream->nmsgs));
1596 else{ /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1597 snprintf(buff2, sizeof(buff2), "Clos%s empty folder \"%.*s\"",
1598 ing, MAX_SCREEN_COLS+1-50, pretty_fn((char *) fname));
1600 if(fname) fs_give((void **)&fname);
1602 if(final_msg)
1603 *final_msg = cpystr(buff2);
1604 else
1605 q_status_message(SM_ORDER, 0, 3, buff2);
1609 else{
1610 if(IS_NEWS(stream)){
1612 * Mark the filtered messages deleted so they aren't
1613 * filtered next time.
1615 for(i = 1L; i <= stream->nmsgs; i++){
1616 int exbits;
1617 if(msgno_exceptions(stream, i, "0" , &exbits, FALSE)
1618 && (exbits & MSG_EX_FILTERED)){
1619 delete_filtered_msgs(stream);
1620 break;
1623 /* first, look to archive read messages */
1624 if((moved_msg = move_read_incoming(stream, context, folder,
1625 VAR_ARCHIVED_FOLDERS,
1626 buff1, sizeof(buff1))) != NULL)
1627 q_status_message(SM_ORDER,
1628 F_ON(F_AUTO_READ_MSGS,ps_global) ? 0 : 3, 5, moved_msg);
1629 /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1630 snprintf(buff2, sizeof(buff2), "Clos%s news group \"%.*s\"",
1631 ing, MAX_SCREEN_COLS+1-50, pretty_fn(folder));
1633 if(F_ON(F_NEWS_CATCHUP, ps_global)){
1634 MESSAGECACHE *mc;
1636 /* count visible messages */
1637 (void) count_flagged(stream, F_DEL);
1638 for(i = 1L, delete_count = 0L; i <= stream->nmsgs; i++)
1639 if(!(get_lflag(stream, NULL, i, MN_EXLD)
1640 || ((mc = mail_elt(stream, i)) && mc->valid
1641 && mc->deleted)
1642 || (mc && !mc->valid && mc->searched)))
1643 delete_count++;
1645 if(delete_count && pith_opt_expunge_prompt){
1646 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1647 ret = (*pith_opt_expunge_prompt)(stream, pretty_fn((char *) fname), delete_count);
1648 if(fname) fs_give((void **)&fname);
1649 if(ret == 'y'){
1650 char seq[64];
1652 snprintf(seq, sizeof(seq), "1:%ld", stream->nmsgs);
1653 mail_flag(stream, seq, "\\DELETED", ST_SET|ST_SILENT);
1658 if(F_ON(F_NEWS_CROSS_DELETE, ps_global))
1659 cross_delete_crossposts(stream);
1661 else{ /* MAX_SCREEN_COLS+1 = sizeof(buff2) */
1662 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
1663 snprintf(buff2, sizeof(buff2),
1664 "Clos%s read-only folder \"%.*s\". No changes to save",
1665 ing, MAX_SCREEN_COLS+1-60, pretty_fn((char *) fname));
1666 if(fname) fs_give((void **)&fname);
1669 if(final_msg)
1670 *final_msg = cpystr(buff2);
1671 else
1672 q_status_message(SM_ORDER, 0, 2, buff2);
1676 * Make darn sure any mm_log fallout caused above gets seen...
1678 if(!no_close){
1679 flush_status_messages(1);
1680 pine_mail_close(stream);
1686 void
1687 agg_select_all(MAILSTREAM *stream, MSGNO_S *msgmap, long int *diff, int on)
1689 long i;
1690 int hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
1692 for(i = 1L; i <= mn_get_total(msgmap); i++){
1693 if(on){ /* mark 'em all */
1694 set_lflag(stream, msgmap, i, MN_SLCT, 1);
1696 else { /* unmark 'em all */
1697 if(get_lflag(stream, msgmap, i, MN_SLCT)){
1698 if(diff)
1699 (*diff)++;
1701 set_lflag(stream, msgmap, i, MN_SLCT, 0);
1703 else if(hidden)
1704 set_lflag(stream, msgmap, i, MN_HIDE, 0);
1710 /*----------------------------------------------------------------------
1711 Move all read messages from srcfldr to dstfldr
1713 Args: stream -- stream to usr
1714 dstfldr -- folder to receive moved messages
1715 buf -- place to write success message
1717 Returns: success message or NULL for failure
1718 ----*/
1719 char *
1720 move_read_msgs(MAILSTREAM *stream, char *dstfldr, char *buf, size_t buflen, long int searched)
1722 long i, raw;
1723 int we_cancel = 0;
1724 MSGNO_S *msgmap = NULL;
1725 CONTEXT_S *save_context = NULL;
1726 char *bufp = NULL;
1727 MESSAGECACHE *mc;
1729 if(!is_absolute_path(dstfldr)
1730 && !(save_context = default_save_context(ps_global->context_list)))
1731 save_context = ps_global->context_list;
1734 * Use the "searched" bit to select the set of messages
1735 * we want to save. If searched is non-neg, the message
1736 * cache already has the necessary "searched" bits set.
1738 if(searched < 0L)
1739 searched = count_flagged(stream, F_SEEN | F_UNDEL);
1741 if(searched){
1743 * We're going to be messing with SLCT flags in order
1744 * to do our work. If this stream is a StayOpen stream
1745 * we want to restore those flags after we're done
1746 * using them. So copy them into STMP so we can put them
1747 * back below.
1749 msgmap = sp_msgmap(stream);
1750 if(sp_flagged(stream, SP_PERMLOCKED))
1751 copy_lflags(stream, msgmap, MN_SLCT, MN_STMP);
1753 set_lflags(stream, msgmap, MN_SLCT, 0);
1755 /* select search results */
1756 for(i = 1L; i <= mn_get_total(msgmap); i++)
1757 if((raw = mn_m2raw(msgmap, i)) > 0L && stream
1758 && raw <= stream->nmsgs
1759 && (mc = mail_elt(stream,raw))
1760 && ((mc->valid && mc->seen && !mc->deleted)
1761 || (!mc->valid && mc->searched)))
1762 set_lflag(stream, msgmap, i, MN_SLCT, 1);
1764 pseudo_selected(stream, msgmap);
1765 snprintf(buf, buflen, "Moving %s read message%s to \"%s\"",
1766 comatose(searched), plural(searched), dstfldr);
1767 we_cancel = busy_cue(buf, NULL, 0);
1768 if(save(ps_global, stream, save_context, dstfldr, msgmap,
1769 SV_DELETE | SV_FIX_DELS | SV_INBOXWOCNTXT) == searched)
1770 strncpy(bufp = buf + 1, "Moved", MIN(5,buflen)); /* change Moving to Moved */
1772 buf[buflen-1] = '\0';
1773 if(we_cancel)
1774 cancel_busy_cue(bufp ? 0 : -1);
1776 if(sp_flagged(stream, SP_PERMLOCKED)){
1777 restore_selected(msgmap);
1778 copy_lflags(stream, msgmap, MN_STMP, MN_SLCT);
1782 return(bufp);
1786 /*----------------------------------------------------------------------
1787 Move read messages from folder if listed in archive
1789 Args:
1791 ----*/
1792 char *
1793 move_read_incoming(MAILSTREAM *stream, CONTEXT_S *context, char *folder,
1794 char **archive, char *buf, size_t buflen)
1796 char *s, *d, *f = folder;
1797 long seen_undel;
1799 if(buf && buflen > 0)
1800 buf[0] = '\0';
1802 if(archive && !sp_flagged(stream, SP_INBOX)
1803 && context && (context->use & CNTXT_INCMNG)
1804 && ((context_isambig(folder)
1805 && folder_is_nick(folder, FOLDERS(context), 0))
1806 || folder_index(folder, context, FI_FOLDER) > 0)
1807 && (seen_undel = count_flagged(stream, F_SEEN | F_UNDEL))){
1809 for(; f && *archive; archive++){
1810 char *p;
1812 get_pair(*archive, &s, &d, 1, 0);
1813 if(s && d
1814 && (!strcmp(s, folder)
1815 || (context_isambig(folder)
1816 && (p = folder_is_nick(folder, FOLDERS(context), 0))
1817 && !strcmp(s, p)))){
1818 if(F_ON(F_AUTO_READ_MSGS,ps_global)
1819 || (pith_opt_read_msg_prompt
1820 && (*pith_opt_read_msg_prompt)(seen_undel, d)))
1821 buf = move_read_msgs(stream, d, buf, buflen, seen_undel);
1823 f = NULL; /* bust out after cleaning up */
1826 fs_give((void **)&s);
1827 fs_give((void **)&d);
1831 return((buf && *buf) ? buf : NULL);
1835 /*----------------------------------------------------------------------
1836 Delete all references to a deleted news posting
1839 ---*/
1840 void
1841 cross_delete_crossposts(MAILSTREAM *stream)
1843 if(count_flagged(stream, F_DEL)){
1844 static char *fields[] = {"Xref", NULL};
1845 MAILSTREAM *tstream;
1846 CONTEXT_S *fake_context;
1847 char *xref, *p, *group, *uidp,
1848 *newgrp, newfolder[MAILTMPLEN];
1849 long i, hostlatch = 0L;
1850 imapuid_t uid;
1851 int we_cancel = 0;
1852 MESSAGECACHE *mc;
1854 strncpy(newfolder, stream->mailbox, sizeof(newfolder));
1855 newfolder[sizeof(newfolder)-1] = '\0';
1856 if(!(newgrp = strstr(newfolder, "#news.")))
1857 return; /* weird mailbox */
1859 newgrp += 6;
1861 we_cancel = busy_cue("Busy deleting crosspostings", NULL, 1);
1863 /* build subscribed list */
1864 strncpy(newgrp, "[]", sizeof(newfolder)-(newgrp-newfolder));
1865 newfolder[sizeof(newfolder)-1] = '\0';
1866 fake_context = new_context(newfolder, 0);
1867 build_folder_list(NULL, fake_context, "*", NULL, BFL_LSUB);
1869 for(i = 1L; i <= stream->nmsgs; i++)
1870 if(!get_lflag(stream, NULL, i, MN_EXLD)
1871 && (mc = mail_elt(stream, i)) && mc->deleted){
1873 if((xref = pine_fetchheader_lines(stream, i, NULL, fields)) != NULL){
1874 if((p = strstr(xref, ": ")) != NULL){
1875 p += 2;
1876 hostlatch = 0L;
1877 while(*p){
1878 group = p;
1879 uidp = NULL;
1881 /* get server */
1882 while(*++p && !isspace((unsigned char) *p))
1883 if(*p == ':'){
1884 *p = '\0';
1885 uidp = p + 1;
1888 /* tie off uid/host */
1889 if(*p)
1890 *p++ = '\0';
1892 if(uidp){
1894 * For the nonce, we're only deleting valid
1895 * uid's from outside the current newsgroup
1896 * and inside only subscribed newsgroups
1898 if(strcmp(group, stream->mailbox
1899 + (newgrp - newfolder))
1900 && folder_index(group, fake_context,
1901 FI_FOLDER) >= 0){
1902 if((uid = strtoul(uidp, NULL, 10)) != 0L){
1903 strncpy(newgrp, group, sizeof(newfolder)-(newgrp-newfolder));
1904 newfolder[sizeof(newfolder)-1] = '\0';
1905 if((tstream = pine_mail_open(NULL,
1906 newfolder,
1907 SP_USEPOOL,
1908 NULL)) != NULL){
1909 mail_flag(tstream, ulong2string(uid),
1910 "\\DELETED",
1911 ST_SET | ST_UID);
1912 pine_mail_close(tstream);
1915 else
1916 break; /* bogus uid */
1919 else if(!hostlatch++){
1920 char *p, *q;
1922 if(stream->mailbox[0] == '{'
1923 && !((p = strpbrk(stream->mailbox+1, "}:/"))
1924 && !struncmp(stream->mailbox + 1,
1925 q = canonical_name(group),
1926 p - (stream->mailbox + 1))
1927 && q[p - (stream->mailbox + 1)] == '\0'))
1928 break; /* different server? */
1930 else
1931 break; /* bogus field! */
1935 fs_give((void **) &xref);
1939 free_context(&fake_context);
1941 if(we_cancel)
1942 cancel_busy_cue(0);
1948 * Original version from Eduardo Chappa.
1950 * Returns a string describing the number of new/unseen messages
1951 * for use in the status line. Can return NULL. Caller must free the memory.
1953 char *
1954 new_messages_string(MAILSTREAM *stream)
1956 char message[80] = {'\0'};
1957 long new = 0L, uns = 0L;
1958 int i, imapstatus = 0;
1960 for (i = 0; ps_global->index_disp_format[i].ctype != iNothing
1961 && ps_global->index_disp_format[i].ctype != iIStatus
1962 && ps_global->index_disp_format[i].ctype != iSIStatus; i++)
1965 imapstatus = ps_global->index_disp_format[i].ctype == iIStatus
1966 || ps_global->index_disp_format[i].ctype == iSIStatus;
1968 get_new_message_count(stream, imapstatus, &new, &uns);
1970 if(imapstatus)
1971 snprintf(message, sizeof(message), " - %s%s%s%s%s%s%s",
1972 uns != 0L ? comatose((long) new) : "",
1973 uns != 0L ? " " : "",
1974 uns != 0L ? _("recent") : "",
1975 uns > 0L ? ", " : "",
1976 uns != -1L ? comatose((long) uns) : "",
1977 uns != -1L ? " " : "",
1978 uns != -1L ? _("unseen") : "");
1979 else if(!imapstatus && new > 0L)
1980 snprintf(message, sizeof(message), " - %s %s",
1981 comatose((long) new), _("new"));
1983 return(*message ? cpystr(message) : NULL);
1987 void
1988 get_new_message_count(MAILSTREAM *stream, int imapstatus,
1989 long *new, long *unseen)
1991 if(new)
1992 *new = 0L;
1994 if(unseen)
1995 *unseen = 0L;
1997 if(imapstatus){
1998 if(new)
1999 *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);
2001 if(!IS_NEWS(stream)){
2002 if(unseen)
2003 *unseen = count_flagged(stream, F_UNSEEN | F_UNDEL);
2005 else if(unseen)
2006 *unseen = -1L;
2008 else{
2009 if(IS_NEWS(stream)){
2010 if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global)){
2011 if(new)
2012 *new = count_flagged(stream, F_RECENT | F_UNSEEN | F_UNDEL);
2015 else{
2016 if(new)
2017 *new = count_flagged(stream, F_UNSEEN | F_UNDEL | F_UNANS);
2023 /*----------------------------------------------------------------------
2024 ZOOM the message index (set any and all necessary hidden flag bits)
2026 Args: state -- usual pine state
2027 msgmap -- usual message mapping
2028 Returns: number of messages zoomed in on
2030 ----*/
2031 long
2032 zoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int onflag)
2034 long i, count = 0L, first = 0L, msgno;
2035 PINETHRD_S *thrd = NULL, *topthrd = NULL, *nthrd;
2037 if(any_lflagged(msgmap, onflag)){
2039 if(THREADING() && sp_viewing_a_thread(stream)){
2040 /* get top of current thread */
2041 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
2042 if(thrd && thrd->top)
2043 topthrd = fetch_thread(stream, thrd->top);
2046 for(i = 1L; i <= mn_get_total(msgmap); i++){
2047 if(!get_lflag(stream, msgmap, i, onflag)){
2048 set_lflag(stream, msgmap, i, MN_HIDE, 1);
2050 else{
2053 * Because subject lines depend on whether or not
2054 * other parts of the thread above us are visible or not.
2056 if(THREADING() && !THRD_INDX()
2057 && ps_global->thread_disp_style == THREAD_MUTTLIKE)
2058 clear_index_cache_ent(stream, i, 0);
2061 * If a selected message is hidden beneath a collapsed
2062 * thread (not beneath a thread index line, but a collapsed
2063 * thread or subthread) then we make it visible. The user
2064 * should be able to see the selected messages when they
2065 * Zoom. We could get a bit fancier and re-collapse the
2066 * thread when the user unzooms, but we don't do that
2067 * for now.
2069 if(THREADING() && !THRD_INDX()
2070 && get_lflag(stream, msgmap, i, MN_CHID)){
2073 * What we need to do is to unhide this message and
2074 * uncollapse any parent above us.
2075 * Also, when we uncollapse a parent, we need to
2076 * trace back down the tree and unhide until we get
2077 * to a collapse point or the end. That's what
2078 * set_thread_subtree does.
2081 thrd = fetch_thread(stream, mn_m2raw(msgmap, i));
2083 if(thrd && thrd->parent)
2084 thrd = fetch_thread(stream, thrd->parent);
2085 else
2086 thrd = NULL;
2088 /* unhide and uncollapse its parents */
2089 while(thrd){
2090 /* if this parent is collapsed */
2091 if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
2092 /* uncollapse this parent and unhide its subtree */
2093 msgno = mn_raw2m(msgmap, thrd->rawno);
2094 if(msgno > 0L && msgno <= mn_get_total(msgmap)){
2095 set_lflag(stream, msgmap, msgno,
2096 MN_COLL | MN_CHID, 0);
2097 if(thrd->next &&
2098 (nthrd = fetch_thread(stream, thrd->next)))
2099 set_thread_subtree(stream, nthrd, msgmap,
2100 0, MN_CHID);
2103 /* collapse symbol will be wrong */
2104 clear_index_cache_ent(stream, msgno, 0);
2108 * Continue up tree to next parent looking for
2109 * more collapse points.
2111 if(thrd->parent)
2112 thrd = fetch_thread(stream, thrd->parent);
2113 else
2114 thrd = NULL;
2118 count++;
2119 if(!first){
2120 if(THRD_INDX()){
2121 /* find msgno of top of thread for msg i */
2122 if((thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
2123 && thrd->top)
2124 first = mn_raw2m(msgmap, thrd->top);
2126 else if(THREADING() && sp_viewing_a_thread(stream)){
2127 /* want first selected message in this thread */
2128 if(topthrd
2129 && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
2130 && thrd->top
2131 && topthrd->rawno == thrd->top)
2132 first = i;
2134 else
2135 first = i;
2140 if(THRD_INDX()){
2141 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
2142 if(count_lflags_in_thread(stream, thrd, msgmap, onflag) == 0)
2143 mn_set_cur(msgmap, first);
2145 else if((THREADING() && sp_viewing_a_thread(stream))
2146 || !get_lflag(stream, msgmap, mn_get_cur(msgmap), onflag)){
2147 if(!first){
2148 int flags = 0;
2151 * Nothing was selected in the thread we were in, so
2152 * drop back to the Thread Index instead. Set the current
2153 * thread to the first one that has a selection in it.
2156 unview_thread(state, stream, msgmap);
2158 i = next_sorted_flagged(F_UNDEL, stream, 1L, &flags);
2160 if(flags & NSF_FLAG_MATCH
2161 && (thrd=fetch_thread(stream, mn_m2raw(msgmap, i)))
2162 && thrd->top)
2163 first = mn_raw2m(msgmap, thrd->top);
2164 else
2165 first = 1L; /* can't happen */
2167 mn_set_cur(msgmap, first);
2169 else{
2170 if(msgline_hidden(stream, msgmap, mn_get_cur(msgmap), 0))
2171 mn_set_cur(msgmap, first);
2176 return(count);
2181 /*----------------------------------------------------------------------
2182 UnZOOM the message index (clear any and all hidden flag bits)
2184 Args: state -- usual pine state
2185 msgmap -- usual message mapping
2186 Returns: 1 if hidden bits to clear and they were, 0 if none to clear
2188 ----*/
2190 unzoom_index(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap)
2192 register long i;
2194 if(!any_lflagged(msgmap, MN_HIDE))
2195 return(0);
2197 for(i = 1L; i <= mn_get_total(msgmap); i++)
2198 set_lflag(stream, msgmap, i, MN_HIDE, 0);
2200 return(1);
2205 agg_text_select(MAILSTREAM *stream, MSGNO_S *msgmap, char type, char *namehdr,
2206 int not, int check_for_my_addresses,
2207 char *sstring, char *charset, SEARCHSET **limitsrch)
2209 int old_imap, we_cancel;
2210 int me_with_regex = 0;
2211 long searchflags;
2212 SEARCHPGM *srchpgm, *pgm, *secondpgm = NULL, *thirdpgm = NULL;
2213 SEARCHPGM *mepgm = NULL;
2215 if(!stream)
2216 return(1);
2218 old_imap = (is_imap_stream(stream) && !modern_imap_stream(stream));
2221 * Special case code for matching one of the user's addresses.
2223 if(check_for_my_addresses){
2224 char **t, *alt;
2226 if(F_OFF(F_DISABLE_REGEX, ps_global)){
2227 for(t = ps_global->VAR_ALT_ADDRS; !me_with_regex && t && t[0] && t[0][0]; t++){
2228 alt = (*t);
2229 if(contains_regex_special_chars(alt))
2230 me_with_regex++;
2235 * In this case we can't use search because it doesn't support
2236 * regex. So we have to manually do the whole thing ourselves.
2237 * The searching is done in the subroutine and the searched bits
2238 * will be set on return.
2240 if(me_with_regex){
2241 search_for_our_regex_addresses(stream, type, not, limitsrch ? *limitsrch : NULL);
2242 return(0);
2244 else{
2245 PATGRP_S *patgrp = NULL;
2246 PATTERN_S *p = NULL;
2247 PATTERN_S *pattern = NULL, **nextp;
2248 char buf[1000];
2251 * We're going to use the pattern matching machinery to generate
2252 * a search program. We build a pattern whose only purpose is
2253 * to generate the program.
2255 nextp = &pattern;
2257 /* add standard me addresses to list */
2258 if(ps_global->VAR_USER_ID){
2259 if(ps_global->userdomain && ps_global->userdomain[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->userdomain);
2264 p->substring = cpystr(buf);
2265 *nextp = p;
2266 nextp = &p->next;
2269 if(!ps_global->userdomain && ps_global->localdomain && ps_global->localdomain[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->localdomain);
2274 p->substring = cpystr(buf);
2275 *nextp = p;
2276 nextp = &p->next;
2279 if(!ps_global->userdomain && ps_global->hostname && ps_global->hostname[0]){
2280 p = (PATTERN_S *) fs_get(sizeof(*p));
2281 memset((void *) p, 0, sizeof(*p));
2282 snprintf(buf, sizeof(buf), "%s@%s", ps_global->VAR_USER_ID,
2283 ps_global->hostname);
2284 p->substring = cpystr(buf);
2285 *nextp = p;
2286 nextp = &p->next;
2290 /* add user's alternate addresses */
2291 for(t = ps_global->VAR_ALT_ADDRS; t && t[0] && t[0][0]; t++){
2292 alt = (*t);
2293 if(alt && alt[0]){
2294 p = (PATTERN_S *) fs_get(sizeof(*p));
2295 memset((void *) p, 0, sizeof(*p));
2296 p->substring = cpystr(alt);
2297 *nextp = p;
2298 nextp = &p->next;
2302 patgrp = (PATGRP_S *) fs_get(sizeof(*patgrp));
2303 memset((void *) patgrp, 0, sizeof(*patgrp));
2305 switch(type){
2306 case 'r' :
2307 patgrp->recip = pattern;
2308 break;
2309 case 'p' :
2310 patgrp->partic = pattern;
2311 break;
2312 case 'f' :
2313 patgrp->from = pattern;
2314 break;
2315 case 'c' :
2316 patgrp->cc = pattern;
2317 break;
2318 case 't' :
2319 patgrp->to = pattern;
2320 break;
2321 default :
2322 q_status_message(SM_ORDER, 3, 3, "Unhandled case in agg_text_select");
2323 break;
2326 mepgm = match_pattern_srchpgm(patgrp, stream, NULL);
2328 free_patgrp(&patgrp);
2332 if(mepgm){
2333 if(not && !old_imap){
2334 srchpgm = mail_newsearchpgm();
2335 srchpgm->not = mail_newsearchpgmlist();
2336 srchpgm->not->pgm = mepgm;
2338 else{
2339 srchpgm = mepgm;
2343 else{
2344 /* create a search program and fill it in */
2345 srchpgm = pgm = mail_newsearchpgm();
2346 if(not && !old_imap){
2347 srchpgm->not = mail_newsearchpgmlist();
2348 srchpgm->not->pgm = mail_newsearchpgm();
2349 pgm = srchpgm->not->pgm;
2353 if(!mepgm){
2354 switch(type){
2355 case 'g' : /* X-GM-EXT-1 */
2356 pgm->x_gm_ext1 = mail_newstringlist();
2357 pgm->x_gm_ext1->text.data = (unsigned char *) cpystr(namehdr);
2358 pgm->x_gm_ext1->text.size = strlen(namehdr);
2359 break;
2361 case 'h' : /* Any header */
2362 pgm->header = mail_newsearchheader (namehdr, sstring);
2363 break;
2365 case 'r' : /* TO or CC */
2366 if(old_imap){
2367 /* No OR on old servers */
2368 pgm->to = mail_newstringlist();
2369 pgm->to->text.data = (unsigned char *) cpystr(sstring);
2370 pgm->to->text.size = strlen(sstring);
2371 secondpgm = mail_newsearchpgm();
2372 secondpgm->cc = mail_newstringlist();
2373 secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
2374 secondpgm->cc->text.size = strlen(sstring);
2376 else{
2377 pgm->or = mail_newsearchor();
2378 pgm->or->first->to = mail_newstringlist();
2379 pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
2380 pgm->or->first->to->text.size = strlen(sstring);
2381 pgm->or->second->cc = mail_newstringlist();
2382 pgm->or->second->cc->text.data = (unsigned char *) cpystr(sstring);
2383 pgm->or->second->cc->text.size = strlen(sstring);
2386 break;
2388 case 'p' : /* TO or CC or FROM */
2389 if(old_imap){
2390 /* No OR on old servers */
2391 pgm->to = mail_newstringlist();
2392 pgm->to->text.data = (unsigned char *) cpystr(sstring);
2393 pgm->to->text.size = strlen(sstring);
2394 secondpgm = mail_newsearchpgm();
2395 secondpgm->cc = mail_newstringlist();
2396 secondpgm->cc->text.data = (unsigned char *) cpystr(sstring);
2397 secondpgm->cc->text.size = strlen(sstring);
2398 thirdpgm = mail_newsearchpgm();
2399 thirdpgm->from = mail_newstringlist();
2400 thirdpgm->from->text.data = (unsigned char *) cpystr(sstring);
2401 thirdpgm->from->text.size = strlen(sstring);
2403 else{
2404 pgm->or = mail_newsearchor();
2405 pgm->or->first->to = mail_newstringlist();
2406 pgm->or->first->to->text.data = (unsigned char *) cpystr(sstring);
2407 pgm->or->first->to->text.size = strlen(sstring);
2409 pgm->or->second->or = mail_newsearchor();
2410 pgm->or->second->or->first->cc = mail_newstringlist();
2411 pgm->or->second->or->first->cc->text.data =
2412 (unsigned char *) cpystr(sstring);
2413 pgm->or->second->or->first->cc->text.size = strlen(sstring);
2414 pgm->or->second->or->second->from = mail_newstringlist();
2415 pgm->or->second->or->second->from->text.data =
2416 (unsigned char *) cpystr(sstring);
2417 pgm->or->second->or->second->from->text.size = strlen(sstring);
2420 break;
2422 case 'f' : /* FROM */
2423 pgm->from = mail_newstringlist();
2424 pgm->from->text.data = (unsigned char *) cpystr(sstring);
2425 pgm->from->text.size = strlen(sstring);
2426 break;
2428 case 'c' : /* CC */
2429 pgm->cc = mail_newstringlist();
2430 pgm->cc->text.data = (unsigned char *) cpystr(sstring);
2431 pgm->cc->text.size = strlen(sstring);
2432 break;
2434 case 't' : /* TO */
2435 pgm->to = mail_newstringlist();
2436 pgm->to->text.data = (unsigned char *) cpystr(sstring);
2437 pgm->to->text.size = strlen(sstring);
2438 break;
2440 case 's' : /* SUBJECT */
2441 pgm->subject = mail_newstringlist();
2442 pgm->subject->text.data = (unsigned char *) cpystr(sstring);
2443 pgm->subject->text.size = strlen(sstring);
2444 break;
2446 case 'a' : /* ALL TEXT */
2447 pgm->text = mail_newstringlist();
2448 pgm->text->text.data = (unsigned char *) cpystr(sstring);
2449 pgm->text->text.size = strlen(sstring);
2450 break;
2452 case 'b' : /* ALL BODY TEXT */
2453 pgm->body = mail_newstringlist();
2454 pgm->body->text.data = (unsigned char *) cpystr(sstring);
2455 pgm->body->text.size = strlen(sstring);
2456 break;
2458 default :
2459 dprint((1,"\n - BOTCH: select_text unrecognized type\n"));
2460 return(1);
2464 * If we happen to have any messages excluded, make sure we
2465 * don't waste time searching their text...
2467 srchpgm->msgno = (limitsrch ? *limitsrch : NULL);
2469 /* TRANSLATORS: warning to user that we're busy selecting messages */
2470 we_cancel = busy_cue(_("Busy Selecting"), NULL, 1);
2472 searchflags = SE_NOPREFETCH | (secondpgm ? 0 : SE_FREE);
2474 pine_mail_search_full(stream, !old_imap ? charset : NULL, srchpgm,
2475 searchflags);
2477 /* search for To or Cc; or To or Cc or From on old imap server */
2478 if(secondpgm){
2479 if(srchpgm){
2480 srchpgm->msgno = NULL;
2481 mail_free_searchpgm(&srchpgm);
2484 secondpgm->msgno = (limitsrch ? *limitsrch : NULL);
2485 searchflags |= (SE_RETAIN | (thirdpgm ? 0 : SE_FREE));
2487 pine_mail_search_full(stream, NULL, secondpgm, searchflags);
2489 if(thirdpgm){
2490 if(secondpgm){
2491 secondpgm->msgno = NULL;
2492 mail_free_searchpgm(&secondpgm);
2495 thirdpgm->msgno = (limitsrch ? *limitsrch : NULL);
2496 searchflags |= SE_FREE;
2497 pine_mail_search_full(stream, NULL, thirdpgm, searchflags);
2501 /* we know this was freed in mail_search, let caller know */
2502 if(limitsrch)
2503 *limitsrch = NULL;
2505 if(old_imap && not){
2506 MESSAGECACHE *mc;
2507 long msgno;
2510 * Old imap server doesn't have a NOT, so we actually searched for
2511 * the subject (or whatever) instead of !subject. Flip the searched
2512 * bits.
2514 for(msgno = 1L; msgno <= mn_get_total(msgmap); msgno++)
2515 if(stream && msgno <= stream->nmsgs){
2516 if((mc = mail_elt(stream, msgno)) != NULL)
2517 mc->searched = mc->searched ? NIL : T;
2521 if(we_cancel)
2522 cancel_busy_cue(0);
2524 return(0);
2528 void
2529 search_for_our_regex_addresses(MAILSTREAM *stream, char type, int not,
2530 SEARCHSET *searchset)
2532 long rawno, count = 0L;
2533 MESSAGECACHE *mc;
2534 ADDRESS *addr1 = NULL, *addr2 = NULL, *addr3 = NULL;
2535 ENVELOPE *env;
2536 SEARCHSET *s, *ss = NULL;
2537 extern MAILSTREAM *mm_search_stream;
2538 extern long mm_search_count;
2540 mm_search_count = 0L;
2541 mm_search_stream = stream;
2543 if(!stream)
2544 return;
2546 /* set searched bits to zero */
2547 for(rawno = 1L; rawno <= stream->nmsgs; rawno++)
2548 if((mc=mail_elt(stream, rawno)) != NULL)
2549 mc->searched = NIL;
2551 /* set sequence bits for envelopes we need */
2552 for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
2553 if((mc = mail_elt(stream, rawno)) != NULL){
2554 if((!searchset || in_searchset(searchset, (unsigned long) rawno))
2555 && !mc->private.msg.env){
2556 mc->sequence = 1;
2557 count++;
2559 else
2560 mc->sequence = 0;
2565 * Set up a searchset that will control the fetch ahead.
2567 if(count){
2568 ss = build_searchset(stream);
2569 if(ss){
2570 SEARCHSET **sset = NULL;
2572 mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count);
2574 /* this resets automatically after the first fetch */
2575 sset = (SEARCHSET **) mail_parameters(stream,
2576 GET_FETCHLOOKAHEAD,
2577 (void *) stream);
2578 if(sset)
2579 *sset = ss;
2583 for(s = searchset; s; s = s->next){
2584 for(rawno = s->first; rawno <= s->last; rawno++){
2585 env = pine_mail_fetchenvelope(stream, rawno);
2586 addr1 = addr2 = addr3 = NULL;
2587 switch(type){
2588 case 'r' :
2589 addr1 = env ? env->to : NULL;
2590 addr2 = env ? env->cc : NULL;
2591 break;
2592 case 'p' :
2593 addr1 = env ? env->to : NULL;
2594 addr2 = env ? env->cc : NULL;
2595 addr3 = env ? env->from : NULL;
2596 break;
2597 case 'f' :
2598 addr1 = env ? env->from : NULL;
2599 break;
2600 case 'c' :
2601 addr1 = env ? env->cc : NULL;
2602 break;
2603 break;
2604 case 't' :
2605 addr1 = env ? env->to : NULL;
2606 break;
2607 default :
2608 q_status_message(SM_ORDER, 3, 3, "Unhandled case2 in agg_text_select");
2609 break;
2612 if(addr1 && address_is_us(addr1, ps_global)){
2613 if(rawno > 0L && rawno <= stream->nmsgs
2614 && (mc=mail_elt(stream, rawno)) != NULL)
2615 mm_searched(stream, rawno);
2617 else if(addr2 && address_is_us(addr2, ps_global)){
2618 if(rawno > 0L && rawno <= stream->nmsgs
2619 && (mc=mail_elt(stream, rawno)) != NULL)
2620 mm_searched(stream, rawno);
2622 else if(addr3 && address_is_us(addr3, ps_global)){
2623 if(rawno > 0L && rawno <= stream->nmsgs
2624 && (mc=mail_elt(stream, rawno)) != NULL)
2625 mm_searched(stream, rawno);
2630 if(ss)
2631 mail_free_searchset(&ss);
2633 if(not){
2634 for(rawno = 1L; rawno <= stream->nmsgs; rawno++){
2635 if((mc=mail_elt(stream, rawno)) && mc->searched)
2636 mc->searched = NIL;
2637 else
2638 mc->searched = T;
2645 agg_flag_select(MAILSTREAM *stream, int not, int crit, SEARCHSET **limitsrch)
2647 SEARCHPGM *pgm;
2649 pgm = mail_newsearchpgm();
2650 switch(crit){
2651 case 'n' :
2652 if(not){
2653 SEARCHPGM *notpgm;
2655 /* this is the same as seen or deleted or answered */
2656 pgm->not = mail_newsearchpgmlist();
2657 notpgm = pgm->not->pgm = mail_newsearchpgm();
2658 notpgm->unseen = notpgm->undeleted = notpgm->unanswered = 1;
2660 else
2661 pgm->unseen = pgm->undeleted = pgm->unanswered = 1;
2663 break;
2665 case 'd' :
2666 if(not)
2667 pgm->undeleted = 1;
2668 else
2669 pgm->deleted = 1;
2671 break;
2673 case 'r' :
2674 if(not)
2675 pgm->old = 1;
2676 else
2677 pgm->recent = 1;
2679 break;
2681 case 'u' :
2682 if(not)
2683 pgm->seen = 1;
2684 else
2685 pgm->unseen = 1;
2687 break;
2689 case 'a':
2691 * Not a true "not", we are implicitly only interested in undeleted.
2693 if(not)
2694 pgm->unanswered = pgm->undeleted = 1;
2695 else
2696 pgm->answered = pgm->undeleted = 1;
2697 break;
2699 case 'f':
2701 STRINGLIST **slpp;
2703 for(slpp = (not) ? &pgm->unkeyword : &pgm->keyword;
2704 *slpp;
2705 slpp = &(*slpp)->next)
2708 *slpp = mail_newstringlist();
2709 (*slpp)->text.data = (unsigned char *) cpystr(FORWARDED_FLAG);
2710 (*slpp)->text.size = (unsigned long) strlen(FORWARDED_FLAG);
2713 break;
2715 case '*' :
2716 if(not)
2717 pgm->unflagged = 1;
2718 else
2719 pgm->flagged = 1;
2721 break;
2723 default :
2724 return(1);
2725 break;
2728 pgm->msgno = (limitsrch ? *limitsrch : NULL);
2729 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
2730 /* we know this was freed in mail_search, let caller know */
2731 if(limitsrch)
2732 *limitsrch = NULL;
2734 return(0);
2739 * Get the user name from the mailbox portion of an address.
2741 * Args: mailbox -- the mailbox portion of an address (lhs of address)
2742 * target -- a buffer to put the result in
2743 * len -- length of the target buffer
2745 * Returns the left most portion up to the first '%', ':' or '@',
2746 * and to the right of any '!' (as if c-client would give us such a mailbox).
2747 * Returns NULL if it can't find a username to point to.
2749 char *
2750 get_uname(char *mailbox, char *target, int len)
2752 int i, start, end;
2754 if(!mailbox || !*mailbox)
2755 return(NULL);
2757 end = strlen(mailbox) - 1;
2758 for(start = end; start > -1 && mailbox[start] != '!'; start--)
2759 if(strindex("%:@", mailbox[start]))
2760 end = start - 1;
2762 start++; /* compensate for either case above */
2764 for(i = start; i <= end && (i-start) < (len-1); i++) /* copy name */
2765 target[i-start] = isupper((unsigned char)mailbox[i])
2766 ? tolower((unsigned char)mailbox[i])
2767 : mailbox[i];
2769 target[i-start] = '\0'; /* tie it off */
2771 return(*target ? target : NULL);