mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2002,2010,2013 Michael R. Elkins <me@mutt.org>
3 * Copyright (C) 1999-2003 Thomas Roessler <roessler@does-not-exist.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#if HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include "mutt.h"
25#include "mx.h"
26#include "rfc2047.h"
27#include "sort.h"
28#include "mailbox.h"
29#include "copy.h"
30#include "keymap.h"
31#include "url.h"
32#ifdef USE_SIDEBAR
33#include "sidebar.h"
34#endif
35
36#ifdef USE_COMPRESSED
37#include "compress.h"
38#endif
39
40#ifdef USE_IMAP
41#include "imap.h"
42#endif
43
44#ifdef USE_POP
45#include "pop.h"
46#endif
47
48#include "buffy.h"
49
50#ifdef USE_DOTLOCK
51#include "dotlock.h"
52#endif
53
54#include "mutt_crypt.h"
55
56#include <dirent.h>
57#include <fcntl.h>
58#include <sys/file.h>
59#include <sys/stat.h>
60#include <errno.h>
61#include <unistd.h>
62#include <stdlib.h>
63#include <string.h>
64#include <ctype.h>
65#include <utime.h>
66
67struct mx_ops* mx_get_ops (int magic)
68{
69 switch (magic)
70 {
71#ifdef USE_IMAP
72 case MUTT_IMAP:
73 return &mx_imap_ops;
74#endif
75 case MUTT_MAILDIR:
76 return &mx_maildir_ops;
77 case MUTT_MBOX:
78 return &mx_mbox_ops;
79 case MUTT_MH:
80 return &mx_mh_ops;
81 case MUTT_MMDF:
82 return &mx_mmdf_ops;
83#ifdef USE_POP
84 case MUTT_POP:
85 return &mx_pop_ops;
86#endif
87#ifdef USE_COMPRESSED
88 case MUTT_COMPRESSED:
89 return &mx_comp_ops;
90#endif
91 default:
92 return NULL;
93 }
94}
95
96#define mutt_is_spool(s) (mutt_strcmp (Spoolfile, s) == 0)
97
98#ifdef USE_DOTLOCK
99/* parameters:
100 * path - file to lock
101 * retry - should retry if unable to lock?
102 */
103
104#ifdef DL_STANDALONE
105
106static int invoke_dotlock (const char *path, int dummy, int flags, int retry)
107{
108 char cmd[LONG_STRING + _POSIX_PATH_MAX];
109 char f[SHORT_STRING + _POSIX_PATH_MAX];
110 char r[SHORT_STRING];
111
112 if (flags & DL_FL_RETRY)
113 snprintf (r, sizeof (r), "-r %d ", retry ? MAXLOCKATTEMPT : 0);
114
115 mutt_quote_filename (f, sizeof (f), path);
116
117 snprintf (cmd, sizeof (cmd),
118 "%s %s%s%s%s%s%s%s",
119 NONULL (MuttDotlock),
120 flags & DL_FL_TRY ? "-t " : "",
121 flags & DL_FL_UNLOCK ? "-u " : "",
122 flags & DL_FL_USEPRIV ? "-p " : "",
123 flags & DL_FL_FORCE ? "-f " : "",
124 flags & DL_FL_UNLINK ? "-d " : "",
125 flags & DL_FL_RETRY ? r : "",
126 f);
127
128 return mutt_system (cmd);
129}
130
131#else
132
133#define invoke_dotlock dotlock_invoke
134
135#endif
136
137static int dotlock_file (const char *path, int fd, int retry)
138{
139 int r;
140 int flags = DL_FL_USEPRIV | DL_FL_RETRY;
141
142 if (retry) retry = 1;
143
144retry_lock:
145 if ((r = invoke_dotlock(path, fd, flags, retry)) == DL_EX_EXIST)
146 {
147 if (!option (OPTNOCURSES))
148 {
149 char msg[LONG_STRING];
150
151 snprintf(msg, sizeof(msg), _("Lock count exceeded, remove lock for %s?"),
152 path);
153 if(retry && mutt_yesorno(msg, MUTT_YES) == MUTT_YES)
154 {
155 flags |= DL_FL_FORCE;
156 retry--;
157 mutt_clear_error ();
158 goto retry_lock;
159 }
160 }
161 else
162 {
163 mutt_error ( _("Can't dotlock %s.\n"), path);
164 }
165 }
166 return (r == DL_EX_OK ? 0 : -1);
167}
168
169static int undotlock_file (const char *path, int fd)
170{
171 return (invoke_dotlock(path, fd, DL_FL_USEPRIV | DL_FL_UNLOCK, 0) == DL_EX_OK ?
172 0 : -1);
173}
174
175#endif /* USE_DOTLOCK */
176
177/* Args:
178 * excl if excl != 0, request an exclusive lock
179 * dot if dot != 0, try to dotlock the file
180 * timeout should retry locking?
181 */
182int mx_lock_file (const char *path, int fd, int excl, int dot, int timeout)
183{
184#if defined (USE_FCNTL) || defined (USE_FLOCK)
185 int count;
186 int attempt;
187 struct stat sb = { 0 }, prev_sb = { 0 }; /* silence gcc warnings */
188#endif
189 int r = 0;
190
191#ifdef USE_FCNTL
192 struct flock lck;
193
194 memset (&lck, 0, sizeof (struct flock));
195 lck.l_type = excl ? F_WRLCK : F_RDLCK;
196 lck.l_whence = SEEK_SET;
197
198 count = 0;
199 attempt = 0;
200 while (fcntl (fd, F_SETLK, &lck) == -1)
201 {
202 dprint(1,(debugfile, "mx_lock_file(): fcntl errno %d.\n", errno));
203 if (errno != EAGAIN && errno != EACCES)
204 {
205 mutt_perror ("fcntl");
206 return -1;
207 }
208
209 if (fstat (fd, &sb) != 0)
210 sb.st_size = 0;
211
212 if (count == 0)
213 prev_sb = sb;
214
215 /* only unlock file if it is unchanged */
216 if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
217 {
218 if (timeout)
219 mutt_error _("Timeout exceeded while attempting fcntl lock!");
220 return -1;
221 }
222
223 prev_sb = sb;
224
225 mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
226 sleep (1);
227 }
228#endif /* USE_FCNTL */
229
230#ifdef USE_FLOCK
231 count = 0;
232 attempt = 0;
233 while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
234 {
235 if (errno != EWOULDBLOCK)
236 {
237 mutt_perror ("flock");
238 r = -1;
239 break;
240 }
241
242 if (fstat(fd, &sb) != 0)
243 sb.st_size = 0;
244
245 if (count == 0)
246 prev_sb = sb;
247
248 /* only unlock file if it is unchanged */
249 if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
250 {
251 if (timeout)
252 mutt_error _("Timeout exceeded while attempting flock lock!");
253 r = -1;
254 break;
255 }
256
257 prev_sb = sb;
258
259 mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
260 sleep (1);
261 }
262#endif /* USE_FLOCK */
263
264#ifdef USE_DOTLOCK
265 if (r == 0 && dot)
266 r = dotlock_file (path, fd, timeout);
267#endif /* USE_DOTLOCK */
268
269 if (r != 0)
270 {
271 /* release any other locks obtained in this routine */
272
273#ifdef USE_FCNTL
274 lck.l_type = F_UNLCK;
275 fcntl (fd, F_SETLK, &lck);
276#endif /* USE_FCNTL */
277
278#ifdef USE_FLOCK
279 flock (fd, LOCK_UN);
280#endif /* USE_FLOCK */
281 }
282
283 return r;
284}
285
286int mx_unlock_file (const char *path, int fd, int dot)
287{
288#ifdef USE_FCNTL
289 struct flock unlockit = { F_UNLCK, 0, 0, 0, 0 };
290
291 memset (&unlockit, 0, sizeof (struct flock));
292 unlockit.l_type = F_UNLCK;
293 unlockit.l_whence = SEEK_SET;
294 fcntl (fd, F_SETLK, &unlockit);
295#endif
296
297#ifdef USE_FLOCK
298 flock (fd, LOCK_UN);
299#endif
300
301#ifdef USE_DOTLOCK
302 if (dot)
303 undotlock_file (path, fd);
304#endif
305
306 return 0;
307}
308
309static void mx_unlink_empty (const char *path)
310{
311 int fd;
312#ifndef USE_DOTLOCK
313 struct stat sb;
314#endif
315
316 if ((fd = open (path, O_RDWR)) == -1)
317 return;
318
319 if (mx_lock_file (path, fd, 1, 0, 1) == -1)
320 {
321 close (fd);
322 return;
323 }
324
325#ifdef USE_DOTLOCK
326 invoke_dotlock (path, fd, DL_FL_UNLINK, 1);
327#else
328 if (fstat (fd, &sb) == 0 && sb.st_size == 0)
329 unlink (path);
330#endif
331
332 mx_unlock_file (path, fd, 0);
333 close (fd);
334}
335
336/* try to figure out what type of mailbox ``path'' is
337 *
338 * return values:
339 * M_* mailbox type
340 * 0 not a mailbox
341 * -1 error
342 */
343
344#ifdef USE_IMAP
345
346int mx_is_imap(const char *p)
347{
348 url_scheme_t scheme;
349
350 if (!p)
351 return 0;
352
353 if (*p == '{')
354 return 1;
355
356 scheme = url_check_scheme (p);
357 if (scheme == U_IMAP || scheme == U_IMAPS)
358 return 1;
359
360 return 0;
361}
362
363#endif
364
365#ifdef USE_POP
366int mx_is_pop (const char *p)
367{
368 url_scheme_t scheme;
369
370 if (!p)
371 return 0;
372
373 scheme = url_check_scheme (p);
374 if (scheme == U_POP || scheme == U_POPS)
375 return 1;
376
377 return 0;
378}
379#endif
380
381int mx_get_magic (const char *path)
382{
383 struct stat st;
384 int magic = 0;
385 char tmp[_POSIX_PATH_MAX];
386 FILE *f;
387
388#ifdef USE_IMAP
389 if(mx_is_imap(path))
390 return MUTT_IMAP;
391#endif /* USE_IMAP */
392
393#ifdef USE_POP
394 if (mx_is_pop (path))
395 return MUTT_POP;
396#endif /* USE_POP */
397
398 if (stat (path, &st) == -1)
399 {
400 dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n",
401 path, strerror (errno), errno));
402 return (-1);
403 }
404
405 if (S_ISDIR (st.st_mode))
406 {
407 /* check for maildir-style mailbox */
408 if (mx_is_maildir (path))
409 return MUTT_MAILDIR;
410
411 /* check for mh-style mailbox */
412 if (mx_is_mh (path))
413 return MUTT_MH;
414 }
415 else if (st.st_size == 0)
416 {
417 /* hard to tell what zero-length files are, so assume the default magic */
418 if (DefaultMagic == MUTT_MBOX || DefaultMagic == MUTT_MMDF)
419 return (DefaultMagic);
420 else
421 return (MUTT_MBOX);
422 }
423 else if ((f = fopen (path, "r")) != NULL)
424 {
425 struct utimbuf times;
426 int ch;
427
428 /* Some mailbox creation tools erroneously append a blank line to
429 * a file before appending a mail message. This allows mutt to
430 * detect magic for and thus open those files. */
431 while ((ch = fgetc (f)) != EOF)
432 {
433 if (ch != '\n' && ch != '\r')
434 {
435 ungetc (ch, f);
436 break;
437 }
438 }
439
440 if (fgets (tmp, sizeof (tmp), f))
441 {
442 if (mutt_strncmp ("From ", tmp, 5) == 0)
443 magic = MUTT_MBOX;
444 else if (mutt_strcmp (MMDF_SEP, tmp) == 0)
445 magic = MUTT_MMDF;
446 }
447 safe_fclose (&f);
448
449 if (!option(OPTCHECKMBOXSIZE))
450 {
451 /* need to restore the times here, the file was not really accessed,
452 * only the type was accessed. This is important, because detection
453 * of "new mail" depends on those times set correctly.
454 */
455 times.actime = st.st_atime;
456 times.modtime = st.st_mtime;
457 utime (path, ×);
458 }
459 }
460 else
461 {
462 dprint (1, (debugfile, "mx_get_magic(): unable to open file %s for reading.\n",
463 path));
464 return (-1);
465 }
466
467#ifdef USE_COMPRESSED
468 /* If there are no other matches, see if there are any
469 * compress hooks that match */
470 if ((magic == 0) && mutt_comp_can_read (path))
471 return MUTT_COMPRESSED;
472#endif
473 return (magic);
474}
475
476/*
477 * set DefaultMagic to the given value
478 */
479int mx_set_magic (const char *s)
480{
481 if (ascii_strcasecmp (s, "mbox") == 0)
482 DefaultMagic = MUTT_MBOX;
483 else if (ascii_strcasecmp (s, "mmdf") == 0)
484 DefaultMagic = MUTT_MMDF;
485 else if (ascii_strcasecmp (s, "mh") == 0)
486 DefaultMagic = MUTT_MH;
487 else if (ascii_strcasecmp (s, "maildir") == 0)
488 DefaultMagic = MUTT_MAILDIR;
489 else
490 return (-1);
491
492 return 0;
493}
494
495/* mx_access: Wrapper for access, checks permissions on a given mailbox.
496 * We may be interested in using ACL-style flags at some point, currently
497 * we use the normal access() flags. */
498int mx_access (const char* path, int flags)
499{
500#ifdef USE_IMAP
501 if (mx_is_imap (path))
502 return imap_access (path, flags);
503#endif
504
505 return access (path, flags);
506}
507
508static int mx_open_mailbox_append (CONTEXT *ctx, int flags)
509{
510 struct stat sb;
511
512 ctx->append = 1;
513 ctx->magic = mx_get_magic (ctx->path);
514 if (ctx->magic == 0)
515 {
516 mutt_error (_("%s is not a mailbox."), ctx->path);
517 return -1;
518 }
519
520 if (ctx->magic < 0)
521 {
522 if (stat (ctx->path, &sb) == -1)
523 {
524 if (errno == ENOENT)
525 {
526#ifdef USE_COMPRESSED
527 if (mutt_comp_can_append (ctx))
528 ctx->magic = MUTT_COMPRESSED;
529 else
530#endif
531 ctx->magic = DefaultMagic;
532 flags |= MUTT_APPENDNEW;
533 }
534 else
535 {
536 mutt_perror (ctx->path);
537 return -1;
538 }
539 }
540 else
541 return -1;
542 }
543
544 ctx->mx_ops = mx_get_ops (ctx->magic);
545 if (!ctx->mx_ops || !ctx->mx_ops->open_append)
546 return -1;
547
548 return ctx->mx_ops->open_append (ctx, flags);
549}
550
551/*
552 * open a mailbox and parse it
553 *
554 * Args:
555 * flags MUTT_NOSORT do not sort mailbox
556 * MUTT_APPEND open mailbox for appending
557 * MUTT_READONLY open mailbox in read-only mode
558 * MUTT_QUIET only print error messages
559 * MUTT_PEEK revert atime where applicable
560 * ctx if non-null, context struct to use
561 */
562CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
563{
564 CONTEXT *ctx = pctx;
565 int rc;
566
567 if (!ctx)
568 ctx = safe_malloc (sizeof (CONTEXT));
569 memset (ctx, 0, sizeof (CONTEXT));
570 ctx->path = safe_strdup (path);
571 if (! (ctx->realpath = realpath (ctx->path, NULL)) )
572 ctx->realpath = safe_strdup (ctx->path);
573
574 ctx->msgnotreadyet = -1;
575 ctx->collapsed = 0;
576
577 for (rc=0; rc < RIGHTSMAX; rc++)
578 mutt_bit_set(ctx->rights,rc);
579
580 if (flags & MUTT_QUIET)
581 ctx->quiet = 1;
582 if (flags & MUTT_READONLY)
583 ctx->readonly = 1;
584 if (flags & MUTT_PEEK)
585 ctx->peekonly = 1;
586
587 if (flags & (MUTT_APPEND|MUTT_NEWFOLDER))
588 {
589 if (mx_open_mailbox_append (ctx, flags) != 0)
590 {
591 mx_fastclose_mailbox (ctx);
592 if (!pctx)
593 FREE (&ctx);
594 return NULL;
595 }
596 return ctx;
597 }
598
599 ctx->magic = mx_get_magic (path);
600 ctx->mx_ops = mx_get_ops (ctx->magic);
601
602 if (ctx->magic <= 0 || !ctx->mx_ops)
603 {
604 if (ctx->magic == 0 || !ctx->mx_ops)
605 mutt_error (_("%s is not a mailbox."), path);
606 else if (ctx->magic == -1)
607 mutt_perror(path);
608
609 mx_fastclose_mailbox (ctx);
610 if (!pctx)
611 FREE (&ctx);
612 return (NULL);
613 }
614
615 /* if the user has a `push' command in their .muttrc, or in a folder-hook,
616 * it will cause the progress messages not to be displayed because
617 * mutt_refresh() will think we are in the middle of a macro. so set a
618 * flag to indicate that we should really refresh the screen.
619 */
620 set_option (OPTFORCEREFRESH);
621
622 if (!ctx->quiet)
623 mutt_message (_("Reading %s..."), ctx->path);
624
625 rc = ctx->mx_ops->open(ctx);
626
627 if (rc == 0)
628 {
629 if ((flags & MUTT_NOSORT) == 0)
630 {
631 /* avoid unnecessary work since the mailbox is completely unthreaded
632 to begin with */
633 unset_option (OPTSORTSUBTHREADS);
634 unset_option (OPTNEEDRESCORE);
635 mutt_sort_headers (ctx, 1);
636 }
637 if (!ctx->quiet)
638 mutt_clear_error ();
639 }
640 else
641 {
642 mx_fastclose_mailbox (ctx);
643 if (!pctx)
644 FREE (&ctx);
645 }
646
647 unset_option (OPTFORCEREFRESH);
648 return (ctx);
649}
650
651/* free up memory associated with the mailbox context */
652void mx_fastclose_mailbox (CONTEXT *ctx)
653{
654 int i;
655 struct utimbuf ut;
656
657 if(!ctx)
658 return;
659
660 /* fix up the times so buffy won't get confused */
661 if (ctx->peekonly && ctx->path && (ctx->mtime > ctx->atime)) {
662 ut.actime = ctx->atime;
663 ut.modtime = ctx->mtime;
664 utime (ctx->path, &ut);
665 }
666
667 /* never announce that a mailbox we've just left has new mail. #3290
668 * XXX: really belongs in mx_close_mailbox, but this is a nice hook point */
669 if (!ctx->peekonly)
670 mutt_buffy_setnotified(ctx->path);
671
672 if (ctx->mx_ops)
673 ctx->mx_ops->close (ctx);
674
675#ifdef USE_COMPRESSED
676 mutt_free_compress_info (ctx);
677#endif /* USE_COMPRESSED */
678
679 if (ctx->subj_hash)
680 hash_destroy (&ctx->subj_hash, NULL);
681 if (ctx->id_hash)
682 hash_destroy (&ctx->id_hash, NULL);
683 mutt_clear_threads (ctx);
684 for (i = 0; i < ctx->msgcount; i++)
685 mutt_free_header (&ctx->hdrs[i]);
686 FREE (&ctx->hdrs);
687 FREE (&ctx->v2r);
688 FREE (&ctx->path);
689 FREE (&ctx->realpath);
690 FREE (&ctx->pattern);
691 if (ctx->limit_pattern)
692 mutt_pattern_free (&ctx->limit_pattern);
693 safe_fclose (&ctx->fp);
694 memset (ctx, 0, sizeof (CONTEXT));
695}
696
697/* save changes to disk */
698static int sync_mailbox (CONTEXT *ctx, int *index_hint)
699{
700 if (!ctx->mx_ops || !ctx->mx_ops->sync)
701 return -1;
702
703 if (!ctx->quiet)
704 mutt_message (_("Writing %s..."), ctx->path);
705
706 return ctx->mx_ops->sync (ctx, index_hint);
707}
708
709/* move deleted mails to the trash folder */
710static int trash_append (CONTEXT *ctx)
711{
712 CONTEXT ctx_trash;
713 int i;
714 struct stat st, stc;
715 int opt_confappend, rc;
716
717 if (!TrashPath || !ctx->deleted ||
718 (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
719 return 0;
720
721 for (i = 0; i < ctx->msgcount; i++)
722 if (ctx->hdrs[i]->deleted && (!ctx->hdrs[i]->purge))
723 break;
724 if (i == ctx->msgcount)
725 return 0; /* nothing to be done */
726
727 /* avoid the "append messages" prompt */
728 opt_confappend = option (OPTCONFIRMAPPEND);
729 if (opt_confappend)
730 unset_option (OPTCONFIRMAPPEND);
731 rc = mutt_save_confirm (TrashPath, &st);
732 if (opt_confappend)
733 set_option (OPTCONFIRMAPPEND);
734 if (rc != 0)
735 {
736 mutt_error _("message(s) not deleted");
737 return -1;
738 }
739
740 if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
741 && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
742 return 0; /* we are in the trash folder: simple sync */
743
744#ifdef USE_IMAP
745 if (Context->magic == MUTT_IMAP && mx_is_imap (TrashPath))
746 {
747 if (!imap_fast_trash (Context, TrashPath))
748 return 0;
749 }
750#endif
751
752 if (mx_open_mailbox (TrashPath, MUTT_APPEND, &ctx_trash) != NULL)
753 {
754 /* continue from initial scan above */
755 for (; i < ctx->msgcount ; i++)
756 if (ctx->hdrs[i]->deleted && (!ctx->hdrs[i]->purge))
757 {
758 if (mutt_append_message (&ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1)
759 {
760 mx_close_mailbox (&ctx_trash, NULL);
761 return -1;
762 }
763 }
764
765 mx_close_mailbox (&ctx_trash, NULL);
766 }
767 else
768 {
769 mutt_error _("Can't open trash folder");
770 return -1;
771 }
772
773 return 0;
774}
775
776/* save changes and close mailbox */
777int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
778{
779 int i, move_messages = 0, purge = 1, read_msgs = 0;
780 int check;
781 int isSpool = 0;
782 CONTEXT f;
783 char mbox[_POSIX_PATH_MAX];
784 char buf[SHORT_STRING];
785
786 if (!ctx) return 0;
787
788 ctx->closing = 1;
789
790 if (ctx->readonly || ctx->dontwrite || ctx->append)
791 {
792 mx_fastclose_mailbox (ctx);
793 return 0;
794 }
795
796 for (i = 0; i < ctx->msgcount; i++)
797 {
798 if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
799 && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
800 read_msgs++;
801#ifdef USE_SIDEBAR
802 if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->read)
803 ctx->unread--;
804 if (ctx->hdrs[i]->deleted && ctx->hdrs[i]->flagged)
805 ctx->flagged--;
806#endif
807 }
808
809 if (read_msgs && quadoption (OPT_MOVE) != MUTT_NO)
810 {
811 char *p;
812
813 if ((p = mutt_find_hook (MUTT_MBOXHOOK, ctx->path)))
814 {
815 isSpool = 1;
816 strfcpy (mbox, p, sizeof (mbox));
817 }
818 else
819 {
820 strfcpy (mbox, NONULL(Inbox), sizeof (mbox));
821 isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
822 }
823
824 if (isSpool && *mbox)
825 {
826 mutt_expand_path (mbox, sizeof (mbox));
827 snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
828 if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1)
829 {
830 ctx->closing = 0;
831 return (-1);
832 }
833 }
834 }
835
836 /*
837 * There is no point in asking whether or not to purge if we are
838 * just marking messages as "trash".
839 */
840 if (ctx->deleted && !(ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
841 {
842 snprintf (buf, sizeof (buf), ctx->deleted == 1
843 ? _("Purge %d deleted message?") : _("Purge %d deleted messages?"),
844 ctx->deleted);
845 if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
846 {
847 ctx->closing = 0;
848 return (-1);
849 }
850 }
851
852 if (option (OPTMARKOLD))
853 {
854 for (i = 0; i < ctx->msgcount; i++)
855 {
856 if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old && !ctx->hdrs[i]->read)
857 mutt_set_flag (ctx, ctx->hdrs[i], MUTT_OLD, 1);
858 }
859 }
860
861 if (move_messages)
862 {
863 if (!ctx->quiet)
864 mutt_message (_("Moving read messages to %s..."), mbox);
865
866#ifdef USE_IMAP
867 /* try to use server-side copy first */
868 i = 1;
869
870 if (ctx->magic == MUTT_IMAP && mx_is_imap (mbox))
871 {
872 /* tag messages for moving, and clear old tags, if any */
873 for (i = 0; i < ctx->msgcount; i++)
874 if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
875 && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
876 ctx->hdrs[i]->tagged = 1;
877 else
878 ctx->hdrs[i]->tagged = 0;
879
880 i = imap_copy_messages (ctx, NULL, mbox, 1);
881 }
882
883 if (i == 0) /* success */
884 mutt_clear_error ();
885 else if (i == -1) /* horrible error, bail */
886 {
887 ctx->closing=0;
888 return -1;
889 }
890 else /* use regular append-copy mode */
891#endif
892 {
893 if (mx_open_mailbox (mbox, MUTT_APPEND, &f) == NULL)
894 {
895 ctx->closing = 0;
896 return -1;
897 }
898
899 for (i = 0; i < ctx->msgcount; i++)
900 {
901 if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
902 && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
903 {
904 if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) == 0)
905 {
906 mutt_set_flag (ctx, ctx->hdrs[i], MUTT_DELETE, 1);
907 mutt_set_flag (ctx, ctx->hdrs[i], MUTT_PURGE, 1);
908 }
909 else
910 {
911 mx_close_mailbox (&f, NULL);
912 ctx->closing = 0;
913 return -1;
914 }
915 }
916 }
917
918 mx_close_mailbox (&f, NULL);
919 }
920
921 }
922 else if (!ctx->changed && ctx->deleted == 0)
923 {
924 if (!ctx->quiet)
925 mutt_message _("Mailbox is unchanged.");
926 if (ctx->magic == MUTT_MBOX || ctx->magic == MUTT_MMDF)
927 mbox_reset_atime (ctx, NULL);
928 mx_fastclose_mailbox (ctx);
929 return 0;
930 }
931
932 /* copy mails to the trash before expunging */
933 if (purge && ctx->deleted && mutt_strcmp (ctx->path, TrashPath))
934 {
935 if (trash_append (ctx) != 0)
936 {
937 ctx->closing = 0;
938 return -1;
939 }
940 }
941
942#ifdef USE_IMAP
943 /* allow IMAP to preserve the deleted flag across sessions */
944 if (ctx->magic == MUTT_IMAP)
945 {
946 if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0)
947 {
948 ctx->closing = 0;
949 return check;
950 }
951 }
952 else
953#endif
954 {
955 if (!purge)
956 {
957 for (i = 0; i < ctx->msgcount; i++)
958 {
959 ctx->hdrs[i]->deleted = 0;
960 ctx->hdrs[i]->purge = 0;
961 }
962 ctx->deleted = 0;
963 }
964
965 if (ctx->changed || ctx->deleted)
966 {
967 if ((check = sync_mailbox (ctx, index_hint)) != 0)
968 {
969 ctx->closing = 0;
970 return check;
971 }
972 }
973 }
974
975 if (!ctx->quiet)
976 {
977 if (move_messages)
978 mutt_message (_("%d kept, %d moved, %d deleted."),
979 ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
980 else
981 mutt_message (_("%d kept, %d deleted."),
982 ctx->msgcount - ctx->deleted, ctx->deleted);
983 }
984
985 if (ctx->msgcount == ctx->deleted &&
986 (ctx->magic == MUTT_MMDF || ctx->magic == MUTT_MBOX) &&
987 !mutt_is_spool(ctx->path) && !option (OPTSAVEEMPTY))
988 mx_unlink_empty (ctx->path);
989
990#ifdef USE_SIDEBAR
991 ctx->msgcount -= ctx->deleted;
992 mutt_sb_set_buffystats (ctx);
993#endif
994
995 mx_fastclose_mailbox (ctx);
996
997 return 0;
998}
999
1000
1001/* update a Context structure's internal tables. */
1002
1003void mx_update_tables(CONTEXT *ctx, int committing)
1004{
1005 int i, j;
1006
1007 /* update memory to reflect the new state of the mailbox */
1008 ctx->vcount = 0;
1009 ctx->vsize = 0;
1010 ctx->tagged = 0;
1011 ctx->deleted = 0;
1012 ctx->new = 0;
1013 ctx->unread = 0;
1014 ctx->changed = 0;
1015 ctx->flagged = 0;
1016#define this_body ctx->hdrs[j]->content
1017 for (i = 0, j = 0; i < ctx->msgcount; i++)
1018 {
1019 if ((committing && (!ctx->hdrs[i]->deleted ||
1020 (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))) ||
1021 (!committing && ctx->hdrs[i]->active))
1022 {
1023 if (i != j)
1024 {
1025 ctx->hdrs[j] = ctx->hdrs[i];
1026 ctx->hdrs[i] = NULL;
1027 }
1028 ctx->hdrs[j]->msgno = j;
1029 if (ctx->hdrs[j]->virtual != -1)
1030 {
1031 ctx->v2r[ctx->vcount] = j;
1032 ctx->hdrs[j]->virtual = ctx->vcount++;
1033 ctx->vsize += this_body->length + this_body->offset -
1034 this_body->hdr_offset;
1035 }
1036
1037 if (committing)
1038 ctx->hdrs[j]->changed = 0;
1039 else if (ctx->hdrs[j]->changed)
1040 ctx->changed++;
1041
1042 if (!committing || (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
1043 {
1044 if (ctx->hdrs[j]->deleted)
1045 ctx->deleted++;
1046 }
1047
1048 if (ctx->hdrs[j]->tagged)
1049 ctx->tagged++;
1050 if (ctx->hdrs[j]->flagged)
1051 ctx->flagged++;
1052 if (!ctx->hdrs[j]->read)
1053 {
1054 ctx->unread++;
1055 if (!ctx->hdrs[j]->old)
1056 ctx->new++;
1057 }
1058
1059 j++;
1060 }
1061 else
1062 {
1063 if (ctx->magic == MUTT_MH || ctx->magic == MUTT_MAILDIR)
1064 ctx->size -= (ctx->hdrs[i]->content->length +
1065 ctx->hdrs[i]->content->offset -
1066 ctx->hdrs[i]->content->hdr_offset);
1067 /* remove message from the hash tables */
1068 if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
1069 hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj, ctx->hdrs[i], NULL);
1070 if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
1071 hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id, ctx->hdrs[i], NULL);
1072 /* The path mx_check_mailbox() -> imap_check_mailbox() ->
1073 * imap_expunge_mailbox() -> mx_update_tables()
1074 * can occur before a call to mx_sync_mailbox(), resulting in
1075 * last_tag being stale if it's not reset here.
1076 */
1077 if (ctx->last_tag == ctx->hdrs[i])
1078 ctx->last_tag = NULL;
1079 mutt_free_header (&ctx->hdrs[i]);
1080 }
1081 }
1082#undef this_body
1083 ctx->msgcount = j;
1084}
1085
1086
1087/* save changes to mailbox
1088 *
1089 * return values:
1090 * 0 success
1091 * -1 error
1092 */
1093int mx_sync_mailbox (CONTEXT *ctx, int *index_hint)
1094{
1095 int rc, i;
1096 int purge = 1;
1097 int msgcount, deleted;
1098
1099 if (ctx->dontwrite)
1100 {
1101 char buf[STRING], tmp[STRING];
1102 if (km_expand_key (buf, sizeof(buf),
1103 km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
1104 snprintf (tmp, sizeof(tmp), _(" Press '%s' to toggle write"), buf);
1105 else
1106 strfcpy (tmp, _("Use 'toggle-write' to re-enable write!"), sizeof(tmp));
1107
1108 mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
1109 return -1;
1110 }
1111 else if (ctx->readonly)
1112 {
1113 mutt_error _("Mailbox is read-only.");
1114 return -1;
1115 }
1116
1117 if (!ctx->changed && !ctx->deleted)
1118 {
1119 if (!ctx->quiet)
1120 mutt_message _("Mailbox is unchanged.");
1121 return (0);
1122 }
1123
1124 if (ctx->deleted)
1125 {
1126 char buf[SHORT_STRING];
1127
1128 snprintf (buf, sizeof (buf), ctx->deleted == 1
1129 ? _("Purge %d deleted message?") : _("Purge %d deleted messages?"),
1130 ctx->deleted);
1131 if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
1132 return (-1);
1133 else if (purge == MUTT_NO)
1134 {
1135 if (!ctx->changed)
1136 return 0; /* nothing to do! */
1137 /* let IMAP servers hold on to D flags */
1138 if (ctx->magic != MUTT_IMAP)
1139 {
1140 for (i = 0 ; i < ctx->msgcount ; i++)
1141 {
1142 ctx->hdrs[i]->deleted = 0;
1143 ctx->hdrs[i]->purge = 0;
1144 }
1145 ctx->deleted = 0;
1146 }
1147 }
1148 else if (ctx->last_tag && ctx->last_tag->deleted)
1149 ctx->last_tag = NULL; /* reset last tagged msg now useless */
1150 }
1151
1152 /* really only for IMAP - imap_sync_mailbox results in a call to
1153 * mx_update_tables, so ctx->deleted is 0 when it comes back */
1154 msgcount = ctx->msgcount;
1155 deleted = ctx->deleted;
1156
1157 if (purge && ctx->deleted && mutt_strcmp (ctx->path, TrashPath))
1158 {
1159 if (trash_append (ctx) != 0)
1160 return -1;
1161 }
1162
1163#ifdef USE_IMAP
1164 if (ctx->magic == MUTT_IMAP)
1165 rc = imap_sync_mailbox (ctx, purge, index_hint);
1166 else
1167#endif
1168 rc = sync_mailbox (ctx, index_hint);
1169 if (rc == 0)
1170 {
1171#ifdef USE_IMAP
1172 if (ctx->magic == MUTT_IMAP && !purge)
1173 {
1174 if (!ctx->quiet)
1175 mutt_message _("Mailbox checkpointed.");
1176 }
1177 else
1178#endif
1179 {
1180 if (!ctx->quiet)
1181 mutt_message (_("%d kept, %d deleted."), msgcount - deleted,
1182 deleted);
1183 }
1184
1185 mutt_sleep (0);
1186
1187 if (ctx->msgcount == ctx->deleted &&
1188 (ctx->magic == MUTT_MBOX || ctx->magic == MUTT_MMDF) &&
1189 !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY))
1190 {
1191 unlink (ctx->path);
1192 mx_fastclose_mailbox (ctx);
1193 return 0;
1194 }
1195
1196 /* if we haven't deleted any messages, we don't need to resort */
1197 /* ... except for certain folder formats which need "unsorted"
1198 * sort order in order to synchronize folders.
1199 *
1200 * MH and maildir are safe. mbox-style seems to need re-sorting,
1201 * at least with the new threading code.
1202 */
1203 if (purge || (ctx->magic != MUTT_MAILDIR && ctx->magic != MUTT_MH))
1204 {
1205 /* IMAP does this automatically after handling EXPUNGE */
1206 if (ctx->magic != MUTT_IMAP)
1207 {
1208 mx_update_tables (ctx, 1);
1209 mutt_sort_headers (ctx, 1); /* rethread from scratch */
1210 }
1211 }
1212 }
1213
1214 return (rc);
1215}
1216
1217/* args:
1218 * dest destination mailbox
1219 * hdr message being copied (required for maildir support, because
1220 * the filename depends on the message flags)
1221 */
1222MESSAGE *mx_open_new_message (CONTEXT *dest, HEADER *hdr, int flags)
1223{
1224 ADDRESS *p = NULL;
1225 MESSAGE *msg;
1226
1227 if (!dest->mx_ops || !dest->mx_ops->open_new_msg)
1228 {
1229 dprint (1, (debugfile, "mx_open_new_message(): function unimplemented for mailbox type %d.\n",
1230 dest->magic));
1231 return NULL;
1232 }
1233
1234 msg = safe_calloc (1, sizeof (MESSAGE));
1235 msg->write = 1;
1236
1237 if (hdr)
1238 {
1239 msg->flags.flagged = hdr->flagged;
1240 msg->flags.replied = hdr->replied;
1241 msg->flags.read = hdr->read;
1242 msg->flags.draft = (flags & MUTT_SET_DRAFT) ? 1 : 0;
1243 msg->received = hdr->received;
1244 }
1245
1246 if(msg->received == 0)
1247 time(&msg->received);
1248
1249 if (dest->mx_ops->open_new_msg (msg, dest, hdr) == 0)
1250 {
1251 if (dest->magic == MUTT_MMDF)
1252 fputs (MMDF_SEP, msg->fp);
1253
1254 if ((dest->magic == MUTT_MBOX || dest->magic == MUTT_MMDF) &&
1255 flags & MUTT_ADD_FROM)
1256 {
1257 if (hdr)
1258 {
1259 if (hdr->env->return_path)
1260 p = hdr->env->return_path;
1261 else if (hdr->env->sender)
1262 p = hdr->env->sender;
1263 else
1264 p = hdr->env->from;
1265 }
1266
1267 fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL(Username), ctime (&msg->received));
1268 }
1269 }
1270 else
1271 FREE (&msg);
1272
1273 return msg;
1274}
1275
1276/* check for new mail */
1277int mx_check_mailbox (CONTEXT *ctx, int *index_hint)
1278{
1279 if (!ctx || !ctx->mx_ops)
1280 {
1281 dprint (1, (debugfile, "mx_check_mailbox: null or invalid context.\n"));
1282 return -1;
1283 }
1284
1285 return ctx->mx_ops->check (ctx, index_hint);
1286}
1287
1288/* return a stream pointer for a message */
1289MESSAGE *mx_open_message (CONTEXT *ctx, int msgno)
1290{
1291 MESSAGE *msg;
1292
1293 if (!ctx->mx_ops || !ctx->mx_ops->open_msg)
1294 {
1295 dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic));
1296 return NULL;
1297 }
1298
1299 msg = safe_calloc (1, sizeof (MESSAGE));
1300 if (ctx->mx_ops->open_msg (ctx, msg, msgno))
1301 FREE (&msg);
1302
1303 return msg;
1304}
1305
1306/* commit a message to a folder */
1307
1308int mx_commit_message (MESSAGE *msg, CONTEXT *ctx)
1309{
1310 if (!ctx->mx_ops || !ctx->mx_ops->commit_msg)
1311 return -1;
1312
1313 if (!(msg->write && ctx->append))
1314 {
1315 dprint (1, (debugfile, "mx_commit_message(): msg->write = %d, ctx->append = %d\n",
1316 msg->write, ctx->append));
1317 return -1;
1318 }
1319
1320 return ctx->mx_ops->commit_msg (ctx, msg);
1321}
1322
1323/* close a pointer to a message */
1324int mx_close_message (CONTEXT *ctx, MESSAGE **msg)
1325{
1326 int r = 0;
1327
1328 if (ctx->mx_ops && ctx->mx_ops->close_msg)
1329 r = ctx->mx_ops->close_msg (ctx, *msg);
1330
1331 if ((*msg)->path)
1332 {
1333 dprint (1, (debugfile, "mx_close_message (): unlinking %s\n",
1334 (*msg)->path));
1335 unlink ((*msg)->path);
1336 FREE (&(*msg)->path);
1337 }
1338
1339 FREE (msg); /* __FREE_CHECKED__ */
1340 return (r);
1341}
1342
1343void mx_alloc_memory (CONTEXT *ctx)
1344{
1345 int i;
1346 size_t s = MAX (sizeof (HEADER *), sizeof (int));
1347
1348 if ((ctx->hdrmax + 25) * s < ctx->hdrmax * s)
1349 {
1350 mutt_error _("Integer overflow -- can't allocate memory.");
1351 sleep (1);
1352 mutt_exit (1);
1353 }
1354
1355 if (ctx->hdrs)
1356 {
1357 safe_realloc (&ctx->hdrs, sizeof (HEADER *) * (ctx->hdrmax += 25));
1358 safe_realloc (&ctx->v2r, sizeof (int) * ctx->hdrmax);
1359 }
1360 else
1361 {
1362 ctx->hdrs = safe_calloc ((ctx->hdrmax += 25), sizeof (HEADER *));
1363 ctx->v2r = safe_calloc (ctx->hdrmax, sizeof (int));
1364 }
1365 for (i = ctx->msgcount ; i < ctx->hdrmax ; i++)
1366 {
1367 ctx->hdrs[i] = NULL;
1368 ctx->v2r[i] = -1;
1369 }
1370}
1371
1372/* this routine is called to update the counts in the context structure for
1373 * the last message header parsed.
1374 */
1375void mx_update_context (CONTEXT *ctx, int new_messages)
1376{
1377 HEADER *h;
1378 int msgno;
1379
1380 for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++)
1381 {
1382 h = ctx->hdrs[msgno];
1383
1384 if (WithCrypto)
1385 {
1386 /* NOTE: this _must_ be done before the check for mailcap! */
1387 h->security = crypt_query (h->content);
1388 }
1389
1390 if (!ctx->pattern)
1391 {
1392 ctx->v2r[ctx->vcount] = msgno;
1393 h->virtual = ctx->vcount++;
1394 }
1395 else
1396 h->virtual = -1;
1397 h->msgno = msgno;
1398
1399 if (h->env->supersedes)
1400 {
1401 HEADER *h2;
1402
1403 if (!ctx->id_hash)
1404 ctx->id_hash = mutt_make_id_hash (ctx);
1405
1406 h2 = hash_find (ctx->id_hash, h->env->supersedes);
1407
1408 /* FREE (&h->env->supersedes); should I ? */
1409 if (h2)
1410 {
1411 h2->superseded = 1;
1412 if (option (OPTSCORE))
1413 mutt_score_message (ctx, h2, 1);
1414 }
1415 }
1416
1417 /* add this message to the hash tables */
1418 if (ctx->id_hash && h->env->message_id)
1419 hash_insert (ctx->id_hash, h->env->message_id, h, 0);
1420 if (ctx->subj_hash && h->env->real_subj)
1421 hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
1422
1423 if (option (OPTSCORE))
1424 mutt_score_message (ctx, h, 0);
1425
1426 if (h->changed)
1427 ctx->changed = 1;
1428 if (h->flagged)
1429 ctx->flagged++;
1430 if (h->deleted)
1431 ctx->deleted++;
1432 if (!h->read)
1433 {
1434 ctx->unread++;
1435 if (!h->old)
1436 ctx->new++;
1437 }
1438 }
1439}
1440
1441/*
1442 * Return:
1443 * 1 if the specified mailbox contains 0 messages.
1444 * 0 if the mailbox contains messages
1445 * -1 on error
1446 */
1447int mx_check_empty (const char *path)
1448{
1449 switch (mx_get_magic (path))
1450 {
1451 case MUTT_MBOX:
1452 case MUTT_MMDF:
1453 return mbox_check_empty (path);
1454 case MUTT_MH:
1455 return mh_check_empty (path);
1456 case MUTT_MAILDIR:
1457 return maildir_check_empty (path);
1458 default:
1459 errno = EINVAL;
1460 return -1;
1461 }
1462 /* not reached */
1463}
1464
1465/* vim: set sw=2: */