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