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