Coverage Report

Created: 2023-06-07 06:15

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