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