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