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