Merge branch 'master' into nohook
[apps/madmutt.git] / lib-mx / 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 #include <lib-lib/lib-lib.h>
12
13 #include <lockfile.h>
14
15 #include <lib-lua/lib-lua.h>
16 #include <lib-sys/unix.h>
17 #include <lib-mime/mime.h>
18 #include <lib-ui/lib-ui.h>
19
20 #include "mutt.h"
21 #include "crypt.h"
22 #include "pattern.h"
23 #include "mx.h"
24 #include "mbox.h"
25 #include "mh.h"
26 #include "sort.h"
27 #include "thread.h"
28 #include "copy.h"
29 #include "keymap.h"
30 #include "compress.h"
31 #include "score.h"
32
33 #include <imap/imap.h>
34 #include "pop.h"
35
36 #define MAXLOCKATTEMPT 5
37
38
39 static mx_t const *mxfmts[] = {
40     &mbox_mx,
41     &mh_mx,
42     &maildir_mx,
43     &imap_mx,
44     &pop_mx,
45     &compress_mx,
46 };
47
48 #define MX_IDX(idx)          (idx >= 0 && idx < countof(mxfmts))
49 #define mutt_is_spool(s)     (m_strcmp(Spoolfile, s) == 0)
50
51 static int dotlock_file(const char *path, int retry)
52 {
53     char lockfile[_POSIX_PATH_MAX];
54     snprintf(lockfile, sizeof(lockfile), "%s.lock", path);
55
56     if (lockfile_create(lockfile, retry ? 1 : 0, 0)) {
57         if (retry)
58             mutt_error (_("Can't dotlock %s.\n"), lockfile);
59     }
60     return 0;
61 }
62
63 static int undotlock_file (const char *path)
64 {
65     char lockfile[_POSIX_PATH_MAX];
66     snprintf(lockfile, sizeof(lockfile), "%s.lock", path);
67     return lockfile_remove(lockfile);
68 }
69
70 /* looks up index of type for path in mxfmts */
71 static int mx_get_idx (const char* path) {
72     int i = 0, t = 0;
73     struct stat st;
74
75     /* first, test all non-local folders to avoid stat() call */
76     for (i = 0; i < countof(mxfmts); i++) {
77         if (!mxfmts[i]->local)
78             t = mxfmts[i]->mx_is_magic(path, NULL);
79         if (t >= 1)
80             return (t-1);
81     }
82     if (stat (path, &st) == 0) {
83         /* if stat() succeeded, keep testing until success and
84          * pass stat() info so that we only need to do it once */
85         for (i = 0; i < countof(mxfmts); i++) {
86             if (mxfmts[i]->local)
87                 t = mxfmts[i]->mx_is_magic(path, &st);
88             if (t >= 1)
89                 return (t-1);
90         }
91     }
92     return (-1);
93 }
94
95 /* Args:
96  *      excl            if excl != 0, request an exclusive lock
97  *      time_out        should retry locking?
98  */
99 int mx_lock_file(const char *path, int fd, int excl, int time_out)
100 {
101     int count = 0, attempt = 0;
102     struct flock lck = {
103         .l_type   = excl ? F_WRLCK : F_RDLCK,
104         .l_whence = SEEK_SET,
105     };
106
107     if (dotlock_file(path, time_out) < 0)
108         return -1;
109
110     while (fcntl(fd, F_SETLK, &lck) == -1) {
111         if (errno != EAGAIN && errno != EACCES) {
112             mutt_perror("fcntl");
113             goto error;
114         }
115
116         if (++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
117             if (time_out)
118                 mutt_error _("Timeout exceeded while attempting fcntl lock!");
119             goto error;
120         }
121         mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
122         mutt_sleep(1);
123     }
124     return 0;
125
126   error:
127     undotlock_file(path);
128     return -1;
129 }
130
131 int mx_unlock_file(const char *path, int fd)
132 {
133     struct flock unlockit;
134
135     p_clear(&unlockit, 1);
136     unlockit.l_type = F_UNLCK;
137     unlockit.l_whence = SEEK_SET;
138     fcntl(fd, F_SETLK, &unlockit);
139     undotlock_file(path);
140     return 0;
141 }
142
143 /* try to figure out what type of mailbox ``path'' is */
144 int mx_get_magic (const char *path) {
145   int i = 0;
146
147   if (m_strlen(path) == 0)
148     return (-1);
149   if ((i = mx_get_idx (path)) >= 0)
150     return (mxfmts[i]->type);
151   return (-1);
152 }
153
154 int mx_is_local (int m) {
155   if (!MX_IDX(m))
156     return (0);
157   return (mxfmts[m]->local);
158 }
159
160 /*
161  * set DefaultMagic to the given value
162  */
163 int mx_set_magic (const char *s)
164 {
165   if (ascii_strcasecmp (s, "mbox") == 0)
166     DefaultMagic = M_MBOX;
167   else if (ascii_strcasecmp (s, "mh") == 0)
168     DefaultMagic = M_MH;
169   else if (ascii_strcasecmp (s, "maildir") == 0)
170     DefaultMagic = M_MAILDIR;
171   else
172     return (-1);
173
174   return 0;
175 }
176
177 /* mx_access: Wrapper for access, checks permissions on a given mailbox.
178  *   We may be interested in using ACL-style flags at some point, currently
179  *   we use the normal access() flags. */
180 int mx_access (const char *path, int flags)
181 {
182   int i = 0;
183
184   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_access)
185     return (mxfmts[i]->mx_access(path,flags));
186   return (0);
187 }
188
189 static int mx_open_mailbox_append (CONTEXT * ctx, int flags)
190 {
191   struct stat sb;
192
193   /* special case for appending to compressed folders -
194    * even if we can not open them for reading */
195   if (mutt_can_append_compressed (ctx->path))
196     mutt_open_append_compressed (ctx);
197
198   ctx->append = 1;
199
200   if (mx_get_magic (ctx->path) == M_IMAP)
201     return imap_open_mailbox_append (ctx);
202
203   if (stat (ctx->path, &sb) == 0) {
204     ctx->magic = mx_get_magic (ctx->path);
205
206     switch (ctx->magic) {
207     case 0:
208       mutt_error (_("%s is not a mailbox."), ctx->path);
209       /* fall through */
210     case -1:
211       return (-1);
212     }
213   }
214   else if (errno == ENOENT) {
215     ctx->magic = DefaultMagic;
216
217     if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) {
218       char tmp[_POSIX_PATH_MAX];
219
220       if (mkdir (ctx->path, S_IRWXU)) {
221         mutt_perror (ctx->path);
222         return (-1);
223       }
224
225       if (ctx->magic == M_MAILDIR) {
226         snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
227         if (mkdir (tmp, S_IRWXU)) {
228           mutt_perror (tmp);
229           rmdir (ctx->path);
230           return (-1);
231         }
232
233         snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
234         if (mkdir (tmp, S_IRWXU)) {
235           mutt_perror (tmp);
236           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
237           rmdir (tmp);
238           rmdir (ctx->path);
239           return (-1);
240         }
241         snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path);
242         if (mkdir (tmp, S_IRWXU)) {
243           mutt_perror (tmp);
244           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
245           rmdir (tmp);
246           snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
247           rmdir (tmp);
248           rmdir (ctx->path);
249           return (-1);
250         }
251       }
252       else {
253         int i;
254
255         snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path);
256         if ((i = creat (tmp, S_IRWXU)) == -1) {
257           mutt_perror (tmp);
258           rmdir (ctx->path);
259           return (-1);
260         }
261         close (i);
262       }
263     }
264   }
265   else {
266     mutt_perror (ctx->path);
267     return (-1);
268   }
269
270   switch (ctx->magic) {
271   case M_MBOX:
272     if ((ctx->fp =
273          safe_fopen (ctx->path, flags & M_NEWFOLDER ? "w" : "a")) == NULL
274         || mbox_lock_mailbox(ctx, 1, 1) != 0) {
275       if (!ctx->fp)
276         mutt_perror (ctx->path);
277       else {
278         mutt_error (_("Couldn't lock %s\n"), ctx->path);
279         m_fclose(&ctx->fp);
280       }
281       return (-1);
282     }
283     fseeko (ctx->fp, 0, 2);
284     break;
285
286   case M_MH:
287   case M_MAILDIR:
288     /* nothing to do */
289     break;
290
291   default:
292     return (-1);
293   }
294
295   return 0;
296 }
297
298 /*
299  * open a mailbox and parse it
300  *
301  * Args:
302  *      flags   M_NOSORT        do not sort mailbox
303  *              M_APPEND        open mailbox for appending
304  *              M_READONLY      open mailbox in read-only mode
305  *              M_QUIET         only print error messages
306  *      ctx     if non-null, context struct to use
307  */
308 CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT * pctx)
309 {
310   CONTEXT *ctx = pctx;
311   int rc;
312
313   if (!ctx)
314     ctx = p_new(CONTEXT, 1);
315   p_clear(ctx, 1);
316   ctx->path = m_strdup(path);
317
318   ctx->msgnotreadyet = -1;
319   ctx->collapsed = 0;
320
321   if (flags & M_QUIET)
322     ctx->quiet = 1;
323   if (flags & M_READONLY)
324     ctx->readonly = 1;
325   if (flags & M_COUNT)
326     ctx->counting = 1;
327
328   if (flags & (M_APPEND | M_NEWFOLDER)) {
329     if (mx_open_mailbox_append (ctx, flags) != 0) {
330       mx_fastclose_mailbox (ctx);
331       if (!pctx)
332         p_delete(&ctx);
333       return NULL;
334     }
335     return ctx;
336   }
337
338   if (!MX_IDX(ctx->magic-1))
339     ctx->magic = mx_get_magic (path);
340
341   if (ctx->magic == M_COMPRESSED)
342     mutt_open_read_compressed (ctx);
343
344   if (ctx->magic == 0)
345     mutt_error (_("%s is not a mailbox."), path);
346
347   if (ctx->magic == -1)
348     mutt_perror (path);
349
350   if (ctx->magic <= 0) {
351     mx_fastclose_mailbox (ctx);
352     if (!pctx)
353       p_delete(&ctx);
354     return (NULL);
355   }
356
357   /* if the user has a `push' command in their .muttrc, or in a folder-hook,
358    * it will cause the progress messages not to be displayed because
359    * mutt_refresh() will think we are in the middle of a macro.  so set a
360    * flag to indicate that we should really refresh the screen.
361    */
362   set_option (OPTFORCEREFRESH);
363
364   if (!ctx->quiet)
365     mutt_message (_("Reading %s..."), ctx->path);
366
367   rc = mxfmts[ctx->magic-1]->mx_open_mailbox(ctx);
368
369   if (rc == 0) {
370     if ((flags & M_NOSORT) == 0) {
371       /* avoid unnecessary work since the mailbox is completely unthreaded
372          to begin with */
373       unset_option (OPTSORTSUBTHREADS);
374       unset_option (OPTNEEDRESCORE);
375       mutt_sort_headers (ctx, 1);
376     }
377     if (!ctx->quiet)
378       mutt_clear_error ();
379   }
380   else {
381     mx_fastclose_mailbox (ctx);
382     if (!pctx)
383       p_delete(&ctx);
384   }
385
386   unset_option (OPTFORCEREFRESH);
387   return (ctx);
388 }
389
390 /* free up memory associated with the mailbox context */
391 void mx_fastclose_mailbox (CONTEXT * ctx)
392 {
393   int i;
394
395   if (!ctx)
396     return;
397
398   if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_fastclose_mailbox)
399     mxfmts[ctx->magic-1]->mx_fastclose_mailbox(ctx);
400   if (ctx->subj_hash)
401     hash_delete (&ctx->subj_hash, NULL);
402   if (ctx->id_hash)
403     hash_delete (&ctx->id_hash, NULL);
404   mutt_clear_threads (ctx);
405   for (i = 0; i < ctx->msgcount; i++)
406     header_delete(&ctx->hdrs[i]);
407   p_delete(&ctx->hdrs);
408   p_delete(&ctx->v2r);
409
410   if (ctx->cinfo)
411     mutt_fast_close_compressed (ctx);
412
413   p_delete(&ctx->path);
414   p_delete(&ctx->pattern);
415   pattern_list_wipe(&ctx->limit_pattern);
416   m_fclose(&ctx->fp);
417   p_clear(ctx, 1);
418 }
419
420 /* save changes to disk */
421 static int sync_mailbox (CONTEXT * ctx, int *index_hint)
422 {
423   int rc = -1;
424
425   if (!ctx->quiet)
426     mutt_message (_("Writing %s..."), ctx->path);
427
428   if (MX_IDX(ctx->magic-1))
429     /* the 1 is only of interest for IMAP and means EXPUNGE */
430     rc = mxfmts[ctx->magic-1]->mx_sync_mailbox(ctx,1,index_hint);
431
432   if (rc == 0 && ctx->cinfo)
433     return mutt_sync_compressed (ctx);
434
435   return rc;
436 }
437
438 /* move deleted mails to the trash folder */
439 static int trash_append (CONTEXT * ctx)
440 {
441   CONTEXT *ctx_trash;
442   int i = 0;
443   struct stat st, stc;
444
445   if (!TrashPath || !ctx->deleted ||
446       (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
447     return 0;
448
449   for (; i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
450                                ctx->hdrs[i]->appended); i++);
451   if (i == ctx->msgcount)
452     return 0;                   /* nothing to be done */
453
454   if (mutt_save_confirm (TrashPath, &st) != 0) {
455     mutt_error _("message(s) not deleted");
456
457     return -1;
458   }
459
460   if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
461       && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
462     return 0;                   /* we are in the trash folder: simple sync */
463
464   if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) {
465     for (i = 0; i < ctx->msgcount; i++)
466       if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
467           && !ctx->hdrs[i]->purged
468           && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) {
469         mx_close_mailbox (ctx_trash, NULL);
470         return -1;
471       }
472
473     mx_close_mailbox (ctx_trash, NULL);
474   }
475   else {
476     mutt_error _("Can't open trash folder");
477
478     return -1;
479   }
480
481   return 0;
482 }
483
484 /* save changes and close mailbox */
485 static int _mx_close_mailbox (CONTEXT * ctx, int *index_hint)
486 {
487   int i, move_messages = 0, purge = 1, read_msgs = 0;
488   int check;
489   int isSpool = 0;
490   CONTEXT f;
491   char mbox[_POSIX_PATH_MAX];
492   char buf[STRING];
493
494   if (!ctx)
495     return 0;
496
497   ctx->closing = 1;
498
499   if (ctx->readonly || ctx->dontwrite) {
500     /* mailbox is readonly or we don't want to write */
501     mx_fastclose_mailbox (ctx);
502     return 0;
503   }
504
505   if (ctx->append) {
506     /* mailbox was opened in write-mode */
507     if (ctx->magic == M_MBOX)
508       mbox_close_mailbox (ctx);
509     else
510       mx_fastclose_mailbox (ctx);
511     return 0;
512   }
513
514   for (i = 0; i < ctx->msgcount; i++) {
515     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
516         && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
517       read_msgs++;
518   }
519
520   if (read_msgs && quadoption (OPT_MOVE) != M_NO) {
521     m_strcpy(mbox, sizeof(mbox), NONULL(Inbox));
522     isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
523     mutt_expand_path (mbox, sizeof (mbox));
524
525     if (isSpool) {
526       snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
527       if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
528         ctx->closing = 0;
529         return (-1);
530       }
531     }
532   }
533
534   /* 
535    * There is no point in asking whether or not to purge if we are
536    * just marking messages as "trash".
537    */
538   if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
539     snprintf (buf, sizeof (buf), ctx->deleted == 1
540               ? _("Purge %d deleted message?") :
541               _("Purge %d deleted messages?"), ctx->deleted);
542     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
543       ctx->closing = 0;
544       return (-1);
545     }
546   }
547
548   /* IMAP servers manage the OLD flag themselves */
549   if (ctx->magic != M_IMAP)
550     if (option (OPTMARKOLD)) {
551       for (i = 0; i < ctx->msgcount; i++) {
552         if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
553           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
554       }
555     }
556
557   if (move_messages) {
558     mutt_message (_("Moving read messages to %s..."), mbox);
559
560     /* try to use server-side copy first */
561     i = 1;
562
563     if (ctx->magic == M_IMAP && imap_is_magic (mbox, NULL) == M_IMAP) {
564       /* tag messages for moving, and clear old tags, if any */
565       for (i = 0; i < ctx->msgcount; i++)
566         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
567             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
568           ctx->hdrs[i]->tagged = 1;
569         else
570           ctx->hdrs[i]->tagged = 0;
571
572       i = imap_copy_messages (ctx, NULL, mbox, 1);
573     }
574
575     if (i == 0)                 /* success */
576       mutt_clear_error ();
577     else if (i == -1) {         /* horrible error, bail */
578       ctx->closing = 0;
579       return -1;
580     }
581     else                        /* use regular append-copy mode */
582     {
583       if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
584         ctx->closing = 0;
585         return -1;
586       }
587
588       for (i = 0; i < ctx->msgcount; i++) {
589         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
590             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
591           if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
592               0) {
593             mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
594             mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
595           }
596           else {
597             mx_close_mailbox (&f, NULL);
598             ctx->closing = 0;
599             return -1;
600           }
601         }
602       }
603
604       mx_close_mailbox (&f, NULL);
605     }
606
607   }
608   else if (!ctx->changed && ctx->deleted == 0) {
609     mutt_message _("Mailbox is unchanged.");
610
611     mx_fastclose_mailbox (ctx);
612     return 0;
613   }
614
615   /* copy mails to the trash before expunging */
616   if (purge && ctx->deleted)
617     if (trash_append (ctx) != 0) {
618       ctx->closing = 0;
619       return -1;
620     }
621
622   /* allow IMAP to preserve the deleted flag across sessions */
623   if (ctx->magic == M_IMAP) {
624     if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
625       ctx->closing = 0;
626       return check;
627     }
628   }
629   else
630   {
631     if (!purge) {
632       for (i = 0; i < ctx->msgcount; i++)
633         ctx->hdrs[i]->deleted = 0;
634       ctx->deleted = 0;
635     }
636
637     if (ctx->changed || ctx->deleted) {
638       if ((check = sync_mailbox (ctx, index_hint)) != 0) {
639         ctx->closing = 0;
640         return check;
641       }
642     }
643   }
644
645   if (move_messages)
646     mutt_message (_("%d kept, %d moved, %d deleted."),
647                   ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
648   else
649     mutt_message (_("%d kept, %d deleted."),
650                   ctx->msgcount - ctx->deleted, ctx->deleted);
651
652   if (ctx->cinfo && mutt_slow_close_compressed (ctx))
653     return (-1);
654
655   mx_fastclose_mailbox (ctx);
656
657   return 0;
658 }
659
660 int mx_close_mailbox (CONTEXT * ctx, int *index_hint) {
661   int ret = 0;
662   if (!ctx)
663     return (0);
664   ret = _mx_close_mailbox (ctx, index_hint);
665   sidebar_set_buffystats (ctx);
666   return (ret);
667 }
668
669 /* update a Context structure's internal tables. */
670
671 void mx_update_tables (CONTEXT * ctx, int committing)
672 {
673   int i, j;
674
675   /* update memory to reflect the new state of the mailbox */
676   ctx->vcount = 0;
677   ctx->vsize = 0;
678   ctx->tagged = 0;
679   ctx->deleted = 0;
680   ctx->new = 0;
681   ctx->unread = 0;
682   ctx->changed = 0;
683   ctx->flagged = 0;
684 #define this_body ctx->hdrs[j]->content
685   for (i = 0, j = 0; i < ctx->msgcount; i++) {
686     if ((committing && (!ctx->hdrs[i]->deleted ||
687                         (ctx->magic == M_MAILDIR
688                          && option (OPTMAILDIRTRASH)))) || (!committing
689                                                             && ctx->hdrs[i]->
690                                                             active)) {
691       if (i != j) {
692         ctx->hdrs[j] = ctx->hdrs[i];
693         ctx->hdrs[i] = NULL;
694       }
695       ctx->hdrs[j]->msgno = j;
696       if (ctx->hdrs[j]->virtual != -1) {
697         ctx->v2r[ctx->vcount] = j;
698         ctx->hdrs[j]->virtual = ctx->vcount++;
699         ctx->vsize += this_body->length + this_body->offset -
700           this_body->hdr_offset;
701       }
702
703       if (committing)
704         ctx->hdrs[j]->changed = 0;
705       else if (ctx->hdrs[j]->changed)
706         ctx->changed++;
707
708       if (!committing
709           || (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
710         if (ctx->hdrs[j]->deleted)
711           ctx->deleted++;
712       }
713
714       if (ctx->hdrs[j]->tagged)
715         ctx->tagged++;
716       if (ctx->hdrs[j]->flagged)
717         ctx->flagged++;
718       if (!ctx->hdrs[j]->read) {
719         ctx->unread++;
720         if (!ctx->hdrs[j]->old)
721           ctx->new++;
722       }
723
724       j++;
725     }
726     else {
727       if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
728         ctx->size -= (ctx->hdrs[i]->content->length +
729                       ctx->hdrs[i]->content->offset -
730                       ctx->hdrs[i]->content->hdr_offset);
731       /* remove message from the hash tables */
732       if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
733         hash_remove (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
734                      ctx->hdrs[i], NULL);
735       if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
736         hash_remove (ctx->id_hash, ctx->hdrs[i]->env->message_id,
737                      ctx->hdrs[i], NULL);
738       header_delete(&ctx->hdrs[i]);
739     }
740   }
741 #undef this_body
742   ctx->msgcount = j;
743
744   /* update sidebar count */
745   sidebar_set_buffystats (ctx);
746 }
747
748
749 /* save changes to mailbox
750  *
751  * return values:
752  *      0               success
753  *      -1              error
754  */
755 static int _mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
756 {
757   int rc, i;
758   int purge = 1;
759   int msgcount, deleted;
760
761   if (ctx->dontwrite) {
762     char buf[STRING], tmp[STRING];
763
764     if (km_expand_key (buf, sizeof (buf),
765                        km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
766       snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
767     else
768       m_strcpy(tmp, sizeof(tmp), _("Use 'toggle-write' to re-enable write!"));
769
770     mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
771     return -1;
772   }
773   else if (ctx->readonly) {
774     mutt_error _("Mailbox is read-only.");
775
776     return -1;
777   }
778
779   if (!ctx->changed && !ctx->deleted) {
780     mutt_message _("Mailbox is unchanged.");
781
782     return (0);
783   }
784
785   if (ctx->deleted) {
786     char buf[STRING];
787
788     snprintf (buf, sizeof (buf), ctx->deleted == 1
789               ? _("Purge %d deleted message?") :
790               _("Purge %d deleted messages?"), ctx->deleted);
791     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
792       return (-1);
793     else if (purge == M_NO) {
794       if (!ctx->changed)
795         return 0;               /* nothing to do! */
796       /* let IMAP servers hold on to D flags */
797       if (ctx->magic != M_IMAP)
798       {
799         for (i = 0; i < ctx->msgcount; i++)
800           ctx->hdrs[i]->deleted = 0;
801         ctx->deleted = 0;
802       }
803     }
804     else if (ctx->last_tag && ctx->last_tag->deleted)
805       ctx->last_tag = NULL;     /* reset last tagged msg now useless */
806   }
807
808   /* really only for IMAP - imap_sync_mailbox results in a call to
809    * mx_update_tables, so ctx->deleted is 0 when it comes back */
810   msgcount = ctx->msgcount;
811   deleted = ctx->deleted;
812
813   if (purge && ctx->deleted) {
814     if (trash_append (ctx) == -1)
815       return -1;
816   }
817
818   if (ctx->magic == M_IMAP)
819     rc = imap_sync_mailbox (ctx, purge, index_hint);
820   else
821     rc = sync_mailbox (ctx, index_hint);
822   if (rc == 0) {
823     if (ctx->magic == M_IMAP && !purge)
824       mutt_message (_("Mailbox checkpointed."));
825     else
826       mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
827
828     mutt_sleep (0);
829
830     /* if we haven't deleted any messages, we don't need to resort */
831     /* ... except for certain folder formats which need "unsorted" 
832      * sort order in order to synchronize folders.
833      * 
834      * MH and maildir are safe.  mbox-style seems to need re-sorting,
835      * at least with the new threading code.
836      */
837     if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
838       /* IMAP does this automatically after handling EXPUNGE */
839       if (ctx->magic != M_IMAP)
840       {
841         mx_update_tables (ctx, 1);
842         mutt_sort_headers (ctx, 1);     /* rethread from scratch */
843       }
844     }
845   }
846
847   return (rc);
848 }
849
850 int mx_sync_mailbox (CONTEXT* ctx, int* index_hint) {
851   int ret = _mx_sync_mailbox (ctx, index_hint);
852   sidebar_set_buffystats (ctx);
853   return (ret);
854 }
855
856 /* args:
857  *      dest    destintation mailbox
858  *      hdr     message being copied (required for maildir support, because
859  *              the filename depends on the message flags)
860  */
861 MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
862 {
863   MESSAGE *msg;
864   address_t *p = NULL;
865
866   if (!MX_IDX(dest->magic-1)) {
867     return (NULL);
868   }
869
870   msg = p_new(MESSAGE, 1);
871   msg->magic = dest->magic;
872   msg->write = 1;
873
874   if (hdr) {
875     msg->flags.flagged = hdr->flagged;
876     msg->flags.replied = hdr->replied;
877     msg->flags.read = hdr->read;
878     msg->received = hdr->received;
879   }
880
881   if (msg->received == 0)
882     time (&msg->received);
883
884   if (mxfmts[dest->magic-1]->mx_open_new_message(msg, dest, hdr) == 0) {
885     if (msg->magic == M_MBOX && flags & M_ADD_FROM) {
886       if (hdr) {
887         if (hdr->env->return_path)
888           p = hdr->env->return_path;
889         else if (hdr->env->sender)
890           p = hdr->env->sender;
891         else
892           p = hdr->env->from;
893       }
894
895       fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL(mod_core.username),
896                ctime (&msg->received));
897     }
898   }
899   else
900     p_delete(&msg);
901
902   return msg;
903 }
904
905 /* check for new mail */
906 int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock) {
907   if (ctx->cinfo)
908     return mutt_check_mailbox_compressed (ctx);
909
910   if (ctx) {
911     if (ctx->locked)
912       lock = 0;
913     if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_check_mailbox)
914       return (mxfmts[ctx->magic-1]->mx_check_mailbox(ctx, index_hint, lock));
915   }
916
917   return (-1);
918 }
919
920 /* return a stream pointer for a message */
921 MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
922 {
923   MESSAGE *msg;
924
925   msg = p_new(MESSAGE, 1);
926   switch (msg->magic = ctx->magic) {
927   case M_MBOX:
928     msg->fp = ctx->fp;
929     break;
930
931   case M_MH:
932   case M_MAILDIR:
933     {
934       HEADER *cur = ctx->hdrs[msgno];
935       char path[_POSIX_PATH_MAX];
936
937       snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
938
939       if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
940           ctx->magic == M_MAILDIR)
941         msg->fp = maildir_open_find_message (ctx->path, cur->path);
942
943       if (msg->fp == NULL) {
944         mutt_perror (path);
945         p_delete(&msg);
946       }
947     }
948     break;
949
950   case M_IMAP:
951     {
952       if (imap_fetch_message (msg, ctx, msgno) != 0)
953         p_delete(&msg);
954       break;
955     }
956
957   case M_POP:
958     {
959       if (pop_fetch_message (msg, ctx, msgno) != 0)
960         p_delete(&msg);
961       break;
962     }
963
964   default:
965     p_delete(&msg);
966     break;
967   }
968   return (msg);
969 }
970
971 /* commit a message to a folder */
972
973 int mx_commit_message (MESSAGE * msg, CONTEXT * ctx) {
974   if (!(msg->write && ctx->append)) {
975     return -1;
976   }
977   if (!ctx || !MX_IDX(ctx->magic-1) || !mxfmts[ctx->magic-1]->mx_commit_message)
978     return (-1);
979   return (mxfmts[ctx->magic-1]->mx_commit_message (msg, ctx));
980 }
981
982 /* close a pointer to a message */
983 int mx_close_message (MESSAGE ** msg)
984 {
985   int r = 0;
986
987   if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
988   ||  (*msg)->magic == M_IMAP || (*msg)->magic == M_POP)
989   {
990     r = m_fclose(&(*msg)->fp);
991   }
992   else
993     (*msg)->fp = NULL;
994
995   if ((*msg)->path) {
996     unlink ((*msg)->path);
997     p_delete(&(*msg)->path);
998   }
999
1000   p_delete(msg);
1001   return (r);
1002 }
1003
1004 void mx_alloc_memory (CONTEXT * ctx)
1005 {
1006     ctx->hdrmax += 32;
1007
1008     p_realloc(&ctx->hdrs, ctx->hdrmax);
1009     p_realloc(&ctx->v2r, ctx->hdrmax);
1010     p_clear(ctx->hdrs + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1011     p_clear(ctx->v2r + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1012 }
1013
1014 /* this routine is called to update the counts in the context structure for
1015  * the last message header parsed.
1016  */
1017 void mx_update_context (CONTEXT * ctx, int new_messages)
1018 {
1019   HEADER *h;
1020   int msgno;
1021
1022   for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
1023     h = ctx->hdrs[msgno];
1024
1025     /* NOTE: this _must_ be done before the check for mailcap! */
1026     h->security = crypt_query (h->content);
1027
1028     if (!ctx->pattern) {
1029       ctx->v2r[ctx->vcount] = msgno;
1030       h->virtual = ctx->vcount++;
1031     }
1032     else
1033       h->virtual = -1;
1034     h->msgno = msgno;
1035
1036     if (h->env->supersedes) {
1037       HEADER *h2;
1038
1039       if (!ctx->id_hash)
1040         ctx->id_hash = mutt_make_id_hash (ctx);
1041
1042       h2 = hash_find (ctx->id_hash, h->env->supersedes);
1043
1044       /* p_delete(&h->env->supersedes); should I ? */
1045       if (h2) {
1046         h2->superseded = 1;
1047         if (!ctx->counting && mod_score.enable)
1048           mutt_score_message (ctx, h2, 1);
1049       }
1050     }
1051
1052     /* add this message to the hash tables */
1053     if (ctx->id_hash && h->env->message_id)
1054       hash_insert (ctx->id_hash, h->env->message_id, h);
1055     if (!ctx->counting) {
1056       if (ctx->subj_hash && h->env->real_subj)
1057         hash_insert (ctx->subj_hash, h->env->real_subj, h);
1058
1059       if (mod_score.enable)
1060         mutt_score_message (ctx, h, 0);
1061     }
1062
1063     if (h->changed)
1064       ctx->changed = 1;
1065     if (h->flagged)
1066       ctx->flagged++;
1067     if (h->deleted)
1068       ctx->deleted++;
1069     if (!h->read) {
1070       ctx->unread++;
1071       if (!h->old)
1072         ctx->new++;
1073     }
1074   }
1075   /* update sidebar count */
1076   sidebar_set_buffystats (ctx);
1077 }
1078
1079 /*
1080  * Return:
1081  * 1 if the specified mailbox contains 0 messages.
1082  * 0 if the mailbox contains messages
1083  * -1 on error
1084  */
1085 int mx_check_empty (const char *path)
1086 {
1087   int i = 0;
1088   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_check_empty)
1089     return (mxfmts[i]->mx_check_empty(path));
1090   errno = EINVAL;
1091   return (-1);
1092 }
1093
1094 int mx_acl_check(CONTEXT *ctx, int flag)
1095 {
1096     if (!mxfmts[ctx->magic-1]->mx_acl_check)
1097         return 1;
1098     return mxfmts[ctx->magic-1]->mx_acl_check(ctx,flag);
1099 }
1100
1101 void mutt_parse_mime_message (CONTEXT * ctx, HEADER * cur)
1102 {
1103   MESSAGE *msg;
1104   int flags = 0;
1105
1106   do {
1107     if (cur->content->type != TYPEMESSAGE
1108         && cur->content->type != TYPEMULTIPART)
1109       break;                     /* nothing to do */
1110
1111     if (cur->content->parts)
1112       break;                     /* The message was parsed earlier. */
1113
1114     if ((msg = mx_open_message (ctx, cur->msgno))) {
1115       mutt_parse_part (msg->fp, cur->content);
1116
1117       cur->security = crypt_query (cur->content);
1118
1119       mx_close_message (&msg);
1120     }
1121   } while (0);
1122   mutt_count_body_parts (cur, flags | M_PARTS_RECOUNT);
1123 }