1 /**
2  * @file
3  * Mailbox multiplexor
4  *
5  * @authors
6  * Copyright (C) 1996-2002,2010,2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 1999-2003 Thomas Roessler <roessler@does-not-exist.org>
8  * Copyright (C) 2016-2018 Richard Russon <rich@flatcap.org>
9  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
10  *
11  * @copyright
12  * This program is free software: you can redistribute it and/or modify it under
13  * the terms of the GNU General Public License as published by the Free Software
14  * Foundation, either version 2 of the License, or (at your option) any later
15  * version.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 /**
27  * @page neo_mx Mailbox multiplexor
28  *
29  * Mailbox multiplexor
30  */
31 
32 #include "config.h"
33 #include <errno.h>
34 #include <limits.h>
35 #include <locale.h>
36 #include <stdbool.h>
37 #include <string.h>
38 #include <sys/stat.h>
39 #include <time.h>
40 #include <unistd.h>
41 #include "mutt/lib.h"
42 #include "address/lib.h"
43 #include "email/lib.h"
44 #include "core/lib.h"
45 #include "alias/lib.h"
46 #include "mutt.h"
47 #include "mx.h"
48 #include "maildir/lib.h"
49 #include "mbox/lib.h"
50 #include "menu/lib.h"
51 #include "question/lib.h"
52 #include "commands.h"
53 #include "copy.h"
54 #include "hook.h"
55 #include "keymap.h"
56 #include "mutt_globals.h"
57 #include "mutt_header.h"
58 #include "mutt_logging.h"
59 #include "mutt_mailbox.h"
60 #include "muttlib.h"
61 #include "opcodes.h"
62 #include "options.h"
63 #include "protos.h"
64 #ifdef USE_COMP_MBOX
65 #include "compmbox/lib.h"
66 #endif
67 #ifdef USE_IMAP
68 #include "imap/lib.h"
69 #endif
70 #ifdef USE_POP
71 #include "pop/lib.h"
72 #endif
73 #ifdef USE_NNTP
74 #include "nntp/lib.h"
75 #include "nntp/adata.h" // IWYU pragma: keep
76 #include "nntp/mdata.h" // IWYU pragma: keep
77 #endif
78 #ifdef USE_NOTMUCH
79 #include "notmuch/lib.h"
80 #endif
81 #ifdef ENABLE_NLS
82 #include <libintl.h>
83 #endif
84 #ifdef __APPLE__
85 #include <xlocale.h>
86 #endif
87 
88 static const struct Mapping MboxTypeMap[] = {
89   // clang-format off
90   { "mbox",    MUTT_MBOX,    },
91   { "MMDF",    MUTT_MMDF,    },
92   { "MH",      MUTT_MH,      },
93   { "Maildir", MUTT_MAILDIR, },
94   { NULL, 0, },
95   // clang-format on
96 };
97 
98 struct EnumDef MboxTypeDef = {
99   "mbox_type",
100   4,
101   (struct Mapping *) &MboxTypeMap,
102 };
103 
104 /**
105  * MxOps - All the Mailbox backends
106  */
107 const struct MxOps *MxOps[] = {
108 /* These mailboxes can be recognised by their Url scheme */
109 #ifdef USE_IMAP
110   &MxImapOps,
111 #endif
112 #ifdef USE_NOTMUCH
113   &MxNotmuchOps,
114 #endif
115 #ifdef USE_POP
116   &MxPopOps,
117 #endif
118 #ifdef USE_NNTP
119   &MxNntpOps,
120 #endif
121 
122   /* Local mailboxes */
123   &MxMaildirOps,
124   &MxMboxOps,
125   &MxMhOps,
126   &MxMmdfOps,
127 
128 /* If everything else fails... */
129 #ifdef USE_COMP_MBOX
130   &MxCompOps,
131 #endif
132   NULL,
133 };
134 
135 /**
136  * mx_get_ops - Get mailbox operations
137  * @param type Mailbox type
138  * @retval ptr  Mailbox function
139  * @retval NULL Error
140  */
mx_get_ops(enum MailboxType type)141 const struct MxOps *mx_get_ops(enum MailboxType type)
142 {
143   for (const struct MxOps **ops = MxOps; *ops; ops++)
144     if ((*ops)->type == type)
145       return *ops;
146 
147   return NULL;
148 }
149 
150 /**
151  * mutt_is_spool - Is this the spool_file?
152  * @param str Name to check
153  * @retval true It is the spool_file
154  */
mutt_is_spool(const char * str)155 static bool mutt_is_spool(const char *str)
156 {
157   const char *const c_spool_file = cs_subset_string(NeoMutt->sub, "spool_file");
158   if (mutt_str_equal(str, c_spool_file))
159     return true;
160 
161   struct Url *ua = url_parse(str);
162   struct Url *ub = url_parse(c_spool_file);
163 
164   const bool is_spool =
165       ua && ub && (ua->scheme == ub->scheme) &&
166       mutt_istr_equal(ua->host, ub->host) && mutt_istr_equal(ua->path, ub->path) &&
167       (!ua->user || !ub->user || mutt_str_equal(ua->user, ub->user));
168 
169   url_free(&ua);
170   url_free(&ub);
171   return is_spool;
172 }
173 
174 /**
175  * mx_access - Wrapper for access, checks permissions on a given mailbox
176  * @param path  Path of mailbox
177  * @param flags Flags, e.g. W_OK
178  * @retval  0 Success, allowed
179  * @retval <0 Failure, not allowed
180  *
181  * We may be interested in using ACL-style flags at some point, currently we
182  * use the normal access() flags.
183  */
mx_access(const char * path,int flags)184 int mx_access(const char *path, int flags)
185 {
186 #ifdef USE_IMAP
187   if (imap_path_probe(path, NULL) == MUTT_IMAP)
188     return imap_access(path);
189 #endif
190 
191   return access(path, flags);
192 }
193 
194 /**
195  * mx_open_mailbox_append - Open a mailbox for appending
196  * @param m     Mailbox
197  * @param flags Flags, see #OpenMailboxFlags
198  * @retval true Success
199  * @retval false Failure
200  */
mx_open_mailbox_append(struct Mailbox * m,OpenMailboxFlags flags)201 static bool mx_open_mailbox_append(struct Mailbox *m, OpenMailboxFlags flags)
202 {
203   if (!m)
204     return false;
205 
206   struct stat st = { 0 };
207 
208   m->append = true;
209   if ((m->type == MUTT_UNKNOWN) || (m->type == MUTT_MAILBOX_ERROR))
210   {
211     m->type = mx_path_probe(mailbox_path(m));
212 
213     if (m->type == MUTT_UNKNOWN)
214     {
215       if (flags & (MUTT_APPEND | MUTT_NEWFOLDER))
216       {
217         m->type = MUTT_MAILBOX_ERROR;
218       }
219       else
220       {
221         mutt_error(_("%s is not a mailbox"), mailbox_path(m));
222         return false;
223       }
224     }
225 
226     if (m->type == MUTT_MAILBOX_ERROR)
227     {
228       if (stat(mailbox_path(m), &st) == -1)
229       {
230         if (errno == ENOENT)
231         {
232 #ifdef USE_COMP_MBOX
233           if (mutt_comp_can_append(m))
234             m->type = MUTT_COMPRESSED;
235           else
236 #endif
237             m->type = cs_subset_enum(NeoMutt->sub, "mbox_type");
238           flags |= MUTT_APPENDNEW;
239         }
240         else
241         {
242           mutt_perror(mailbox_path(m));
243           return false;
244         }
245       }
246       else
247         return false;
248     }
249 
250     m->mx_ops = mx_get_ops(m->type);
251   }
252 
253   if (!m->mx_ops || !m->mx_ops->mbox_open_append)
254     return false;
255 
256   const bool rc = m->mx_ops->mbox_open_append(m, flags);
257   m->opened++;
258   return rc;
259 }
260 
261 /**
262  * mx_mbox_ac_link - Link a Mailbox to an existing or new Account
263  * @param m Mailbox to link
264  * @retval true Success
265  * @retval false Failure
266  */
mx_mbox_ac_link(struct Mailbox * m)267 bool mx_mbox_ac_link(struct Mailbox *m)
268 {
269   if (!m)
270     return false;
271 
272   if (m->account)
273     return true;
274 
275   struct Account *a = mx_ac_find(m);
276   const bool new_account = !a;
277   if (new_account)
278   {
279     a = account_new(NULL, NeoMutt->sub);
280     a->type = m->type;
281   }
282   if (!mx_ac_add(a, m))
283   {
284     if (new_account)
285     {
286       account_free(&a);
287     }
288     return false;
289   }
290   if (new_account)
291   {
292     neomutt_account_add(NeoMutt, a);
293   }
294   return true;
295 }
296 
297 /**
298  * mx_mbox_open - Open a mailbox and parse it
299  * @param m     Mailbox to open
300  * @param flags Flags, see #OpenMailboxFlags
301  * @retval true Success
302  * @retval false Error
303  */
mx_mbox_open(struct Mailbox * m,OpenMailboxFlags flags)304 bool mx_mbox_open(struct Mailbox *m, OpenMailboxFlags flags)
305 {
306   if (!m)
307     return false;
308 
309   if ((m->type == MUTT_UNKNOWN) && (flags & (MUTT_NEWFOLDER | MUTT_APPEND)))
310   {
311     m->type = cs_subset_enum(NeoMutt->sub, "mbox_type");
312     m->mx_ops = mx_get_ops(m->type);
313   }
314 
315   const bool newly_linked_account = !m->account;
316   if (newly_linked_account)
317   {
318     if (!mx_mbox_ac_link(m))
319     {
320       return false;
321     }
322   }
323 
324   m->verbose = !(flags & MUTT_QUIET);
325   m->readonly = (flags & MUTT_READONLY);
326   m->peekonly = (flags & MUTT_PEEK);
327 
328   if (flags & (MUTT_APPEND | MUTT_NEWFOLDER))
329   {
330     if (!mx_open_mailbox_append(m, flags))
331     {
332       goto error;
333     }
334     return true;
335   }
336 
337   if (m->opened > 0)
338   {
339     m->opened++;
340     return true;
341   }
342 
343   m->size = 0;
344   m->msg_unread = 0;
345   m->msg_flagged = 0;
346   m->rights = MUTT_ACL_ALL;
347 
348   if (m->type == MUTT_UNKNOWN)
349   {
350     m->type = mx_path_probe(mailbox_path(m));
351     m->mx_ops = mx_get_ops(m->type);
352   }
353 
354   if ((m->type == MUTT_UNKNOWN) || (m->type == MUTT_MAILBOX_ERROR) || !m->mx_ops)
355   {
356     if (m->type == MUTT_MAILBOX_ERROR)
357       mutt_perror(mailbox_path(m));
358     else if ((m->type == MUTT_UNKNOWN) || !m->mx_ops)
359       mutt_error(_("%s is not a mailbox"), mailbox_path(m));
360     goto error;
361   }
362 
363   mutt_make_label_hash(m);
364 
365   /* if the user has a 'push' command in their .neomuttrc, or in a folder-hook,
366    * it will cause the progress messages not to be displayed because
367    * mutt_refresh() will think we are in the middle of a macro.  so set a
368    * flag to indicate that we should really refresh the screen.  */
369   OptForceRefresh = true;
370 
371   if (m->verbose)
372     mutt_message(_("Reading %s..."), mailbox_path(m));
373 
374   // Clear out any existing emails
375   for (int i = 0; i < m->email_max; i++)
376   {
377     email_free(&m->emails[i]);
378   }
379 
380   m->msg_count = 0;
381   m->msg_unread = 0;
382   m->msg_flagged = 0;
383   m->msg_new = 0;
384   m->msg_deleted = 0;
385   m->msg_tagged = 0;
386   m->vcount = 0;
387 
388   enum MxOpenReturns rc = m->mx_ops->mbox_open(m);
389   m->opened++;
390 
391   if ((rc == MX_OPEN_OK) || (rc == MX_OPEN_ABORT))
392   {
393     if ((flags & MUTT_NOSORT) == 0)
394     {
395       /* avoid unnecessary work since the mailbox is completely unthreaded
396        * to begin with */
397       OptSortSubthreads = false;
398       OptNeedRescore = false;
399     }
400     if (m->verbose)
401       mutt_clear_error();
402     if (rc == MX_OPEN_ABORT)
403     {
404       mutt_error(_("Reading from %s interrupted..."), mailbox_path(m));
405     }
406   }
407   else
408   {
409     goto error;
410   }
411 
412   if (!m->peekonly)
413     m->has_new = false;
414   OptForceRefresh = false;
415 
416   return true;
417 
418 error:
419   mx_fastclose_mailbox(m);
420   if (newly_linked_account)
421     account_mailbox_remove(m->account, m);
422   return false;
423 }
424 
425 /**
426  * mx_fastclose_mailbox - Free up memory associated with the Mailbox
427  * @param m Mailbox
428  */
mx_fastclose_mailbox(struct Mailbox * m)429 void mx_fastclose_mailbox(struct Mailbox *m)
430 {
431   if (!m)
432     return;
433 
434   m->opened--;
435   if (m->opened != 0)
436     return;
437 
438   /* never announce that a mailbox we've just left has new mail.
439    * TODO: really belongs in mx_mbox_close, but this is a nice hook point */
440   if (!m->peekonly)
441     mutt_mailbox_set_notified(m);
442 
443   if (m->mx_ops)
444     m->mx_ops->mbox_close(m);
445 
446   mutt_hash_free(&m->subj_hash);
447   mutt_hash_free(&m->id_hash);
448   mutt_hash_free(&m->label_hash);
449 
450   if (m->emails)
451   {
452     for (int i = 0; i < m->msg_count; i++)
453     {
454       if (!m->emails[i])
455         break;
456       email_free(&m->emails[i]);
457     }
458   }
459 
460   if (m->flags & MB_HIDDEN)
461   {
462     mx_ac_remove(m);
463   }
464 }
465 
466 /**
467  * sync_mailbox - Save changes to disk
468  * @param m Mailbox
469  * @retval enum #MxStatus
470  */
sync_mailbox(struct Mailbox * m)471 static enum MxStatus sync_mailbox(struct Mailbox *m)
472 {
473   if (!m || !m->mx_ops || !m->mx_ops->mbox_sync)
474     return MX_STATUS_ERROR;
475 
476   if (m->verbose)
477   {
478     /* L10N: Displayed before/as a mailbox is being synced */
479     mutt_message(_("Writing %s..."), mailbox_path(m));
480   }
481 
482   enum MxStatus rc = m->mx_ops->mbox_sync(m);
483   if (rc != MX_STATUS_OK)
484   {
485     mutt_debug(LL_DEBUG2, "mbox_sync returned: %d\n", rc);
486     if ((rc == MX_STATUS_ERROR) && m->verbose)
487     {
488       /* L10N: Displayed if a mailbox sync fails */
489       mutt_error(_("Unable to write %s"), mailbox_path(m));
490     }
491   }
492 
493   return rc;
494 }
495 
496 /**
497  * trash_append - Move deleted mails to the trash folder
498  * @param m Mailbox
499  * @retval  0 Success
500  * @retval -1 Failure
501  */
trash_append(struct Mailbox * m)502 static int trash_append(struct Mailbox *m)
503 {
504   if (!m)
505     return -1;
506 
507   struct stat st = { 0 };
508   struct stat stc = { 0 };
509   int rc;
510 
511   const bool c_maildir_trash = cs_subset_bool(NeoMutt->sub, "maildir_trash");
512   const char *const c_trash = cs_subset_string(NeoMutt->sub, "trash");
513   if (!c_trash || (m->msg_deleted == 0) || ((m->type == MUTT_MAILDIR) && c_maildir_trash))
514   {
515     return 0;
516   }
517 
518   int delmsgcount = 0;
519   int first_del = -1;
520   for (int i = 0; i < m->msg_count; i++)
521   {
522     struct Email *e = m->emails[i];
523     if (!e)
524       break;
525 
526     if (e->deleted && !e->purge)
527     {
528       if (first_del < 0)
529         first_del = i;
530       delmsgcount++;
531     }
532   }
533 
534   if (delmsgcount == 0)
535     return 0; /* nothing to be done */
536 
537   /* avoid the "append messages" prompt */
538   const bool c_confirm_append = cs_subset_bool(NeoMutt->sub, "confirm_append");
539   cs_subset_str_native_set(NeoMutt->sub, "confirm_append", false, NULL);
540   rc = mutt_save_confirm(c_trash, &st);
541   cs_subset_str_native_set(NeoMutt->sub, "confirm_append", c_confirm_append, NULL);
542   if (rc != 0)
543   {
544     /* L10N: Although we know the precise number of messages, we do not show it to the user.
545        So feel free to use a "generic plural" as plural translation if your language has one. */
546     mutt_error(ngettext("message not deleted", "messages not deleted", delmsgcount));
547     return -1;
548   }
549 
550   if ((lstat(mailbox_path(m), &stc) == 0) && (stc.st_ino == st.st_ino) &&
551       (stc.st_dev == st.st_dev) && (stc.st_rdev == st.st_rdev))
552   {
553     return 0; /* we are in the trash folder: simple sync */
554   }
555 
556 #ifdef USE_IMAP
557   if ((m->type == MUTT_IMAP) && (imap_path_probe(c_trash, NULL) == MUTT_IMAP))
558   {
559     if (imap_fast_trash(m, c_trash) == 0)
560       return 0;
561   }
562 #endif
563 
564   struct Mailbox *m_trash = mx_path_resolve(c_trash);
565   const bool old_append = m_trash->append;
566   if (!mx_mbox_open(m_trash, MUTT_APPEND))
567   {
568     mutt_error(_("Can't open trash folder"));
569     mailbox_free(&m_trash);
570     return -1;
571   }
572 
573   /* continue from initial scan above */
574   for (int i = first_del; i < m->msg_count; i++)
575   {
576     struct Email *e = m->emails[i];
577     if (!e)
578       break;
579 
580     if (e->deleted && !e->purge)
581     {
582       if (mutt_append_message(m_trash, m, e, NULL, MUTT_CM_NO_FLAGS, CH_NO_FLAGS) == -1)
583       {
584         mx_mbox_close(m_trash);
585         // L10N: Displayed if appending to $trash fails when syncing or closing a mailbox
586         mutt_error(_("Unable to append to trash folder"));
587         m_trash->append = old_append;
588         return -1;
589       }
590     }
591   }
592 
593   mx_mbox_close(m_trash);
594   m_trash->append = old_append;
595   if (m_trash->flags == MB_HIDDEN)
596     mailbox_free(&m_trash);
597 
598   return 0;
599 }
600 
601 /**
602  * mx_mbox_close - Save changes and close mailbox
603  * @param m Mailbox
604  * @retval enum #MxStatus
605  *
606  * @note The flag retvals come from a call to a backend sync function
607  *
608  * @note It's very important to ensure the mailbox is properly closed before
609  *       free'ing the context.  For selected mailboxes, IMAP will cache the
610  *       context inside connection->adata until imap_close_mailbox() removes
611  *       it.  Readonly, dontwrite, and append mailboxes are guaranteed to call
612  *       mx_fastclose_mailbox(), so for most of NeoMutt's code you won't see
613  *       return value checks for temporary contexts.
614  */
mx_mbox_close(struct Mailbox * m)615 enum MxStatus mx_mbox_close(struct Mailbox *m)
616 {
617   if (!m)
618     return MX_STATUS_ERROR;
619 
620   const bool c_mail_check_recent =
621       cs_subset_bool(NeoMutt->sub, "mail_check_recent");
622   if (c_mail_check_recent && !m->peekonly)
623     m->has_new = false;
624 
625   if (m->readonly || m->dontwrite || m->append || m->peekonly)
626   {
627     mx_fastclose_mailbox(m);
628     return 0;
629   }
630 
631   int i, read_msgs = 0;
632   enum MxStatus rc = MX_STATUS_ERROR;
633   enum QuadOption move_messages = MUTT_NO;
634   enum QuadOption purge = MUTT_YES;
635   struct Buffer *mbox = NULL;
636   struct Buffer *buf = mutt_buffer_pool_get();
637 
638 #ifdef USE_NNTP
639   if ((m->msg_unread != 0) && (m->type == MUTT_NNTP))
640   {
641     struct NntpMboxData *mdata = m->mdata;
642 
643     if (mdata && mdata->adata && mdata->group)
644     {
645       const enum QuadOption c_catchup_newsgroup =
646           cs_subset_quad(NeoMutt->sub, "catchup_newsgroup");
647       enum QuadOption ans =
648           query_quadoption(c_catchup_newsgroup, _("Mark all articles read?"));
649       if (ans == MUTT_ABORT)
650         goto cleanup;
651       if (ans == MUTT_YES)
652         mutt_newsgroup_catchup(m, mdata->adata, mdata->group);
653     }
654   }
655 #endif
656 
657   const bool c_keep_flagged = cs_subset_bool(NeoMutt->sub, "keep_flagged");
658   for (i = 0; i < m->msg_count; i++)
659   {
660     struct Email *e = m->emails[i];
661     if (!e)
662       break;
663 
664     if (!e->deleted && e->read && !(e->flagged && c_keep_flagged))
665       read_msgs++;
666   }
667 
668 #ifdef USE_NNTP
669   /* don't need to move articles from newsgroup */
670   if (m->type == MUTT_NNTP)
671     read_msgs = 0;
672 #endif
673 
674   const enum QuadOption c_move = cs_subset_quad(NeoMutt->sub, "move");
675   if ((read_msgs != 0) && (c_move != MUTT_NO))
676   {
677     bool is_spool;
678     mbox = mutt_buffer_pool_get();
679 
680     char *p = mutt_find_hook(MUTT_MBOX_HOOK, mailbox_path(m));
681     if (p)
682     {
683       is_spool = true;
684       mutt_buffer_strcpy(mbox, p);
685     }
686     else
687     {
688       const char *const c_mbox = cs_subset_string(NeoMutt->sub, "mbox");
689       mutt_buffer_strcpy(mbox, c_mbox);
690       is_spool = mutt_is_spool(mailbox_path(m)) &&
691                  !mutt_is_spool(mutt_buffer_string(mbox));
692     }
693 
694     if (is_spool && !mutt_buffer_is_empty(mbox))
695     {
696       mutt_buffer_expand_path(mbox);
697       mutt_buffer_printf(buf,
698                          /* L10N: The first argument is the number of read messages to be
699                             moved, the second argument is the target mailbox. */
700                          ngettext("Move %d read message to %s?",
701                                   "Move %d read messages to %s?", read_msgs),
702                          read_msgs, mutt_buffer_string(mbox));
703       move_messages = query_quadoption(c_move, mutt_buffer_string(buf));
704       if (move_messages == MUTT_ABORT)
705         goto cleanup;
706     }
707   }
708 
709   /* There is no point in asking whether or not to purge if we are
710    * just marking messages as "trash".  */
711   const bool c_maildir_trash = cs_subset_bool(NeoMutt->sub, "maildir_trash");
712   if ((m->msg_deleted != 0) && !((m->type == MUTT_MAILDIR) && c_maildir_trash))
713   {
714     mutt_buffer_printf(buf,
715                        ngettext("Purge %d deleted message?",
716                                 "Purge %d deleted messages?", m->msg_deleted),
717                        m->msg_deleted);
718     const enum QuadOption c_delete = cs_subset_quad(NeoMutt->sub, "delete");
719     purge = query_quadoption(c_delete, mutt_buffer_string(buf));
720     if (purge == MUTT_ABORT)
721       goto cleanup;
722   }
723 
724   const bool c_mark_old = cs_subset_bool(NeoMutt->sub, "mark_old");
725   if (c_mark_old && !m->peekonly)
726   {
727     for (i = 0; i < m->msg_count; i++)
728     {
729       struct Email *e = m->emails[i];
730       if (!e)
731         break;
732       if (!e->deleted && !e->old && !e->read)
733         mutt_set_flag(m, e, MUTT_OLD, true);
734     }
735   }
736 
737   if (move_messages)
738   {
739     if (m->verbose)
740       mutt_message(_("Moving read messages to %s..."), mutt_buffer_string(mbox));
741 
742 #ifdef USE_IMAP
743     /* try to use server-side copy first */
744     i = 1;
745 
746     if ((m->type == MUTT_IMAP) && (imap_path_probe(mutt_buffer_string(mbox), NULL) == MUTT_IMAP))
747     {
748       /* add messages for moving, and clear old tags, if any */
749       struct EmailList el = STAILQ_HEAD_INITIALIZER(el);
750       for (i = 0; i < m->msg_count; i++)
751       {
752         struct Email *e = m->emails[i];
753         if (!e)
754           break;
755 
756         if (e->read && !e->deleted && !(e->flagged && c_keep_flagged))
757         {
758           e->tagged = true;
759           emaillist_add_email(&el, e);
760         }
761         else
762           e->tagged = false;
763       }
764 
765       i = imap_copy_messages(m, &el, mutt_buffer_string(mbox), SAVE_MOVE);
766       emaillist_clear(&el);
767     }
768 
769     if (i == 0) /* success */
770       mutt_clear_error();
771     else if (i == -1) /* horrible error, bail */
772       goto cleanup;
773     else /* use regular append-copy mode */
774 #endif
775     {
776       struct Mailbox *m_read = mx_path_resolve(mutt_buffer_string(mbox));
777       if (!mx_mbox_open(m_read, MUTT_APPEND))
778       {
779         mailbox_free(&m_read);
780         goto cleanup;
781       }
782 
783       for (i = 0; i < m->msg_count; i++)
784       {
785         struct Email *e = m->emails[i];
786         if (!e)
787           break;
788         if (e->read && !e->deleted && !(e->flagged && c_keep_flagged))
789         {
790           if (mutt_append_message(m_read, m, e, NULL, MUTT_CM_NO_FLAGS, CH_UPDATE_LEN) == 0)
791           {
792             mutt_set_flag(m, e, MUTT_DELETE, true);
793             mutt_set_flag(m, e, MUTT_PURGE, true);
794           }
795           else
796           {
797             mx_mbox_close(m_read);
798             goto cleanup;
799           }
800         }
801       }
802 
803       mx_mbox_close(m_read);
804     }
805   }
806   else if (!m->changed && (m->msg_deleted == 0))
807   {
808     if (m->verbose)
809       mutt_message(_("Mailbox is unchanged"));
810     if ((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF))
811       mbox_reset_atime(m, NULL);
812     mx_fastclose_mailbox(m);
813     rc = MX_STATUS_OK;
814     goto cleanup;
815   }
816 
817   /* copy mails to the trash before expunging */
818   const char *const c_trash = cs_subset_string(NeoMutt->sub, "trash");
819   const struct Mailbox *m_trash = mx_mbox_find(m->account, c_trash);
820   if (purge && (m->msg_deleted != 0) && (m != m_trash))
821   {
822     if (trash_append(m) != 0)
823       goto cleanup;
824   }
825 
826 #ifdef USE_IMAP
827   /* allow IMAP to preserve the deleted flag across sessions */
828   if (m->type == MUTT_IMAP)
829   {
830     const enum MxStatus check = imap_sync_mailbox(m, (purge != MUTT_NO), true);
831     if (check == MX_STATUS_ERROR)
832     {
833       rc = check;
834       goto cleanup;
835     }
836   }
837   else
838 #endif
839   {
840     if (purge == MUTT_NO)
841     {
842       for (i = 0; i < m->msg_count; i++)
843       {
844         struct Email *e = m->emails[i];
845         if (!e)
846           break;
847 
848         e->deleted = false;
849         e->purge = false;
850       }
851       m->msg_deleted = 0;
852     }
853 
854     if (m->changed || (m->msg_deleted != 0))
855     {
856       enum MxStatus check = sync_mailbox(m);
857       if (check != MX_STATUS_OK)
858       {
859         rc = check;
860         goto cleanup;
861       }
862     }
863   }
864 
865   if (m->verbose)
866   {
867     if (move_messages)
868     {
869       mutt_message(_("%d kept, %d moved, %d deleted"),
870                    m->msg_count - m->msg_deleted, read_msgs, m->msg_deleted);
871     }
872     else
873       mutt_message(_("%d kept, %d deleted"), m->msg_count - m->msg_deleted, m->msg_deleted);
874   }
875 
876   const bool c_save_empty = cs_subset_bool(NeoMutt->sub, "save_empty");
877   if ((m->msg_count == m->msg_deleted) &&
878       ((m->type == MUTT_MMDF) || (m->type == MUTT_MBOX)) &&
879       !mutt_is_spool(mailbox_path(m)) && !c_save_empty)
880   {
881     mutt_file_unlink_empty(mailbox_path(m));
882   }
883 
884 #ifdef USE_SIDEBAR
885   if ((purge == MUTT_YES) && (m->msg_deleted != 0))
886   {
887     for (i = 0; i < m->msg_count; i++)
888     {
889       struct Email *e = m->emails[i];
890       if (!e)
891         break;
892       if (e->deleted && !e->read)
893       {
894         m->msg_unread--;
895         if (!e->old)
896           m->msg_new--;
897       }
898       if (e->deleted && e->flagged)
899         m->msg_flagged--;
900     }
901   }
902 #endif
903 
904   mx_fastclose_mailbox(m);
905 
906   rc = MX_STATUS_OK;
907 
908 cleanup:
909   mutt_buffer_pool_release(&mbox);
910   mutt_buffer_pool_release(&buf);
911   return rc;
912 }
913 
914 /**
915  * mx_mbox_sync - Save changes to mailbox
916  * @param[in]  m          Mailbox
917  * @retval enum #MxStatus
918  *
919  * @note The flag retvals come from a call to a backend sync function
920  */
mx_mbox_sync(struct Mailbox * m)921 enum MxStatus mx_mbox_sync(struct Mailbox *m)
922 {
923   if (!m)
924     return MX_STATUS_ERROR;
925 
926   enum MxStatus rc = MX_STATUS_OK;
927   int purge = 1;
928   int msgcount, deleted;
929 
930   if (m->dontwrite)
931   {
932     char buf[256], tmp[256];
933     if (km_expand_key(buf, sizeof(buf), km_find_func(MENU_MAIN, OP_TOGGLE_WRITE)))
934       snprintf(tmp, sizeof(tmp), _(" Press '%s' to toggle write"), buf);
935     else
936       mutt_str_copy(tmp, _("Use 'toggle-write' to re-enable write"), sizeof(tmp));
937 
938     mutt_error(_("Mailbox is marked unwritable. %s"), tmp);
939     return MX_STATUS_ERROR;
940   }
941   else if (m->readonly)
942   {
943     mutt_error(_("Mailbox is read-only"));
944     return MX_STATUS_ERROR;
945   }
946 
947   if (!m->changed && (m->msg_deleted == 0))
948   {
949     if (m->verbose)
950       mutt_message(_("Mailbox is unchanged"));
951     return MX_STATUS_OK;
952   }
953 
954   if (m->msg_deleted != 0)
955   {
956     char buf[128];
957 
958     snprintf(buf, sizeof(buf),
959              ngettext("Purge %d deleted message?", "Purge %d deleted messages?", m->msg_deleted),
960              m->msg_deleted);
961     const enum QuadOption c_delete = cs_subset_quad(NeoMutt->sub, "delete");
962     purge = query_quadoption(c_delete, buf);
963     if (purge == MUTT_ABORT)
964       return MX_STATUS_ERROR;
965     if (purge == MUTT_NO)
966     {
967       if (!m->changed)
968         return MX_STATUS_OK; /* nothing to do! */
969       /* let IMAP servers hold on to D flags */
970       if (m->type != MUTT_IMAP)
971       {
972         for (int i = 0; i < m->msg_count; i++)
973         {
974           struct Email *e = m->emails[i];
975           if (!e)
976             break;
977           e->deleted = false;
978           e->purge = false;
979         }
980         m->msg_deleted = 0;
981       }
982     }
983     mailbox_changed(m, NT_MAILBOX_UNTAG);
984   }
985 
986   /* really only for IMAP - imap_sync_mailbox results in a call to
987    * ctx_update_tables, so m->msg_deleted is 0 when it comes back */
988   msgcount = m->msg_count;
989   deleted = m->msg_deleted;
990 
991   const char *const c_trash = cs_subset_string(NeoMutt->sub, "trash");
992   const struct Mailbox *m_trash = mx_mbox_find(m->account, c_trash);
993   if (purge && (m->msg_deleted != 0) && (m != m_trash))
994   {
995     if (trash_append(m) != 0)
996       return MX_STATUS_OK;
997   }
998 
999 #ifdef USE_IMAP
1000   if (m->type == MUTT_IMAP)
1001     rc = imap_sync_mailbox(m, purge, false);
1002   else
1003 #endif
1004     rc = sync_mailbox(m);
1005   if (rc != MX_STATUS_ERROR)
1006   {
1007 #ifdef USE_IMAP
1008     if ((m->type == MUTT_IMAP) && !purge)
1009     {
1010       if (m->verbose)
1011         mutt_message(_("Mailbox checkpointed"));
1012     }
1013     else
1014 #endif
1015     {
1016       if (m->verbose)
1017         mutt_message(_("%d kept, %d deleted"), msgcount - deleted, deleted);
1018     }
1019 
1020     mutt_sleep(0);
1021 
1022     const bool c_save_empty = cs_subset_bool(NeoMutt->sub, "save_empty");
1023     if ((m->msg_count == m->msg_deleted) &&
1024         ((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF)) &&
1025         !mutt_is_spool(mailbox_path(m)) && !c_save_empty)
1026     {
1027       unlink(mailbox_path(m));
1028       mx_fastclose_mailbox(m);
1029       return MX_STATUS_OK;
1030     }
1031 
1032     /* if we haven't deleted any messages, we don't need to resort
1033      * ... except for certain folder formats which need "unsorted"
1034      * sort order in order to synchronize folders.
1035      *
1036      * MH and maildir are safe.  mbox-style seems to need re-sorting,
1037      * at least with the new threading code.  */
1038     if (purge || ((m->type != MUTT_MAILDIR) && (m->type != MUTT_MH)))
1039     {
1040       /* IMAP does this automatically after handling EXPUNGE */
1041       if (m->type != MUTT_IMAP)
1042       {
1043         mailbox_changed(m, NT_MAILBOX_UPDATE);
1044         mailbox_changed(m, NT_MAILBOX_RESORT);
1045       }
1046     }
1047   }
1048 
1049   return rc;
1050 }
1051 
1052 /**
1053  * mx_msg_open_new - Open a new message
1054  * @param m     Destination mailbox
1055  * @param e     Message being copied (required for maildir support, because the filename depends on the message flags)
1056  * @param flags Flags, see #MsgOpenFlags
1057  * @retval ptr New Message
1058  */
mx_msg_open_new(struct Mailbox * m,const struct Email * e,MsgOpenFlags flags)1059 struct Message *mx_msg_open_new(struct Mailbox *m, const struct Email *e, MsgOpenFlags flags)
1060 {
1061   if (!m)
1062     return NULL;
1063 
1064   struct Address *p = NULL;
1065   struct Message *msg = NULL;
1066 
1067   if (!m->mx_ops || !m->mx_ops->msg_open_new)
1068   {
1069     mutt_debug(LL_DEBUG1, "function unimplemented for mailbox type %d\n", m->type);
1070     return NULL;
1071   }
1072 
1073   msg = mutt_mem_calloc(1, sizeof(struct Message));
1074   msg->write = true;
1075 
1076   if (e)
1077   {
1078     msg->flags.flagged = e->flagged;
1079     msg->flags.replied = e->replied;
1080     msg->flags.read = e->read;
1081     msg->flags.draft = (flags & MUTT_SET_DRAFT);
1082     msg->received = e->received;
1083   }
1084 
1085   if (msg->received == 0)
1086     msg->received = mutt_date_epoch();
1087 
1088   if (m->mx_ops->msg_open_new(m, msg, e))
1089   {
1090     if (m->type == MUTT_MMDF)
1091       fputs(MMDF_SEP, msg->fp);
1092 
1093     if (((m->type == MUTT_MBOX) || (m->type == MUTT_MMDF)) && (flags & MUTT_ADD_FROM))
1094     {
1095       if (e)
1096       {
1097         p = TAILQ_FIRST(&e->env->return_path);
1098         if (!p)
1099           p = TAILQ_FIRST(&e->env->sender);
1100         if (!p)
1101           p = TAILQ_FIRST(&e->env->from);
1102       }
1103 
1104       // Force a 'C' locale for the date, so that day/month names are in English
1105       locale_t loc = newlocale(LC_TIME_MASK, "C", 0);
1106       char buf[64] = { 0 };
1107       struct tm tm = mutt_date_localtime(msg->received);
1108       strftime_l(buf, sizeof(buf), "%a %b %e %H:%M:%S %Y", &tm, loc);
1109       freelocale(loc);
1110       fprintf(msg->fp, "From %s %s\n", p ? p->mailbox : NONULL(Username), buf);
1111     }
1112   }
1113   else
1114     FREE(&msg);
1115 
1116   return msg;
1117 }
1118 
1119 /**
1120  * mx_mbox_check - Check for new mail - Wrapper for MxOps::mbox_check()
1121  * @param m          Mailbox
1122  * @retval enum #MxStatus
1123  */
mx_mbox_check(struct Mailbox * m)1124 enum MxStatus mx_mbox_check(struct Mailbox *m)
1125 {
1126   if (!m || !m->mx_ops)
1127     return MX_STATUS_ERROR;
1128 
1129   enum MxStatus rc = m->mx_ops->mbox_check(m);
1130   if ((rc == MX_STATUS_NEW_MAIL) || (rc == MX_STATUS_REOPENED))
1131   {
1132     mailbox_changed(m, NT_MAILBOX_INVALID);
1133   }
1134 
1135   return rc;
1136 }
1137 
1138 /**
1139  * mx_msg_open - Return a stream pointer for a message
1140  * @param m   Mailbox
1141  * @param msgno Message number
1142  * @retval ptr  Message
1143  * @retval NULL Error
1144  */
mx_msg_open(struct Mailbox * m,int msgno)1145 struct Message *mx_msg_open(struct Mailbox *m, int msgno)
1146 {
1147   if (!m || !m->emails || (msgno < 0) || (msgno >= m->msg_count))
1148     return NULL;
1149 
1150   if (!m->mx_ops || !m->mx_ops->msg_open)
1151   {
1152     mutt_debug(LL_DEBUG1, "function not implemented for mailbox type %d\n", m->type);
1153     return NULL;
1154   }
1155 
1156   struct Message *msg = mutt_mem_calloc(1, sizeof(struct Message));
1157   if (!m->mx_ops->msg_open(m, msg, msgno))
1158     FREE(&msg);
1159 
1160   return msg;
1161 }
1162 
1163 /**
1164  * mx_msg_commit - Commit a message to a folder - Wrapper for MxOps::msg_commit()
1165  * @param m   Mailbox
1166  * @param msg Message to commit
1167  * @retval  0 Success
1168  * @retval -1 Failure
1169  */
mx_msg_commit(struct Mailbox * m,struct Message * msg)1170 int mx_msg_commit(struct Mailbox *m, struct Message *msg)
1171 {
1172   if (!m || !m->mx_ops || !m->mx_ops->msg_commit || !msg)
1173     return -1;
1174 
1175   if (!(msg->write && m->append))
1176   {
1177     mutt_debug(LL_DEBUG1, "msg->write = %d, m->append = %d\n", msg->write, m->append);
1178     return -1;
1179   }
1180 
1181   return m->mx_ops->msg_commit(m, msg);
1182 }
1183 
1184 /**
1185  * mx_msg_close - Close a message
1186  * @param[in]  m   Mailbox
1187  * @param[out] msg Message to close
1188  * @retval  0 Success
1189  * @retval -1 Failure
1190  */
mx_msg_close(struct Mailbox * m,struct Message ** msg)1191 int mx_msg_close(struct Mailbox *m, struct Message **msg)
1192 {
1193   if (!m || !msg || !*msg)
1194     return 0;
1195 
1196   int rc = 0;
1197 
1198   if (m->mx_ops && m->mx_ops->msg_close)
1199     rc = m->mx_ops->msg_close(m, *msg);
1200 
1201   if ((*msg)->path)
1202   {
1203     mutt_debug(LL_DEBUG1, "unlinking %s\n", (*msg)->path);
1204     unlink((*msg)->path);
1205     FREE(&(*msg)->path);
1206   }
1207 
1208   FREE(&(*msg)->committed_path);
1209   FREE(msg);
1210   return rc;
1211 }
1212 
1213 /**
1214  * mx_alloc_memory - Create storage for the emails
1215  * @param m Mailbox
1216  */
mx_alloc_memory(struct Mailbox * m)1217 void mx_alloc_memory(struct Mailbox *m)
1218 {
1219   size_t s = MAX(sizeof(struct Email *), sizeof(int));
1220 
1221   if ((m->email_max + 25) * s < m->email_max * s)
1222   {
1223     mutt_error(_("Out of memory"));
1224     mutt_exit(1);
1225   }
1226 
1227   m->email_max += 25;
1228   if (m->emails)
1229   {
1230     mutt_mem_realloc(&m->emails, sizeof(struct Email *) * m->email_max);
1231     mutt_mem_realloc(&m->v2r, sizeof(int) * m->email_max);
1232   }
1233   else
1234   {
1235     m->emails = mutt_mem_calloc(m->email_max, sizeof(struct Email *));
1236     m->v2r = mutt_mem_calloc(m->email_max, sizeof(int));
1237   }
1238   for (int i = m->email_max - 25; i < m->email_max; i++)
1239   {
1240     m->emails[i] = NULL;
1241     m->v2r[i] = -1;
1242   }
1243 }
1244 
1245 /**
1246  * mx_path_is_empty - Is the mailbox empty
1247  * @param path Mailbox to check
1248  * @retval 1 Mailbox is empty
1249  * @retval 0 Mailbox contains mail
1250  * @retval -1 Error
1251  */
mx_path_is_empty(const char * path)1252 int mx_path_is_empty(const char *path)
1253 {
1254   if (!path || (*path == '\0'))
1255     return -1;
1256 
1257   enum MailboxType type = mx_path_probe(path);
1258   const struct MxOps *ops = mx_get_ops(type);
1259   if (!ops || !ops->path_is_empty)
1260     return -1;
1261 
1262   return ops->path_is_empty(path);
1263 }
1264 
1265 /**
1266  * mx_tags_edit - Start the tag editor of the mailbox
1267  * @param m      Mailbox
1268  * @param tags   Existing tags
1269  * @param buf    Buffer for the results
1270  * @param buflen Length of the buffer
1271  * @retval -1 Error
1272  * @retval 0  No valid user input
1273  * @retval 1  Buffer set
1274  */
mx_tags_edit(struct Mailbox * m,const char * tags,char * buf,size_t buflen)1275 int mx_tags_edit(struct Mailbox *m, const char *tags, char *buf, size_t buflen)
1276 {
1277   if (!m || !buf)
1278     return -1;
1279 
1280   if (m->mx_ops->tags_edit)
1281     return m->mx_ops->tags_edit(m, tags, buf, buflen);
1282 
1283   mutt_message(_("Folder doesn't support tagging, aborting"));
1284   return -1;
1285 }
1286 
1287 /**
1288  * mx_tags_commit - Save tags to the Mailbox - Wrapper for MxOps::tags_commit()
1289  * @param m    Mailbox
1290  * @param e    Email
1291  * @param tags Tags to save
1292  * @retval  0 Success
1293  * @retval -1 Failure
1294  */
mx_tags_commit(struct Mailbox * m,struct Email * e,char * tags)1295 int mx_tags_commit(struct Mailbox *m, struct Email *e, char *tags)
1296 {
1297   if (!m || !e || !tags)
1298     return -1;
1299 
1300   if (m->mx_ops->tags_commit)
1301     return m->mx_ops->tags_commit(m, e, tags);
1302 
1303   mutt_message(_("Folder doesn't support tagging, aborting"));
1304   return -1;
1305 }
1306 
1307 /**
1308  * mx_tags_is_supported - Return true if mailbox support tagging
1309  * @param m Mailbox
1310  * @retval true Tagging is supported
1311  */
mx_tags_is_supported(struct Mailbox * m)1312 bool mx_tags_is_supported(struct Mailbox *m)
1313 {
1314   return m && m->mx_ops->tags_commit && m->mx_ops->tags_edit;
1315 }
1316 
1317 /**
1318  * mx_path_probe - Find a mailbox that understands a path
1319  * @param path Path to examine
1320  * @retval num Type, e.g. #MUTT_IMAP
1321  */
mx_path_probe(const char * path)1322 enum MailboxType mx_path_probe(const char *path)
1323 {
1324   if (!path)
1325     return MUTT_UNKNOWN;
1326 
1327   enum MailboxType rc = MUTT_UNKNOWN;
1328 
1329   // First, search the non-local Mailbox types (is_local == false)
1330   for (const struct MxOps **ops = MxOps; *ops; ops++)
1331   {
1332     if ((*ops)->is_local)
1333       continue;
1334     rc = (*ops)->path_probe(path, NULL);
1335     if (rc != MUTT_UNKNOWN)
1336       return rc;
1337   }
1338 
1339   struct stat st = { 0 };
1340   if (stat(path, &st) != 0)
1341   {
1342     mutt_debug(LL_DEBUG1, "unable to stat %s: %s (errno %d)\n", path, strerror(errno), errno);
1343     return MUTT_UNKNOWN;
1344   }
1345 
1346   if (S_ISFIFO(st.st_mode))
1347   {
1348     mutt_error(_("Can't open %s: it is a pipe"), path);
1349     return MUTT_UNKNOWN;
1350   }
1351 
1352   // Next, search the local Mailbox types (is_local == true)
1353   for (const struct MxOps **ops = MxOps; *ops; ops++)
1354   {
1355     if (!(*ops)->is_local)
1356       continue;
1357     rc = (*ops)->path_probe(path, &st);
1358     if (rc != MUTT_UNKNOWN)
1359       return rc;
1360   }
1361 
1362   return rc;
1363 }
1364 
1365 /**
1366  * mx_path_canon - Canonicalise a mailbox path - Wrapper for MxOps::path_canon()
1367  */
mx_path_canon(char * buf,size_t buflen,const char * folder,enum MailboxType * type)1368 int mx_path_canon(char *buf, size_t buflen, const char *folder, enum MailboxType *type)
1369 {
1370   if (!buf)
1371     return -1;
1372 
1373   for (size_t i = 0; i < 3; i++)
1374   {
1375     /* Look for !! ! - < > or ^ followed by / or NUL */
1376     if ((buf[0] == '!') && (buf[1] == '!'))
1377     {
1378       if (((buf[2] == '/') || (buf[2] == '\0')))
1379       {
1380         mutt_str_inline_replace(buf, buflen, 2, LastFolder);
1381       }
1382     }
1383     else if ((buf[0] == '+') || (buf[0] == '='))
1384     {
1385       size_t folder_len = mutt_str_len(folder);
1386       if ((folder_len > 0) && (folder[folder_len - 1] != '/'))
1387       {
1388         buf[0] = '/';
1389         mutt_str_inline_replace(buf, buflen, 0, folder);
1390       }
1391       else
1392       {
1393         mutt_str_inline_replace(buf, buflen, 1, folder);
1394       }
1395     }
1396     else if ((buf[1] == '/') || (buf[1] == '\0'))
1397     {
1398       if (buf[0] == '!')
1399       {
1400         const char *const c_spool_file =
1401             cs_subset_string(NeoMutt->sub, "spool_file");
1402         mutt_str_inline_replace(buf, buflen, 1, c_spool_file);
1403       }
1404       else if (buf[0] == '-')
1405       {
1406         mutt_str_inline_replace(buf, buflen, 1, LastFolder);
1407       }
1408       else if (buf[0] == '<')
1409       {
1410         const char *const c_record = cs_subset_string(NeoMutt->sub, "record");
1411         mutt_str_inline_replace(buf, buflen, 1, c_record);
1412       }
1413       else if (buf[0] == '>')
1414       {
1415         const char *const c_mbox = cs_subset_string(NeoMutt->sub, "mbox");
1416         mutt_str_inline_replace(buf, buflen, 1, c_mbox);
1417       }
1418       else if (buf[0] == '^')
1419       {
1420         mutt_str_inline_replace(buf, buflen, 1, CurrentFolder);
1421       }
1422       else if (buf[0] == '~')
1423       {
1424         mutt_str_inline_replace(buf, buflen, 1, HomeDir);
1425       }
1426     }
1427     else if (buf[0] == '@')
1428     {
1429       /* elm compatibility, @ expands alias to user name */
1430       struct AddressList *al = alias_lookup(buf + 1);
1431       if (!al || TAILQ_EMPTY(al))
1432         break;
1433 
1434       struct Email *e = email_new();
1435       e->env = mutt_env_new();
1436       mutt_addrlist_copy(&e->env->from, al, false);
1437       mutt_addrlist_copy(&e->env->to, al, false);
1438       mutt_default_save(buf, buflen, e);
1439       email_free(&e);
1440       break;
1441     }
1442     else
1443     {
1444       break;
1445     }
1446   }
1447 
1448   // if (!folder) //XXX - use inherited version, or pass NULL to backend?
1449   //   return -1;
1450 
1451   enum MailboxType type2 = mx_path_probe(buf);
1452   if (type)
1453     *type = type2;
1454   const struct MxOps *ops = mx_get_ops(type2);
1455   if (!ops || !ops->path_canon)
1456     return -1;
1457 
1458   if (ops->path_canon(buf, buflen) < 0)
1459   {
1460     mutt_path_canon(buf, buflen, HomeDir, true);
1461   }
1462 
1463   return 0;
1464 }
1465 
1466 /**
1467  * mx_path_canon2 - Canonicalise the path to realpath
1468  * @param m      Mailbox
1469  * @param folder Path to canonicalise
1470  * @retval  0 Success
1471  * @retval -1 Failure
1472  */
mx_path_canon2(struct Mailbox * m,const char * folder)1473 int mx_path_canon2(struct Mailbox *m, const char *folder)
1474 {
1475   if (!m)
1476     return -1;
1477 
1478   char buf[PATH_MAX];
1479 
1480   if (m->realpath)
1481     mutt_str_copy(buf, m->realpath, sizeof(buf));
1482   else
1483     mutt_str_copy(buf, mailbox_path(m), sizeof(buf));
1484 
1485   int rc = mx_path_canon(buf, sizeof(buf), folder, &m->type);
1486 
1487   mutt_str_replace(&m->realpath, buf);
1488 
1489   if (rc >= 0)
1490   {
1491     m->mx_ops = mx_get_ops(m->type);
1492     mutt_buffer_strcpy(&m->pathbuf, m->realpath);
1493   }
1494 
1495   return rc;
1496 }
1497 
1498 /**
1499  * mx_path_pretty - Abbreviate a mailbox path - Wrapper for MxOps::path_pretty()
1500  */
mx_path_pretty(char * buf,size_t buflen,const char * folder)1501 int mx_path_pretty(char *buf, size_t buflen, const char *folder)
1502 {
1503   if (!buf)
1504     return -1;
1505 
1506   enum MailboxType type = mx_path_probe(buf);
1507   const struct MxOps *ops = mx_get_ops(type);
1508   if (!ops)
1509     return -1;
1510 
1511   if (!ops->path_canon)
1512     return -1;
1513 
1514   if (ops->path_canon(buf, buflen) < 0)
1515     return -1;
1516 
1517   if (!ops->path_pretty)
1518     return -1;
1519 
1520   if (ops->path_pretty(buf, buflen, folder) < 0)
1521     return -1;
1522 
1523   return 0;
1524 }
1525 
1526 /**
1527  * mx_path_parent - Find the parent of a mailbox path - Wrapper for MxOps::path_parent()
1528  */
mx_path_parent(char * buf,size_t buflen)1529 int mx_path_parent(char *buf, size_t buflen)
1530 {
1531   if (!buf)
1532     return -1;
1533 
1534   return 0;
1535 }
1536 
1537 /**
1538  * mx_msg_padding_size - Bytes of padding between messages - Wrapper for MxOps::msg_padding_size()
1539  * @param m Mailbox
1540  * @retval num Number of bytes of padding
1541  *
1542  * mmdf and mbox add separators, which leads a small discrepancy when computing
1543  * vsize for a limited view.
1544  */
mx_msg_padding_size(struct Mailbox * m)1545 int mx_msg_padding_size(struct Mailbox *m)
1546 {
1547   if (!m || !m->mx_ops || !m->mx_ops->msg_padding_size)
1548     return 0;
1549 
1550   return m->mx_ops->msg_padding_size(m);
1551 }
1552 
1553 /**
1554  * mx_ac_find - Find the Account owning a Mailbox
1555  * @param m Mailbox
1556  * @retval ptr  Account
1557  * @retval NULL None found
1558  */
mx_ac_find(struct Mailbox * m)1559 struct Account *mx_ac_find(struct Mailbox *m)
1560 {
1561   if (!m || !m->mx_ops || !m->realpath)
1562     return NULL;
1563 
1564   struct Account *np = NULL;
1565   TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
1566   {
1567     if (np->type != m->type)
1568       continue;
1569 
1570     if (m->mx_ops->ac_owns_path(np, m->realpath))
1571       return np;
1572   }
1573 
1574   return NULL;
1575 }
1576 
1577 /**
1578  * mx_mbox_find - Find a Mailbox on an Account
1579  * @param a    Account to search
1580  * @param path Path to find
1581  * @retval ptr Mailbox
1582  */
mx_mbox_find(struct Account * a,const char * path)1583 struct Mailbox *mx_mbox_find(struct Account *a, const char *path)
1584 {
1585   if (!a || !path)
1586     return NULL;
1587 
1588   struct MailboxNode *np = NULL;
1589   struct Url *url_p = NULL;
1590   struct Url *url_a = NULL;
1591 
1592   const bool use_url = (a->type == MUTT_IMAP);
1593   if (use_url)
1594   {
1595     url_p = url_parse(path);
1596     if (!url_p)
1597       goto done;
1598   }
1599 
1600   STAILQ_FOREACH(np, &a->mailboxes, entries)
1601   {
1602     if (!use_url)
1603     {
1604       if (mutt_str_equal(np->mailbox->realpath, path))
1605         return np->mailbox;
1606       continue;
1607     }
1608 
1609     url_free(&url_a);
1610     url_a = url_parse(np->mailbox->realpath);
1611     if (!url_a)
1612       continue;
1613 
1614     if (!mutt_istr_equal(url_a->host, url_p->host))
1615       continue;
1616     if (url_p->user && !mutt_istr_equal(url_a->user, url_p->user))
1617       continue;
1618     if (a->type == MUTT_IMAP)
1619     {
1620       if (imap_mxcmp(url_a->path, url_p->path) == 0)
1621         break;
1622     }
1623     else
1624     {
1625       if (mutt_str_equal(url_a->path, url_p->path))
1626         break;
1627     }
1628   }
1629 
1630 done:
1631   url_free(&url_p);
1632   url_free(&url_a);
1633 
1634   if (!np)
1635     return NULL;
1636   return np->mailbox;
1637 }
1638 
1639 /**
1640  * mx_mbox_find2 - Find a Mailbox on an Account
1641  * @param path Path to find
1642  * @retval ptr  Mailbox
1643  * @retval NULL No match
1644  */
mx_mbox_find2(const char * path)1645 struct Mailbox *mx_mbox_find2(const char *path)
1646 {
1647   if (!path)
1648     return NULL;
1649 
1650   char buf[PATH_MAX];
1651   mutt_str_copy(buf, path, sizeof(buf));
1652   const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
1653   mx_path_canon(buf, sizeof(buf), c_folder, NULL);
1654 
1655   struct Account *np = NULL;
1656   TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
1657   {
1658     struct Mailbox *m = mx_mbox_find(np, buf);
1659     if (m)
1660       return m;
1661   }
1662 
1663   return NULL;
1664 }
1665 
1666 /**
1667  * mx_path_resolve - Get a Mailbox for a path
1668  * @param path Mailbox path
1669  * @retval ptr Mailbox
1670  *
1671  * If there isn't a Mailbox for the path, one will be created.
1672  */
mx_path_resolve(const char * path)1673 struct Mailbox *mx_path_resolve(const char *path)
1674 {
1675   if (!path)
1676     return NULL;
1677 
1678   struct Mailbox *m = mx_mbox_find2(path);
1679   if (m)
1680     return m;
1681 
1682   m = mailbox_new();
1683   m->flags = MB_HIDDEN;
1684   mutt_buffer_strcpy(&m->pathbuf, path);
1685   const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
1686   mx_path_canon2(m, c_folder);
1687 
1688   return m;
1689 }
1690 
1691 /**
1692  * mx_mbox_find_by_name_ac - Find a Mailbox with given name under an Account
1693  * @param a    Account to search
1694  * @param name Name to find
1695  * @retval ptr Mailbox
1696  */
mx_mbox_find_by_name_ac(struct Account * a,const char * name)1697 static struct Mailbox *mx_mbox_find_by_name_ac(struct Account *a, const char *name)
1698 {
1699   if (!a || !name)
1700     return NULL;
1701 
1702   struct MailboxNode *np = NULL;
1703 
1704   STAILQ_FOREACH(np, &a->mailboxes, entries)
1705   {
1706     if (mutt_str_equal(np->mailbox->name, name))
1707       return np->mailbox;
1708   }
1709 
1710   return NULL;
1711 }
1712 
1713 /**
1714  * mx_mbox_find_by_name - Find a Mailbox with given name
1715  * @param name Name to search
1716  * @retval ptr Mailbox
1717  */
mx_mbox_find_by_name(const char * name)1718 static struct Mailbox *mx_mbox_find_by_name(const char *name)
1719 {
1720   if (!name)
1721     return NULL;
1722 
1723   struct Account *np = NULL;
1724   TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
1725   {
1726     struct Mailbox *m = mx_mbox_find_by_name_ac(np, name);
1727     if (m)
1728       return m;
1729   }
1730 
1731   return NULL;
1732 }
1733 
1734 /**
1735  * mx_resolve - Get a Mailbox from either a path or name
1736  * @param path_or_name Mailbox path or name
1737  * @retval ptr         Mailbox
1738  *
1739  * Order of resolving:
1740  *  1. Name
1741  *  2. Path
1742  */
mx_resolve(const char * path_or_name)1743 struct Mailbox *mx_resolve(const char *path_or_name)
1744 {
1745   if (!path_or_name)
1746     return NULL;
1747 
1748   // Order is name first because you can create a Mailbox from
1749   // a path, but can't from a name. So fallback behavior creates
1750   // a new Mailbox for us.
1751   struct Mailbox *m = mx_mbox_find_by_name(path_or_name);
1752   if (!m)
1753     m = mx_path_resolve(path_or_name);
1754 
1755   return m;
1756 }
1757 
1758 /**
1759  * mx_ac_add - Add a Mailbox to an Account - Wrapper for MxOps::ac_add()
1760  */
mx_ac_add(struct Account * a,struct Mailbox * m)1761 bool mx_ac_add(struct Account *a, struct Mailbox *m)
1762 {
1763   if (!a || !m || !m->mx_ops || !m->mx_ops->ac_add)
1764     return false;
1765 
1766   return m->mx_ops->ac_add(a, m) && account_mailbox_add(a, m);
1767 }
1768 
1769 /**
1770  * mx_ac_remove - Remove a Mailbox from an Account and delete Account if empty
1771  * @param m Mailbox to remove
1772  * @retval  0 Success
1773  * @retval -1 Error
1774  *
1775  * @note The mailbox is NOT free'd
1776  */
mx_ac_remove(struct Mailbox * m)1777 int mx_ac_remove(struct Mailbox *m)
1778 {
1779   if (!m || !m->account)
1780     return -1;
1781 
1782   struct Account *a = m->account;
1783   account_mailbox_remove(m->account, m);
1784   if (STAILQ_EMPTY(&a->mailboxes))
1785   {
1786     neomutt_account_remove(NeoMutt, a);
1787   }
1788   return 0;
1789 }
1790 
1791 /**
1792  * mx_mbox_check_stats - Check the statistics for a mailbox - Wrapper for MxOps::mbox_check_stats()
1793  *
1794  * @note Emits: #NT_MAILBOX_CHANGE
1795  */
mx_mbox_check_stats(struct Mailbox * m,uint8_t flags)1796 enum MxStatus mx_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1797 {
1798   if (!m)
1799     return MX_STATUS_ERROR;
1800 
1801   enum MxStatus rc = m->mx_ops->mbox_check_stats(m, flags);
1802   if (rc != MX_STATUS_ERROR)
1803   {
1804     struct EventMailbox ev_m = { m };
1805     notify_send(m->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m);
1806   }
1807 
1808   return rc;
1809 }
1810 
1811 /**
1812  * mx_save_hcache - Save message to the header cache - Wrapper for MxOps::msg_save_hcache()
1813  * @param m Mailbox
1814  * @param e Email
1815  * @retval  0 Success
1816  * @retval -1 Failure
1817  *
1818  * Write a single header out to the header cache.
1819  */
mx_save_hcache(struct Mailbox * m,struct Email * e)1820 int mx_save_hcache(struct Mailbox *m, struct Email *e)
1821 {
1822   if (!m || !m->mx_ops || !m->mx_ops->msg_save_hcache || !e)
1823     return 0;
1824 
1825   return m->mx_ops->msg_save_hcache(m, e);
1826 }
1827 
1828 /**
1829  * mx_type - Return the type of the Mailbox
1830  * @param m Mailbox
1831  * @retval enum #MailboxType
1832  */
mx_type(struct Mailbox * m)1833 enum MailboxType mx_type(struct Mailbox *m)
1834 {
1835   return m ? m->type : MUTT_MAILBOX_ERROR;
1836 }
1837 
1838 /**
1839  * mx_toggle_write - Toggle the mailbox's readonly flag
1840  * @param m Mailbox
1841  * @retval  0 Success
1842  * @retval -1 Error
1843  */
mx_toggle_write(struct Mailbox * m)1844 int mx_toggle_write(struct Mailbox *m)
1845 {
1846   if (!m)
1847     return -1;
1848 
1849   if (m->readonly)
1850   {
1851     mutt_error(_("Can't toggle write on a readonly mailbox"));
1852     return -1;
1853   }
1854 
1855   if (m->dontwrite)
1856   {
1857     m->dontwrite = false;
1858     mutt_message(_("Changes to folder will be written on folder exit"));
1859   }
1860   else
1861   {
1862     m->dontwrite = true;
1863     mutt_message(_("Changes to folder will not be written"));
1864   }
1865 
1866   struct EventMailbox ev_m = { m };
1867   notify_send(m->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m);
1868   return 0;
1869 }
1870