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