• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 17366932035

31 Aug 2025 12:06PM UTC coverage: 50.099% (+0.05%) from 50.049%
17366932035

push

github

web-flow
tweak observer event types (#4023)

9132 of 18228 relevant lines covered (50.1%)

272.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/imap/util.c
1
/**
2
 * @file
3
 * IMAP helper functions
4
 *
5
 * @authors
6
 * Copyright (C) 1996-1998,2010,2012-2013 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 1996-1999 Brandon Long <blong@fiction.net>
8
 * Copyright (C) 1999-2009,2012 Brendan Cully <brendan@kublai.com>
9
 * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org>
10
 * Copyright (C) 2018 Mehdi Abaakouk <sileht@sileht.net>
11
 * Copyright (C) 2018-2022 Pietro Cerutti <gahr@gahr.ch>
12
 *
13
 * @copyright
14
 * This program is free software: you can redistribute it and/or modify it under
15
 * the terms of the GNU General Public License as published by the Free Software
16
 * Foundation, either version 2 of the License, or (at your option) any later
17
 * version.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
22
 * details.
23
 *
24
 * You should have received a copy of the GNU General Public License along with
25
 * this program.  If not, see <http://www.gnu.org/licenses/>.
26
 */
27

28
/**
29
 * @page imap_util IMAP helper functions
30
 *
31
 * IMAP helper functions
32
 */
33

34
#include "config.h"
35
#include <arpa/inet.h>
36
#include <errno.h>
37
#include <netdb.h>
38
#include <signal.h>
39
#include <stdbool.h>
40
#include <stdio.h>
41
#include <string.h>
42
#include <sys/types.h>
43
#include <sys/wait.h>
44
#include <unistd.h>
45
#include "private.h"
46
#include "mutt/lib.h"
47
#include "config/lib.h"
48
#include "email/lib.h"
49
#include "core/lib.h"
50
#include "conn/lib.h"
51
#include "lib.h"
52
#include "bcache/lib.h"
53
#include "question/lib.h"
54
#include "adata.h"
55
#include "edata.h"
56
#include "globals.h"
57
#include "mdata.h"
58
#include "msn.h"
59
#ifdef USE_HCACHE
60
#include "hcache/lib.h"
61
#endif
62

63
/**
64
 * imap_adata_find - Find the Account data for this path
65
 * @param path  Path to search for
66
 * @param adata Imap Account data
67
 * @param mdata Imap Mailbox data
68
 * @retval  0 Success
69
 * @retval -1 Failure
70
 */
71
int imap_adata_find(const char *path, struct ImapAccountData **adata,
×
72
                    struct ImapMboxData **mdata)
73
{
74
  struct ConnAccount cac = { { 0 } };
×
75
  struct ImapAccountData *tmp_adata = NULL;
76
  char tmp[1024] = { 0 };
×
77

78
  if (imap_parse_path(path, &cac, tmp, sizeof(tmp)) < 0)
×
79
    return -1;
80

81
  struct Account *np = NULL;
82
  TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
×
83
  {
84
    if (np->type != MUTT_IMAP)
×
85
      continue;
×
86

87
    tmp_adata = np->adata;
×
88
    if (!tmp_adata)
×
89
      continue;
×
90
    if (imap_account_match(&tmp_adata->conn->account, &cac))
×
91
    {
92
      if (mdata)
×
93
      {
94
        *mdata = imap_mdata_new(tmp_adata, tmp);
×
95
      }
96
      *adata = tmp_adata;
×
97
      return 0;
×
98
    }
99
  }
100
  mutt_debug(LL_DEBUG3, "no ImapAccountData found\n");
×
101
  return -1;
×
102
}
103

104
/**
105
 * imap_mdata_cache_reset - Release and clear cache data of ImapMboxData structure
106
 * @param mdata Imap Mailbox data
107
 */
108
void imap_mdata_cache_reset(struct ImapMboxData *mdata)
×
109
{
110
  mutt_hash_free(&mdata->uid_hash);
×
111
  imap_msn_free(&mdata->msn);
×
112
  mutt_bcache_close(&mdata->bcache);
×
113
}
×
114

115
/**
116
 * imap_get_parent - Get an IMAP folder's parent
117
 * @param mbox   Mailbox whose parent is to be determined
118
 * @param delim  Path delimiter
119
 * @param buf    Buffer for the result
120
 * @param buflen Length of the buffer
121
 */
122
void imap_get_parent(const char *mbox, char delim, char *buf, size_t buflen)
×
123
{
124
  /* Make a copy of the mailbox name, but only if the pointers are different */
125
  if (mbox != buf)
×
126
    mutt_str_copy(buf, mbox, buflen);
×
127

128
  int n = mutt_str_len(buf);
×
129

130
  /* Let's go backwards until the next delimiter
131
   *
132
   * If buf[n] is a '/', the first n-- will allow us
133
   * to ignore it. If it isn't, then buf looks like
134
   * "/aaaaa/bbbb". There is at least one "b", so we can't skip
135
   * the "/" after the 'a's.
136
   *
137
   * If buf == '/', then n-- => n == 0, so the loop ends
138
   * immediately */
139
  for (n--; (n >= 0) && (buf[n] != delim); n--)
×
140
    ; // do nothing
141

142
  /* We stopped before the beginning. There is a trailing slash.  */
143
  if (n > 0)
×
144
  {
145
    /* Strip the trailing delimiter.  */
146
    buf[n] = '\0';
×
147
  }
148
  else
149
  {
150
    buf[0] = (n == 0) ? delim : '\0';
×
151
  }
152
}
×
153

154
/**
155
 * imap_get_parent_path - Get the path of the parent folder
156
 * @param path   Mailbox whose parent is to be determined
157
 * @param buf    Buffer for the result
158
 * @param buflen Length of the buffer
159
 *
160
 * Provided an imap path, returns in buf the parent directory if
161
 * existent. Else returns the same path.
162
 */
163
void imap_get_parent_path(const char *path, char *buf, size_t buflen)
×
164
{
165
  struct ImapAccountData *adata = NULL;
×
166
  struct ImapMboxData *mdata = NULL;
×
167
  char mbox[1024] = { 0 };
×
168

169
  if (imap_adata_find(path, &adata, &mdata) < 0)
×
170
  {
171
    mutt_str_copy(buf, path, buflen);
×
172
    return;
×
173
  }
174

175
  /* Gets the parent mbox in mbox */
176
  imap_get_parent(mdata->name, adata->delim, mbox, sizeof(mbox));
×
177

178
  /* Returns a fully qualified IMAP url */
179
  imap_qualify_path(buf, buflen, &adata->conn->account, mbox);
×
180
  imap_mdata_free((void *) &mdata);
×
181
}
182

183
/**
184
 * imap_clean_path - Cleans an IMAP path using imap_fix_path
185
 * @param path Path to be cleaned
186
 * @param plen Length of the buffer
187
 *
188
 * Does it in place.
189
 */
190
void imap_clean_path(char *path, size_t plen)
×
191
{
192
  struct ImapAccountData *adata = NULL;
×
193
  struct ImapMboxData *mdata = NULL;
×
194

195
  if (imap_adata_find(path, &adata, &mdata) < 0)
×
196
    return;
×
197

198
  /* Returns a fully qualified IMAP url */
199
  imap_qualify_path(path, plen, &adata->conn->account, mdata->name);
×
200
  imap_mdata_free((void *) &mdata);
×
201
}
202

203
/**
204
 * imap_get_field - Get connection login credentials - Implements ConnAccount::get_field() - @ingroup conn_account_get_field
205
 */
206
static const char *imap_get_field(enum ConnAccountField field, void *gf_data)
×
207
{
208
  switch (field)
×
209
  {
210
    case MUTT_CA_LOGIN:
×
211
      return cs_subset_string(NeoMutt->sub, "imap_login");
×
212
    case MUTT_CA_USER:
×
213
      return cs_subset_string(NeoMutt->sub, "imap_user");
×
214
    case MUTT_CA_PASS:
×
215
      return cs_subset_string(NeoMutt->sub, "imap_pass");
×
216
    case MUTT_CA_OAUTH_CMD:
×
217
      return cs_subset_string(NeoMutt->sub, "imap_oauth_refresh_command");
×
218
    case MUTT_CA_HOST:
219
    default:
220
      return NULL;
221
  }
222
}
223

224
#ifdef USE_HCACHE
225
/**
226
 * imap_msn_index_to_uid_seqset - Convert MSN index of UIDs to Seqset
227
 * @param buf   Buffer for the result
228
 * @param mdata Imap Mailbox data
229
 *
230
 * Generates a seqseq of the UIDs in msn_index to persist in the header cache.
231
 * Empty spots are stored as 0.
232
 */
233
static void imap_msn_index_to_uid_seqset(struct Buffer *buf, struct ImapMboxData *mdata)
×
234
{
235
  int first = 1, state = 0;
236
  unsigned int cur_uid = 0, last_uid = 0;
237
  unsigned int range_begin = 0, range_end = 0;
238
  const size_t max_msn = imap_msn_highest(&mdata->msn);
×
239

240
  for (unsigned int msn = 1; msn <= max_msn + 1; msn++)
×
241
  {
242
    bool match = false;
243
    if (msn <= max_msn)
×
244
    {
245
      struct Email *e_cur = imap_msn_get(&mdata->msn, msn - 1);
×
246
      cur_uid = e_cur ? imap_edata_get(e_cur)->uid : 0;
×
247
      if (!state || (cur_uid && ((cur_uid - 1) == last_uid)))
×
248
        match = true;
249
      last_uid = cur_uid;
250
    }
251

252
    if (match)
×
253
    {
254
      switch (state)
×
255
      {
256
        case 1: /* single: convert to a range */
257
          state = 2;
258
          FALLTHROUGH;
259

260
        case 2: /* extend range ending */
×
261
          range_end = cur_uid;
262
          break;
×
263
        default:
264
          state = 1;
265
          range_begin = cur_uid;
266
          break;
267
      }
268
    }
269
    else if (state)
×
270
    {
271
      if (first)
×
272
        first = 0;
273
      else
274
        buf_addch(buf, ',');
×
275

276
      if (state == 1)
×
277
        buf_add_printf(buf, "%u", range_begin);
×
278
      else if (state == 2)
279
        buf_add_printf(buf, "%u:%u", range_begin, range_end);
×
280

281
      state = 1;
282
      range_begin = cur_uid;
283
    }
284
  }
285
}
×
286

287
/**
288
 * imap_hcache_namer - Generate a filename for the header cache - Implements ::hcache_namer_t - @ingroup hcache_namer_api
289
 */
290
static void imap_hcache_namer(const char *path, struct Buffer *dest)
×
291
{
292
  buf_printf(dest, "%s.hcache", path);
×
293
}
×
294

295
/**
296
 * imap_hcache_open - Open a header cache
297
 * @param adata  Imap Account data
298
 * @param mdata  Imap Mailbox data
299
 * @param create Create a new header cache if missing?
300
 */
301
void imap_hcache_open(struct ImapAccountData *adata, struct ImapMboxData *mdata, bool create)
×
302
{
303
  if (!adata || !mdata)
×
304
    return;
×
305

306
  if (mdata->hcache)
×
307
    return;
308

309
  struct HeaderCache *hc = NULL;
310
  struct Buffer *mbox = buf_pool_get();
×
311
  struct Buffer *cachepath = buf_pool_get();
×
312

313
  imap_cachepath(adata->delim, mdata->name, mbox);
×
314

315
  if (strstr(buf_string(mbox), "/../") || mutt_str_equal(buf_string(mbox), "..") ||
×
316
      mutt_strn_equal(buf_string(mbox), "../", 3))
×
317
  {
318
    goto cleanup;
×
319
  }
320
  size_t len = buf_len(mbox);
×
321
  if ((len > 3) && (mutt_str_equal(buf_string(mbox) + len - 3, "/..")))
×
322
    goto cleanup;
×
323

324
  struct Url url = { 0 };
×
325
  account_to_url(&adata->conn->account, &url);
×
326
  url.path = mbox->data;
×
327
  url_tobuffer(&url, cachepath, U_PATH);
×
328

329
  const char *const c_header_cache = cs_subset_path(NeoMutt->sub, "header_cache");
×
330
  hc = hcache_open(c_header_cache, buf_string(cachepath), imap_hcache_namer, create);
×
331

332
cleanup:
×
333
  buf_pool_release(&mbox);
×
334
  buf_pool_release(&cachepath);
×
335
  mdata->hcache = hc;
×
336
}
337

338
/**
339
 * imap_hcache_close - Close the header cache
340
 * @param mdata Imap Mailbox data
341
 */
342
void imap_hcache_close(struct ImapMboxData *mdata)
×
343
{
344
  if (!mdata->hcache)
×
345
    return;
346

347
  hcache_close(&mdata->hcache);
×
348
}
349

350
/**
351
 * imap_hcache_get - Get a header cache entry by its UID
352
 * @param mdata Imap Mailbox data
353
 * @param uid   UID to find
354
 * @retval ptr Email
355
 * @retval NULL Failure
356
 */
357
struct Email *imap_hcache_get(struct ImapMboxData *mdata, unsigned int uid)
×
358
{
359
  if (!mdata->hcache)
×
360
    return NULL;
361

362
  char key[16] = { 0 };
×
363

364
  snprintf(key, sizeof(key), "%u", uid);
365
  struct HCacheEntry hce = hcache_fetch_email(mdata->hcache, key, mutt_str_len(key),
×
366
                                              mdata->uidvalidity);
367
  if (!hce.email && hce.uidvalidity)
×
368
  {
369
    mutt_debug(LL_DEBUG3, "hcache uidvalidity mismatch: %u\n", hce.uidvalidity);
×
370
  }
371

372
  return hce.email;
373
}
374

375
/**
376
 * imap_hcache_put - Add an entry to the header cache
377
 * @param mdata Imap Mailbox data
378
 * @param e     Email
379
 * @retval  0 Success
380
 * @retval -1 Failure
381
 */
382
int imap_hcache_put(struct ImapMboxData *mdata, struct Email *e)
×
383
{
384
  if (!mdata->hcache)
×
385
    return -1;
386

387
  char key[16] = { 0 };
×
388

389
  snprintf(key, sizeof(key), "%u", imap_edata_get(e)->uid);
×
390
  return hcache_store_email(mdata->hcache, key, mutt_str_len(key), e, mdata->uidvalidity);
×
391
}
392

393
/**
394
 * imap_hcache_del - Delete an item from the header cache
395
 * @param mdata Imap Mailbox data
396
 * @param uid   UID of entry to delete
397
 * @retval  0 Success
398
 * @retval -1 Failure
399
 */
400
int imap_hcache_del(struct ImapMboxData *mdata, unsigned int uid)
×
401
{
402
  if (!mdata->hcache)
×
403
    return -1;
404

405
  char key[16] = { 0 };
×
406

407
  snprintf(key, sizeof(key), "%u", uid);
408
  return hcache_delete_email(mdata->hcache, key, mutt_str_len(key));
×
409
}
410

411
/**
412
 * imap_hcache_store_uid_seqset - Store a UID Sequence Set in the header cache
413
 * @param mdata Imap Mailbox data
414
 * @retval  0 Success
415
 * @retval -1 Error
416
 */
417
int imap_hcache_store_uid_seqset(struct ImapMboxData *mdata)
×
418
{
419
  if (!mdata->hcache)
×
420
    return -1;
421

422
  struct Buffer *buf = buf_pool_get();
×
423
  buf_alloc(buf, 8192); // The seqset is likely large.  Preallocate to reduce reallocs
×
424
  imap_msn_index_to_uid_seqset(buf, mdata);
×
425

426
  int rc = hcache_store_raw(mdata->hcache, "UIDSEQSET", 9, buf->data, buf_len(buf) + 1);
×
427
  mutt_debug(LL_DEBUG3, "Stored UIDSEQSET %s\n", buf_string(buf));
×
428
  buf_pool_release(&buf);
×
429
  return rc;
×
430
}
431

432
/**
433
 * imap_hcache_clear_uid_seqset - Delete a UID Sequence Set from the header cache
434
 * @param mdata Imap Mailbox data
435
 * @retval  0 Success
436
 * @retval -1 Error
437
 */
438
int imap_hcache_clear_uid_seqset(struct ImapMboxData *mdata)
×
439
{
440
  if (!mdata->hcache)
×
441
    return -1;
442

443
  return hcache_delete_raw(mdata->hcache, "UIDSEQSET", 9);
×
444
}
445

446
/**
447
 * imap_hcache_get_uid_seqset - Get a UID Sequence Set from the header cache
448
 * @param mdata Imap Mailbox data
449
 * @retval ptr  UID Sequence Set
450
 * @retval NULL Error
451
 */
452
char *imap_hcache_get_uid_seqset(struct ImapMboxData *mdata)
×
453
{
454
  if (!mdata->hcache)
×
455
    return NULL;
456

457
  char *seqset = hcache_fetch_raw_str(mdata->hcache, "UIDSEQSET", 9);
×
458
  mutt_debug(LL_DEBUG3, "Retrieved UIDSEQSET %s\n", NONULL(seqset));
×
459

460
  return seqset;
×
461
}
462
#endif
463

464
/**
465
 * imap_parse_path - Parse an IMAP mailbox name into ConnAccount, name
466
 * @param path       Mailbox path to parse
467
 * @param cac        Account credentials
468
 * @param mailbox    Buffer for mailbox name
469
 * @param mailboxlen Length of buffer
470
 * @retval  0 Success
471
 * @retval -1 Failure
472
 *
473
 * Given an IMAP mailbox name, return host, port and a path IMAP servers will
474
 * recognize.
475
 */
476
int imap_parse_path(const char *path, struct ConnAccount *cac, char *mailbox, size_t mailboxlen)
×
477
{
478
  static unsigned short ImapPort = 0;
479
  static unsigned short ImapsPort = 0;
480

481
  if (ImapPort == 0)
×
482
  {
483
    struct servent *service = getservbyname("imap", "tcp");
×
484
    if (service)
×
485
      ImapPort = ntohs(service->s_port);
×
486
    else
487
      ImapPort = IMAP_PORT;
×
488
    mutt_debug(LL_DEBUG3, "Using default IMAP port %d\n", ImapPort);
×
489
  }
490

491
  if (ImapsPort == 0)
×
492
  {
493
    struct servent *service = getservbyname("imaps", "tcp");
×
494
    if (service)
×
495
      ImapsPort = ntohs(service->s_port);
×
496
    else
497
      ImapsPort = IMAP_SSL_PORT;
×
498
    mutt_debug(LL_DEBUG3, "Using default IMAPS port %d\n", ImapsPort);
×
499
  }
500

501
  /* Defaults */
502
  cac->port = ImapPort;
×
503
  cac->type = MUTT_ACCT_TYPE_IMAP;
×
504
  cac->service = "imap";
×
505
  cac->get_field = imap_get_field;
×
506

507
  struct Url *url = url_parse(path);
×
508
  if (!url)
×
509
    return -1;
510

511
  if ((url->scheme != U_IMAP) && (url->scheme != U_IMAPS))
×
512
  {
513
    url_free(&url);
×
514
    return -1;
×
515
  }
516

517
  if ((account_from_url(cac, url) < 0) || (cac->host[0] == '\0'))
×
518
  {
519
    url_free(&url);
×
520
    return -1;
×
521
  }
522

523
  if (url->scheme == U_IMAPS)
×
524
    cac->flags |= MUTT_ACCT_SSL;
×
525

526
  mutt_str_copy(mailbox, url->path, mailboxlen);
×
527

528
  url_free(&url);
×
529

530
  if ((cac->flags & MUTT_ACCT_SSL) && !(cac->flags & MUTT_ACCT_PORT))
×
531
    cac->port = ImapsPort;
×
532

533
  return 0;
534
}
535

536
/**
537
 * imap_mxcmp - Compare mailbox names, giving priority to INBOX
538
 * @param mx1 First mailbox name
539
 * @param mx2 Second mailbox name
540
 * @retval <0 First mailbox precedes Second mailbox
541
 * @retval  0 Mailboxes are the same
542
 * @retval >0 Second mailbox precedes First mailbox
543
 *
544
 * Like a normal sort function except that "INBOX" will be sorted to the
545
 * beginning of the list.
546
 */
547
int imap_mxcmp(const char *mx1, const char *mx2)
×
548
{
549
  char *b1 = NULL;
×
550
  char *b2 = NULL;
×
551
  int rc;
552

553
  if (!mx1 || (*mx1 == '\0'))
×
554
    mx1 = "INBOX";
555
  if (!mx2 || (*mx2 == '\0'))
×
556
    mx2 = "INBOX";
557
  if (mutt_istr_equal(mx1, "INBOX") && mutt_istr_equal(mx2, "INBOX"))
×
558
  {
559
    return 0;
560
  }
561

562
  b1 = MUTT_MEM_MALLOC(strlen(mx1) + 1, char);
×
563
  b2 = MUTT_MEM_MALLOC(strlen(mx2) + 1, char);
×
564

565
  imap_fix_path(mx1, b1, strlen(mx1) + 1);
×
566
  imap_fix_path(mx2, b2, strlen(mx2) + 1);
×
567

568
  rc = mutt_str_cmp(b1, b2);
×
569
  FREE(&b1);
×
570
  FREE(&b2);
×
571

572
  return rc;
×
573
}
574

575
/**
576
 * imap_pretty_mailbox - Prettify an IMAP mailbox name
577
 * @param path    Mailbox name to be tidied
578
 * @param pathlen Length of path
579
 * @param folder  Path to use for '+' abbreviations
580
 *
581
 * Called by mutt_pretty_mailbox() to make IMAP paths look nice.
582
 */
583
void imap_pretty_mailbox(char *path, size_t pathlen, const char *folder)
×
584
{
585
  struct ConnAccount cac_target = { { 0 } };
×
586
  struct ConnAccount cac_home = { { 0 } };
×
587
  struct Url url = { 0 };
×
588
  const char *delim = NULL;
589
  int tlen;
590
  int hlen = 0;
591
  bool home_match = false;
592
  char target_mailbox[1024] = { 0 };
×
593
  char home_mailbox[1024] = { 0 };
×
594

595
  if (imap_parse_path(path, &cac_target, target_mailbox, sizeof(target_mailbox)) < 0)
×
596
    return;
×
597

598
  if (imap_path_probe(folder, NULL) != MUTT_IMAP)
×
599
    goto fallback;
×
600

601
  if (imap_parse_path(folder, &cac_home, home_mailbox, sizeof(home_mailbox)) < 0)
×
602
    goto fallback;
×
603

604
  tlen = mutt_str_len(target_mailbox);
×
605
  hlen = mutt_str_len(home_mailbox);
×
606

607
  /* check whether we can do '+' substitution */
608
  if (tlen && imap_account_match(&cac_home, &cac_target) &&
×
609
      mutt_strn_equal(home_mailbox, target_mailbox, hlen))
×
610
  {
611
    const char *const c_imap_delim_chars = cs_subset_string(NeoMutt->sub, "imap_delim_chars");
×
612
    if (hlen == 0)
×
613
    {
614
      home_match = true;
615
    }
616
    else if (c_imap_delim_chars)
×
617
    {
618
      for (delim = c_imap_delim_chars; *delim != '\0'; delim++)
×
619
        if (target_mailbox[hlen] == *delim)
×
620
          home_match = true;
621
    }
622
  }
623

624
  /* do the '+' substitution */
625
  if (home_match)
×
626
  {
627
    *path++ = '+';
×
628
    /* copy remaining path, skipping delimiter */
629
    if (hlen != 0)
×
630
      hlen++;
×
631
    memcpy(path, target_mailbox + hlen, tlen - hlen);
×
632
    path[tlen - hlen] = '\0';
×
633
    return;
×
634
  }
635

636
fallback:
×
637
  account_to_url(&cac_target, &url);
×
638
  url.path = target_mailbox;
×
639
  url_tostring(&url, path, pathlen, U_NO_FLAGS);
×
640
}
641

642
/**
643
 * imap_continue - Display a message and ask the user if they want to go on
644
 * @param msg  Location of the error
645
 * @param resp Message for user
646
 * @retval #QuadOption Result, e.g. #MUTT_NO
647
 */
648
enum QuadOption imap_continue(const char *msg, const char *resp)
×
649
{
650
  imap_error(msg, resp);
×
651
  return query_yesorno(_("Continue?"), MUTT_NO);
×
652
}
653

654
/**
655
 * imap_error - Show an error and abort
656
 * @param where Location of the error
657
 * @param msg   Message for user
658
 */
659
void imap_error(const char *where, const char *msg)
×
660
{
661
  mutt_error("%s [%s]", where, msg);
×
662
}
×
663

664
/**
665
 * imap_fix_path - Fix up the imap path
666
 * @param mailbox   Mailbox path
667
 * @param path      Buffer for the result
668
 * @param plen      Length of buffer
669
 * @retval ptr      Fixed-up path
670
 *
671
 * @note the first character in mailbox matching any of the characters in
672
 * `$imap_delim_chars` is used as a delimiter.
673
 *
674
 * This is necessary because the rest of neomutt assumes a hierarchy delimiter of
675
 * '/', which is not necessarily true in IMAP.  Additionally, the filesystem
676
 * converts multiple hierarchy delimiters into a single one, ie "///" is equal
677
 * to "/".  IMAP servers are not required to do this.
678
 * Moreover, IMAP servers may dislike the path ending with the delimiter.
679
 */
680
char *imap_fix_path(const char *mailbox, char *path, size_t plen)
×
681
{
682
  const char *const c_imap_delim_chars = cs_subset_string(NeoMutt->sub, "imap_delim_chars");
×
683

684
  char *out = path;
685
  size_t space_left = plen - 1;
×
686

687
  if (mailbox)
×
688
  {
689
    for (const char *c = mailbox; *c && space_left; c++, space_left--)
×
690
    {
691
      if (strchr(NONULL(c_imap_delim_chars), *c))
×
692
      {
693
        return imap_fix_path_with_delim(*c, mailbox, path, plen);
×
694
      }
695
      *out++ = *c;
×
696
    }
697
  }
698

699
  *out = '\0';
×
700
  return path;
×
701
}
702

703
/**
704
 * imap_fix_path_with_delim - Fix up the imap path
705
 * @param delim     Delimiter specified by the server
706
 * @param mailbox   Mailbox path
707
 * @param path      Buffer for the result
708
 * @param plen      Length of buffer
709
 * @retval ptr      Fixed-up path
710
 *
711
 */
712
char *imap_fix_path_with_delim(const char delim, const char *mailbox, char *path, size_t plen)
×
713
{
714
  char *out = path;
715
  size_t space_left = plen - 1;
×
716

717
  if (mailbox)
×
718
  {
719
    for (const char *c = mailbox; *c && space_left; c++, space_left--)
×
720
    {
721
      if (*c == delim || *c == '/')
×
722
      {
723
        while (*c && *(c + 1) == *c)
×
724
          c++;
×
725
        *out++ = delim;
×
726
      }
727
      else
728
      {
729
        *out++ = *c;
×
730
      }
731
    }
732
  }
733

734
  if (out != path && *(out - 1) == delim)
×
735
  {
736
    out--;
×
737
  }
738
  *out = '\0';
×
739
  return path;
×
740
}
741

742
/**
743
 * imap_cachepath - Generate a cache path for a mailbox
744
 * @param delim   Imap server delimiter
745
 * @param mailbox Mailbox name
746
 * @param dest    Buffer to store cache path
747
 */
748
void imap_cachepath(char delim, const char *mailbox, struct Buffer *dest)
×
749
{
750
  const char *p = mailbox;
751
  buf_reset(dest);
×
752
  if (!p)
×
753
    return;
754

755
  while (*p)
×
756
  {
757
    if (p[0] == delim)
×
758
    {
759
      buf_addch(dest, '/');
×
760
      /* simple way to avoid collisions with UIDs */
761
      if ((p[1] >= '0') && (p[1] <= '9'))
×
762
        buf_addch(dest, '_');
×
763
    }
764
    else
765
    {
766
      buf_addch(dest, *p);
×
767
    }
768
    p++;
×
769
  }
770
}
771

772
/**
773
 * imap_get_literal_count - Write number of bytes in an IMAP literal into bytes
774
 * @param[in]  buf   Number as a string
775
 * @param[out] bytes Resulting number
776
 * @retval  0 Success
777
 * @retval -1 Failure
778
 */
779
int imap_get_literal_count(const char *buf, unsigned int *bytes)
×
780
{
781
  char *pc = NULL;
782
  char *pn = NULL;
783

784
  if (!buf || !(pc = strchr(buf, '{')))
×
785
    return -1;
786

787
  pc++;
×
788
  pn = pc;
789
  while (mutt_isdigit(*pc))
×
790
    pc++;
×
791
  *pc = '\0';
×
792
  if (!mutt_str_atoui(pn, bytes))
×
793
    return -1;
794

795
  return 0;
796
}
797

798
/**
799
 * imap_get_qualifier - Get the qualifier from a tagged response
800
 * @param buf Command string to process
801
 * @retval ptr Start of the qualifier
802
 *
803
 * In a tagged response, skip tag and status for the qualifier message.
804
 * Used by imap_copy_message for TRYCREATE
805
 */
806
char *imap_get_qualifier(char *buf)
×
807
{
808
  char *s = buf;
809

810
  /* skip tag */
811
  s = imap_next_word(s);
×
812
  /* skip OK/NO/BAD response */
813
  s = imap_next_word(s);
×
814

815
  return s;
×
816
}
817

818
/**
819
 * imap_next_word - Find where the next IMAP word begins
820
 * @param s Command string to process
821
 * @retval ptr Next IMAP word
822
 */
823
char *imap_next_word(char *s)
×
824
{
825
  bool quoted = false;
826

827
  while (*s)
×
828
  {
829
    if (*s == '\\')
×
830
    {
831
      s++;
×
832
      if (*s)
×
833
        s++;
×
834
      continue;
×
835
    }
836
    if (*s == '\"')
×
837
      quoted = !quoted;
×
838
    if (!quoted && mutt_isspace(*s))
×
839
      break;
840
    s++;
×
841
  }
842

843
  SKIPWS(s);
×
844
  return s;
×
845
}
846

847
/**
848
 * imap_qualify_path - Make an absolute IMAP folder target
849
 * @param buf    Buffer for the result
850
 * @param buflen Length of buffer
851
 * @param cac    ConnAccount of the account
852
 * @param path   Path relative to the mailbox
853
 */
854
void imap_qualify_path(char *buf, size_t buflen, struct ConnAccount *cac, char *path)
×
855
{
856
  struct Url url = { 0 };
×
857
  account_to_url(cac, &url);
×
858
  url.path = path;
×
859
  url_tostring(&url, buf, buflen, U_NO_FLAGS);
×
860
}
×
861

862
/**
863
 * imap_buf_qualify_path - Make an absolute IMAP folder target to a buffer
864
 * @param buf  Buffer for the result
865
 * @param cac  ConnAccount of the account
866
 * @param path Path relative to the mailbox
867
 */
868
void imap_buf_qualify_path(struct Buffer *buf, struct ConnAccount *cac, char *path)
×
869
{
870
  struct Url url = { 0 };
×
871
  account_to_url(cac, &url);
×
872
  url.path = path;
×
873
  url_tobuffer(&url, buf, U_NO_FLAGS);
×
874
}
×
875

876
/**
877
 * imap_quote_string - Quote string according to IMAP rules
878
 * @param dest           Buffer for the result
879
 * @param dlen           Length of the buffer
880
 * @param src            String to be quoted
881
 * @param quote_backtick If true, quote backticks too
882
 *
883
 * Surround string with quotes, escape " and \ with backslash
884
 */
885
void imap_quote_string(char *dest, size_t dlen, const char *src, bool quote_backtick)
×
886
{
887
  const char *quote = "`\"\\";
888
  if (!quote_backtick)
×
889
    quote++;
890

891
  char *pt = dest;
892
  const char *s = src;
893

894
  *pt++ = '"';
×
895
  /* save room for quote-chars */
896
  dlen -= 3;
×
897

898
  for (; *s && dlen; s++)
×
899
  {
900
    if (strchr(quote, *s))
×
901
    {
902
      if (dlen < 2)
×
903
        break;
904
      dlen -= 2;
×
905
      *pt++ = '\\';
×
906
      *pt++ = *s;
×
907
    }
908
    else
909
    {
910
      *pt++ = *s;
×
911
      dlen--;
×
912
    }
913
  }
914
  *pt++ = '"';
×
915
  *pt = '\0';
×
916
}
×
917

918
/**
919
 * imap_unquote_string - Equally stupid unquoting routine
920
 * @param s String to be unquoted
921
 */
922
void imap_unquote_string(char *s)
×
923
{
924
  char *d = s;
925

926
  if (*s == '\"')
×
927
    s++;
×
928
  else
929
    return;
930

931
  while (*s)
×
932
  {
933
    if (*s == '\"')
×
934
    {
935
      *d = '\0';
×
936
      return;
×
937
    }
938
    if (*s == '\\')
×
939
    {
940
      s++;
×
941
    }
942
    if (*s)
×
943
    {
944
      *d = *s;
×
945
      d++;
×
946
      s++;
×
947
    }
948
  }
949
  *d = '\0';
×
950
}
951

952
/**
953
 * imap_munge_mbox_name - Quote awkward characters in a mailbox name
954
 * @param unicode true if Unicode is allowed
955
 * @param dest    Buffer to store safe mailbox name
956
 * @param dlen    Length of buffer
957
 * @param src     Mailbox name
958
 */
959
void imap_munge_mbox_name(bool unicode, char *dest, size_t dlen, const char *src)
×
960
{
961
  char *buf = mutt_str_dup(src);
×
962
  imap_utf_encode(unicode, &buf);
×
963

964
  imap_quote_string(dest, dlen, buf, false);
×
965

966
  FREE(&buf);
×
967
}
×
968

969
/**
970
 * imap_unmunge_mbox_name - Remove quoting from a mailbox name
971
 * @param unicode true if Unicode is allowed
972
 * @param s       Mailbox name
973
 *
974
 * The string will be altered in-place.
975
 */
976
void imap_unmunge_mbox_name(bool unicode, char *s)
×
977
{
978
  imap_unquote_string(s);
×
979

980
  char *buf = mutt_str_dup(s);
×
981
  if (buf)
×
982
  {
983
    imap_utf_decode(unicode, &buf);
×
984
    strncpy(s, buf, strlen(s));
×
985
  }
986

987
  FREE(&buf);
×
988
}
×
989

990
/**
991
 * imap_keep_alive - Poll the current folder to keep the connection alive
992
 */
993
void imap_keep_alive(void)
×
994
{
995
  time_t now = mutt_date_now();
×
996
  struct Account *np = NULL;
997
  const short c_imap_keep_alive = cs_subset_number(NeoMutt->sub, "imap_keep_alive");
×
998
  TAILQ_FOREACH(np, &NeoMutt->accounts, entries)
×
999
  {
1000
    if (np->type != MUTT_IMAP)
×
1001
      continue;
×
1002

1003
    struct ImapAccountData *adata = np->adata;
×
1004
    if (!adata || !adata->mailbox)
×
1005
      continue;
×
1006

1007
    if ((adata->state >= IMAP_AUTHENTICATED) && (now >= (adata->lastread + c_imap_keep_alive)))
×
1008
      imap_check_mailbox(adata->mailbox, true);
×
1009
  }
1010
}
×
1011

1012
/**
1013
 * imap_wait_keep_alive - Wait for a process to change state
1014
 * @param pid Process ID to listen to
1015
 * @retval num 'wstatus' from waitpid()
1016
 */
1017
int imap_wait_keep_alive(pid_t pid)
×
1018
{
1019
  struct sigaction oldalrm = { 0 };
×
1020
  struct sigaction act = { 0 };
×
1021
  sigset_t oldmask = { 0 };
×
1022
  int rc;
1023

1024
  const bool c_imap_passive = cs_subset_bool(NeoMutt->sub, "imap_passive");
×
1025
  cs_subset_str_native_set(NeoMutt->sub, "imap_passive", true, NULL);
×
1026
  OptKeepQuiet = true;
×
1027

1028
  sigprocmask(SIG_SETMASK, NULL, &oldmask);
×
1029

1030
  sigemptyset(&act.sa_mask);
×
1031
  act.sa_handler = mutt_sig_empty_handler;
×
1032
#ifdef SA_INTERRUPT
1033
  act.sa_flags = SA_INTERRUPT;
×
1034
#else
1035
  act.sa_flags = 0;
1036
#endif
1037

1038
  sigaction(SIGALRM, &act, &oldalrm);
×
1039

1040
  const short c_imap_keep_alive = cs_subset_number(NeoMutt->sub, "imap_keep_alive");
×
1041
  alarm(c_imap_keep_alive);
×
1042
  while ((waitpid(pid, &rc, 0) < 0) && (errno == EINTR))
×
1043
  {
1044
    alarm(0); /* cancel a possibly pending alarm */
×
1045
    imap_keep_alive();
×
1046
    alarm(c_imap_keep_alive);
×
1047
  }
1048

1049
  alarm(0); /* cancel a possibly pending alarm */
×
1050

1051
  sigaction(SIGALRM, &oldalrm, NULL);
×
1052
  sigprocmask(SIG_SETMASK, &oldmask, NULL);
×
1053

1054
  OptKeepQuiet = false;
×
1055
  cs_subset_str_native_set(NeoMutt->sub, "imap_passive", c_imap_passive, NULL);
×
1056

1057
  return rc;
×
1058
}
1059

1060
/**
1061
 * imap_allow_reopen - Allow re-opening a folder upon expunge
1062
 * @param m Mailbox
1063
 */
1064
void imap_allow_reopen(struct Mailbox *m)
×
1065
{
1066
  struct ImapAccountData *adata = imap_adata_get(m);
×
1067
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1068
  if (!adata || !adata->mailbox || (adata->mailbox != m) || !mdata)
×
1069
    return;
1070
  mdata->reopen |= IMAP_REOPEN_ALLOW;
×
1071
}
1072

1073
/**
1074
 * imap_disallow_reopen - Disallow re-opening a folder upon expunge
1075
 * @param m Mailbox
1076
 */
1077
void imap_disallow_reopen(struct Mailbox *m)
×
1078
{
1079
  struct ImapAccountData *adata = imap_adata_get(m);
×
1080
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1081
  if (!adata || !adata->mailbox || (adata->mailbox != m) || !mdata)
×
1082
    return;
1083
  mdata->reopen &= ~IMAP_REOPEN_ALLOW;
×
1084
}
1085

1086
/**
1087
 * imap_account_match - Compare two Accounts
1088
 * @param a1 First ConnAccount
1089
 * @param a2 Second ConnAccount
1090
 * @retval true Accounts match
1091
 */
1092
bool imap_account_match(const struct ConnAccount *a1, const struct ConnAccount *a2)
×
1093
{
1094
  if (!a1 || !a2)
×
1095
    return false;
1096
  if (a1->type != a2->type)
×
1097
    return false;
1098
  if (!mutt_istr_equal(a1->host, a2->host))
×
1099
    return false;
1100
  if ((a1->port != 0) && (a2->port != 0) && (a1->port != a2->port))
×
1101
    return false;
1102
  if (a1->flags & a2->flags & MUTT_ACCT_USER)
×
1103
    return mutt_str_equal(a1->user, a2->user);
×
1104

1105
  const char *user = NONULL(NeoMutt->username);
×
1106

1107
  const char *const c_imap_user = cs_subset_string(NeoMutt->sub, "imap_user");
×
1108
  if ((a1->type == MUTT_ACCT_TYPE_IMAP) && c_imap_user)
×
1109
    user = c_imap_user;
1110

1111
  if (a1->flags & MUTT_ACCT_USER)
×
1112
    return mutt_str_equal(a1->user, user);
×
1113
  if (a2->flags & MUTT_ACCT_USER)
×
1114
    return mutt_str_equal(a2->user, user);
×
1115

1116
  return true;
1117
}
1118

1119
/**
1120
 * mutt_seqset_iterator_new - Create a new Sequence Set Iterator
1121
 * @param seqset Source Sequence Set
1122
 * @retval ptr Newly allocated Sequence Set Iterator
1123
 */
1124
struct SeqsetIterator *mutt_seqset_iterator_new(const char *seqset)
×
1125
{
1126
  if (!seqset || (*seqset == '\0'))
×
1127
    return NULL;
1128

1129
  struct SeqsetIterator *iter = MUTT_MEM_CALLOC(1, struct SeqsetIterator);
×
1130
  iter->full_seqset = mutt_str_dup(seqset);
×
1131
  iter->eostr = strchr(iter->full_seqset, '\0');
×
1132
  iter->substr_cur = iter->substr_end = iter->full_seqset;
×
1133

1134
  return iter;
×
1135
}
1136

1137
/**
1138
 * mutt_seqset_iterator_next - Get the next UID from a Sequence Set
1139
 * @param[in]  iter Sequence Set Iterator
1140
 * @param[out] next Next UID in set
1141
 * @retval  0 Next sequence is generated
1142
 * @retval  1 Iterator is finished
1143
 * @retval -1 error
1144
 */
1145
int mutt_seqset_iterator_next(struct SeqsetIterator *iter, unsigned int *next)
×
1146
{
1147
  if (!iter || !next)
×
1148
    return -1;
1149

1150
  if (iter->in_range)
×
1151
  {
1152
    if ((iter->down && (iter->range_cur == (iter->range_end - 1))) ||
×
1153
        (!iter->down && (iter->range_cur == (iter->range_end + 1))))
×
1154
    {
1155
      iter->in_range = 0;
×
1156
    }
1157
  }
1158

1159
  if (!iter->in_range)
×
1160
  {
1161
    iter->substr_cur = iter->substr_end;
×
1162
    if (iter->substr_cur == iter->eostr)
×
1163
      return 1;
1164

1165
    iter->substr_end = strchr(iter->substr_cur, ',');
×
1166
    if (!iter->substr_end)
×
1167
      iter->substr_end = iter->eostr;
×
1168
    else
1169
      *(iter->substr_end++) = '\0';
×
1170

1171
    char *range_sep = strchr(iter->substr_cur, ':');
×
1172
    if (range_sep)
×
1173
      *range_sep++ = '\0';
×
1174

1175
    if (!mutt_str_atoui_full(iter->substr_cur, &iter->range_cur))
×
1176
      return -1;
1177
    if (range_sep)
×
1178
    {
1179
      if (!mutt_str_atoui_full(range_sep, &iter->range_end))
×
1180
        return -1;
1181
    }
1182
    else
1183
    {
1184
      iter->range_end = iter->range_cur;
×
1185
    }
1186

1187
    iter->down = (iter->range_end < iter->range_cur);
×
1188
    iter->in_range = 1;
×
1189
  }
1190

1191
  *next = iter->range_cur;
×
1192
  if (iter->down)
×
1193
    iter->range_cur--;
×
1194
  else
1195
    iter->range_cur++;
×
1196

1197
  return 0;
1198
}
1199

1200
/**
1201
 * mutt_seqset_iterator_free - Free a Sequence Set Iterator
1202
 * @param[out] ptr Iterator to free
1203
 */
1204
void mutt_seqset_iterator_free(struct SeqsetIterator **ptr)
×
1205
{
1206
  if (!ptr || !*ptr)
×
1207
    return;
1208

1209
  struct SeqsetIterator *iter = *ptr;
1210
  FREE(&iter->full_seqset);
×
1211
  FREE(ptr);
×
1212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc