• 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/message.c
1
/**
2
 * @file
3
 * Manage IMAP messages
4
 *
5
 * @authors
6
 * Copyright (C) 1996-1999 Brandon Long <blong@fiction.net>
7
 * Copyright (C) 1999-2009 Brendan Cully <brendan@kublai.com>
8
 * Copyright (C) 2017-2023 Richard Russon <rich@flatcap.org>
9
 * Copyright (C) 2018 Mehdi Abaakouk <sileht@sileht.net>
10
 * Copyright (C) 2018-2023 Pietro Cerutti <gahr@gahr.ch>
11
 * Copyright (C) 2019 Ian Zimmerman <itz@no-use.mooo.com>
12
 * Copyright (C) 2021 Ihor Antonov <ihor@antonovs.family>
13
 * Copyright (C) 2024 Dennis Schön <mail@dennis-schoen.de>
14
 *
15
 * @copyright
16
 * This program is free software: you can redistribute it and/or modify it under
17
 * the terms of the GNU General Public License as published by the Free Software
18
 * Foundation, either version 2 of the License, or (at your option) any later
19
 * version.
20
 *
21
 * This program is distributed in the hope that it will be useful, but WITHOUT
22
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
23
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
24
 * details.
25
 *
26
 * You should have received a copy of the GNU General Public License along with
27
 * this program.  If not, see <http://www.gnu.org/licenses/>.
28
 */
29

30
/**
31
 * @page imap_message IMAP messages
32
 *
33
 * Manage IMAP messages
34
 */
35

36
#include "config.h"
37
#include <limits.h>
38
#include <stdbool.h>
39
#include <stdint.h>
40
#include <stdio.h>
41
#include <string.h>
42
#include <unistd.h>
43
#include "private.h"
44
#include "mutt/lib.h"
45
#include "config/lib.h"
46
#include "email/lib.h"
47
#include "core/lib.h"
48
#include "conn/lib.h"
49
#include "gui/lib.h"
50
#include "mutt.h"
51
#include "message.h"
52
#include "lib.h"
53
#include "bcache/lib.h"
54
#include "progress/lib.h"
55
#include "question/lib.h"
56
#include "adata.h"
57
#include "edata.h"
58
#include "external.h"
59
#include "mdata.h"
60
#include "msg_set.h"
61
#include "msn.h"
62
#include "mutt_logging.h"
63
#include "mx.h"
64
#include "protos.h"
65
#ifdef ENABLE_NLS
66
#include <libintl.h>
67
#endif
68
#ifdef USE_HCACHE
69
#include "hcache/lib.h"
70
#endif
71

72
struct BodyCache;
73

74
/**
75
 * imap_bcache_open - Open a message cache
76
 * @param m     Selected Imap Mailbox
77
 * @retval ptr  Success, using existing cache (or opened new cache)
78
 * @retval NULL Failure
79
 */
80
static struct BodyCache *imap_bcache_open(struct Mailbox *m)
×
81
{
82
  struct ImapAccountData *adata = imap_adata_get(m);
×
83
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
84

85
  if (!adata || (adata->mailbox != m))
×
86
    return NULL;
87

88
  if (mdata->bcache)
×
89
    return mdata->bcache;
90

91
  struct Buffer *mailbox = buf_pool_get();
×
92
  imap_cachepath(adata->delim, mdata->name, mailbox);
×
93

94
  struct BodyCache *bc = mutt_bcache_open(&adata->conn->account, buf_string(mailbox));
×
95
  buf_pool_release(&mailbox);
×
96

97
  return bc;
×
98
}
99

100
/**
101
 * msg_cache_get - Get the message cache entry for an email
102
 * @param m     Selected Imap Mailbox
103
 * @param e     Email
104
 * @retval ptr  Success, handle of cache entry
105
 * @retval NULL Failure
106
 */
107
static FILE *msg_cache_get(struct Mailbox *m, struct Email *e)
×
108
{
109
  struct ImapAccountData *adata = imap_adata_get(m);
×
110
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
111

112
  if (!e || !adata || (adata->mailbox != m))
×
113
    return NULL;
114

115
  mdata->bcache = imap_bcache_open(m);
×
116
  char id[64] = { 0 };
×
117
  snprintf(id, sizeof(id), "%u-%u", mdata->uidvalidity, imap_edata_get(e)->uid);
×
118
  return mutt_bcache_get(mdata->bcache, id);
×
119
}
120

121
/**
122
 * msg_cache_put - Put an email into the message cache
123
 * @param m     Selected Imap Mailbox
124
 * @param e     Email
125
 * @retval ptr  Success, handle of cache entry
126
 * @retval NULL Failure
127
 */
128
static FILE *msg_cache_put(struct Mailbox *m, struct Email *e)
×
129
{
130
  struct ImapAccountData *adata = imap_adata_get(m);
×
131
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
132

133
  if (!e || !adata || (adata->mailbox != m))
×
134
    return NULL;
135

136
  mdata->bcache = imap_bcache_open(m);
×
137
  char id[64] = { 0 };
×
138
  snprintf(id, sizeof(id), "%u-%u", mdata->uidvalidity, imap_edata_get(e)->uid);
×
139
  return mutt_bcache_put(mdata->bcache, id);
×
140
}
141

142
/**
143
 * msg_cache_commit - Add to the message cache
144
 * @param m     Selected Imap Mailbox
145
 * @param e     Email
146
 * @retval  0 Success
147
 * @retval -1 Failure
148
 */
149
static int msg_cache_commit(struct Mailbox *m, struct Email *e)
×
150
{
151
  struct ImapAccountData *adata = imap_adata_get(m);
×
152
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
153

154
  if (!e || !adata || (adata->mailbox != m))
×
155
    return -1;
156

157
  mdata->bcache = imap_bcache_open(m);
×
158
  char id[64] = { 0 };
×
159
  snprintf(id, sizeof(id), "%u-%u", mdata->uidvalidity, imap_edata_get(e)->uid);
×
160

161
  return mutt_bcache_commit(mdata->bcache, id);
×
162
}
163

164
/**
165
 * imap_bcache_delete - Delete an entry from the message cache - Implements ::bcache_list_t - @ingroup bcache_list_api
166
 * @retval 0 Always
167
 */
168
static int imap_bcache_delete(const char *id, struct BodyCache *bcache, void *data)
×
169
{
170
  uint32_t uv = 0;
×
171
  unsigned int uid = 0;
×
172
  struct ImapMboxData *mdata = data;
173

174
  if (sscanf(id, "%u-%u", &uv, &uid) != 2)
×
175
    return 0;
176

177
  /* bad UID */
178
  if ((uv != mdata->uidvalidity) || !mutt_hash_int_find(mdata->uid_hash, uid))
×
179
    mutt_bcache_del(bcache, id);
×
180

181
  return 0;
182
}
183

184
/**
185
 * msg_parse_flags - Read a FLAGS token into an ImapHeader
186
 * @param h Header to store flags
187
 * @param s Command string containing flags
188
 * @retval ptr  The end of flags string
189
 * @retval NULL Failure
190
 */
191
static char *msg_parse_flags(struct ImapHeader *h, char *s)
×
192
{
193
  struct ImapEmailData *edata = h->edata;
×
194

195
  /* sanity-check string */
196
  size_t plen = mutt_istr_startswith(s, "FLAGS");
×
197
  if (plen == 0)
×
198
  {
199
    mutt_debug(LL_DEBUG1, "not a FLAGS response: %s\n", s);
×
200
    return NULL;
×
201
  }
202
  s += plen;
×
203
  SKIPWS(s);
×
204
  if (*s != '(')
×
205
  {
206
    mutt_debug(LL_DEBUG1, "bogus FLAGS response: %s\n", s);
×
207
    return NULL;
×
208
  }
209
  s++;
×
210

211
  FREE(&edata->flags_system);
×
212
  FREE(&edata->flags_remote);
×
213

214
  edata->deleted = false;
×
215
  edata->flagged = false;
×
216
  edata->replied = false;
×
217
  edata->read = false;
×
218
  edata->old = false;
×
219

220
  /* start parsing */
221
  while (*s && (*s != ')'))
×
222
  {
223
    if ((plen = mutt_istr_startswith(s, "\\deleted")))
×
224
    {
225
      s += plen;
×
226
      edata->deleted = true;
×
227
    }
228
    else if ((plen = mutt_istr_startswith(s, "\\flagged")))
×
229
    {
230
      s += plen;
×
231
      edata->flagged = true;
×
232
    }
233
    else if ((plen = mutt_istr_startswith(s, "\\answered")))
×
234
    {
235
      s += plen;
×
236
      edata->replied = true;
×
237
    }
238
    else if ((plen = mutt_istr_startswith(s, "\\seen")))
×
239
    {
240
      s += plen;
×
241
      edata->read = true;
×
242
    }
243
    else if ((plen = mutt_istr_startswith(s, "\\recent")))
×
244
    {
245
      s += plen;
×
246
    }
247
    else if ((plen = mutt_istr_startswith(s, "old")))
×
248
    {
249
      s += plen;
×
250
      edata->old = cs_subset_bool(NeoMutt->sub, "mark_old");
×
251
    }
252
    else
253
    {
254
      char ctmp;
255
      char *flag_word = s;
256
      bool is_system_keyword = mutt_istr_startswith(s, "\\");
×
257

258
      while (*s && !mutt_isspace(*s) && (*s != ')'))
×
259
        s++;
×
260

261
      ctmp = *s;
×
262
      *s = '\0';
×
263

264
      struct Buffer *buf = buf_pool_get();
×
265
      if (is_system_keyword)
×
266
      {
267
        /* store other system flags as well (mainly \\Draft) */
268
        buf_addstr(buf, edata->flags_system);
×
269
        buf_join_str(buf, flag_word, ' ');
×
270
        edata->flags_system = buf_strdup(buf);
×
271
      }
272
      else
273
      {
274
        /* store custom flags as well */
275
        buf_addstr(buf, edata->flags_remote);
×
276
        buf_join_str(buf, flag_word, ' ');
×
277
        edata->flags_remote = buf_strdup(buf);
×
278
      }
279
      buf_pool_release(&buf);
×
280

281
      *s = ctmp;
×
282
    }
283
    SKIPWS(s);
×
284
  }
285

286
  /* wrap up, or note bad flags response */
287
  if (*s == ')')
×
288
  {
289
    s++;
×
290
  }
291
  else
292
  {
293
    mutt_debug(LL_DEBUG1, "Unterminated FLAGS response: %s\n", s);
×
294
    return NULL;
×
295
  }
296

297
  return s;
×
298
}
299

300
/**
301
 * msg_parse_fetch - Handle headers returned from header fetch
302
 * @param h IMAP Header
303
 * @param s Command string
304
 * @retval  0 Success
305
 * @retval -1 String is corrupted
306
 * @retval -2 Fetch contains a body or header lines that still need to be parsed
307
 */
308
static int msg_parse_fetch(struct ImapHeader *h, char *s)
×
309
{
310
  if (!s)
×
311
    return -1;
312

313
  char tmp[128] = { 0 };
×
314
  char *ptmp = NULL;
315
  size_t plen = 0;
316

317
  while (*s)
×
318
  {
319
    SKIPWS(s);
×
320

321
    if (mutt_istr_startswith(s, "FLAGS"))
×
322
    {
323
      s = msg_parse_flags(h, s);
×
324
      if (!s)
×
325
        return -1;
326
    }
327
    else if ((plen = mutt_istr_startswith(s, "UID")))
×
328
    {
329
      s += plen;
×
330
      SKIPWS(s);
×
331
      if (!mutt_str_atoui(s, &h->edata->uid))
×
332
        return -1;
333

334
      s = imap_next_word(s);
×
335
    }
336
    else if ((plen = mutt_istr_startswith(s, "INTERNALDATE")))
×
337
    {
338
      s += plen;
×
339
      SKIPWS(s);
×
340
      if (*s != '\"')
×
341
      {
342
        mutt_debug(LL_DEBUG1, "bogus INTERNALDATE entry: %s\n", s);
×
343
        return -1;
×
344
      }
345
      s++;
×
346
      ptmp = tmp;
347
      while (*s && (*s != '\"') && (ptmp != (tmp + sizeof(tmp) - 1)))
×
348
        *ptmp++ = *s++;
×
349
      if (*s != '\"')
×
350
        return -1;
351
      s++; /* skip past the trailing " */
×
352
      *ptmp = '\0';
×
353
      h->received = mutt_date_parse_imap(tmp);
×
354
    }
355
    else if ((plen = mutt_istr_startswith(s, "RFC822.SIZE")))
×
356
    {
357
      s += plen;
×
358
      SKIPWS(s);
×
359
      ptmp = tmp;
360
      while (mutt_isdigit(*s) && (ptmp != (tmp + sizeof(tmp) - 1)))
×
361
        *ptmp++ = *s++;
×
362
      *ptmp = '\0';
×
363
      if (!mutt_str_atol(tmp, &h->content_length))
×
364
        return -1;
365
    }
366
    else if (mutt_istr_startswith(s, "BODY") || mutt_istr_startswith(s, "RFC822.HEADER"))
×
367
    {
368
      /* handle above, in msg_fetch_header */
369
      return -2;
×
370
    }
371
    else if ((plen = mutt_istr_startswith(s, "MODSEQ")))
×
372
    {
373
      s += plen;
×
374
      SKIPWS(s);
×
375
      if (*s != '(')
×
376
      {
377
        mutt_debug(LL_DEBUG1, "bogus MODSEQ response: %s\n", s);
×
378
        return -1;
×
379
      }
380
      s++;
×
381
      while (*s && (*s != ')'))
×
382
        s++;
×
383
      if (*s == ')')
×
384
      {
385
        s++;
×
386
      }
387
      else
388
      {
389
        mutt_debug(LL_DEBUG1, "Unterminated MODSEQ response: %s\n", s);
×
390
        return -1;
×
391
      }
392
    }
393
    else if (*s == ')')
×
394
    {
395
      s++; /* end of request */
×
396
    }
397
    else if (*s)
×
398
    {
399
      /* got something i don't understand */
400
      imap_error("msg_parse_fetch", s);
×
401
      return -1;
×
402
    }
403
  }
404

405
  return 0;
406
}
407

408
/**
409
 * msg_fetch_header - Import IMAP FETCH response into an ImapHeader
410
 * @param m   Mailbox
411
 * @param ih  ImapHeader
412
 * @param buf Server string containing FETCH response
413
 * @param fp  Connection to server
414
 * @retval  0 Success
415
 * @retval -1 String is not a fetch response
416
 * @retval -2 String is a corrupt fetch response
417
 *
418
 * Expects string beginning with * n FETCH.
419
 */
420
static int msg_fetch_header(struct Mailbox *m, struct ImapHeader *ih, char *buf, FILE *fp)
×
421
{
422
  int rc = -1; /* default now is that string isn't FETCH response */
423

424
  struct ImapAccountData *adata = imap_adata_get(m);
×
425

426
  if (buf[0] != '*')
×
427
    return rc;
428

429
  /* skip to message number */
430
  buf = imap_next_word(buf);
×
431
  if (!mutt_str_atoui(buf, &ih->edata->msn))
×
432
    return rc;
433

434
  /* find FETCH tag */
435
  buf = imap_next_word(buf);
×
436
  if (!mutt_istr_startswith(buf, "FETCH"))
×
437
    return rc;
438

439
  rc = -2; /* we've got a FETCH response, for better or worse */
440
  buf = strchr(buf, '(');
×
441
  if (!buf)
×
442
    return rc;
443
  buf++;
×
444

445
  /* FIXME: current implementation - call msg_parse_fetch - if it returns -2,
446
   *   read header lines and call it again. Silly. */
447
  int parse_rc = msg_parse_fetch(ih, buf);
×
448
  if (parse_rc == 0)
×
449
    return 0;
450
  if ((parse_rc != -2) || !fp)
×
451
    return rc;
452

453
  unsigned int bytes = 0;
×
454
  if (imap_get_literal_count(buf, &bytes) == 0)
×
455
  {
456
    imap_read_literal(fp, adata, bytes, NULL);
×
457

458
    /* we may have other fields of the FETCH _after_ the literal
459
     * (eg Domino puts FLAGS here). Nothing wrong with that, either.
460
     * This all has to go - we should accept literals and nonliterals
461
     * interchangeably at any time. */
462
    if (imap_cmd_step(adata) != IMAP_RES_CONTINUE)
×
463
      return rc;
464

465
    if (msg_parse_fetch(ih, adata->buf) == -1)
×
466
      return rc;
467
  }
468

469
  rc = 0; /* success */
470

471
  /* subtract headers from message size - unfortunately only the subset of
472
   * headers we've requested. */
473
  ih->content_length -= bytes;
×
474

475
  return rc;
×
476
}
477

478
/**
479
 * flush_buffer - Write data to a connection
480
 * @param buf  Buffer containing data
481
 * @param len  Length of buffer
482
 * @param conn Network connection
483
 * @retval >0 Number of bytes written
484
 * @retval -1 Error
485
 */
486
static int flush_buffer(char *buf, size_t *len, struct Connection *conn)
×
487
{
488
  buf[*len] = '\0';
×
489
  int rc = mutt_socket_write_n(conn, buf, *len);
×
490
  *len = 0;
×
491
  return rc;
×
492
}
493

494
/**
495
 * query_abort_header_download - Ask the user whether to abort the download
496
 * @param adata Imap Account data
497
 * @retval true Abort the download
498
 *
499
 * If the user hits ctrl-c during an initial header download for a mailbox,
500
 * prompt whether to completely abort the download and close the mailbox.
501
 */
502
static bool query_abort_header_download(struct ImapAccountData *adata)
×
503
{
504
  bool abort = false;
505

506
  mutt_flushinp();
×
507
  /* L10N: This prompt is made if the user hits Ctrl-C when opening an IMAP mailbox */
508
  if (query_yesorno(_("Abort download and close mailbox?"), MUTT_YES) == MUTT_YES)
×
509
  {
510
    abort = true;
511
    imap_close_connection(adata);
×
512
  }
513
  SigInt = false;
×
514

515
  return abort;
×
516
}
517

518
/**
519
 * imap_alloc_uid_hash - Create a Hash Table for the UIDs
520
 * @param adata Imap Account data
521
 * @param msn_count Number of MSNs in use
522
 *
523
 * This function is run after imap_imap_msn_reserve, so we skip the
524
 * malicious msn_count size check.
525
 */
526
static void imap_alloc_uid_hash(struct ImapAccountData *adata, unsigned int msn_count)
×
527
{
528
  struct ImapMboxData *mdata = adata->mailbox->mdata;
×
529
  if (!mdata->uid_hash)
×
530
    mdata->uid_hash = mutt_hash_int_new(MAX(6 * msn_count / 5, 30), MUTT_HASH_NO_FLAGS);
×
531
}
×
532

533
/**
534
 * imap_fetch_msn_seqset - Generate a sequence set
535
 * @param[in]  buf           Buffer for the result
536
 * @param[in]  adata         Imap Account data
537
 * @param[in]  evalhc        If true, check the Header Cache
538
 * @param[in]  msn_begin     First Message Sequence Number
539
 * @param[in]  msn_end       Last Message Sequence Number
540
 * @param[out] fetch_msn_end Highest Message Sequence Number fetched
541
 * @retval num MSN count
542
 *
543
 * Generates a more complicated sequence set after using the header cache,
544
 * in case there are missing MSNs in the middle.
545
 *
546
 * This can happen if during a sync/close, messages are deleted from
547
 * the cache, but the server doesn't get the updates (via a dropped
548
 * network connection, or just plain refusing the updates).
549
 */
550
static unsigned int imap_fetch_msn_seqset(struct Buffer *buf, struct ImapAccountData *adata,
×
551
                                          bool evalhc, unsigned int msn_begin,
552
                                          unsigned int msn_end, unsigned int *fetch_msn_end)
553
{
554
  struct ImapMboxData *mdata = adata->mailbox->mdata;
×
555
  unsigned int max_headers_per_fetch = UINT_MAX;
556
  bool first_chunk = true;
557
  int state = 0; /* 1: single msn, 2: range of msn */
558
  unsigned int msn;
559
  unsigned int range_begin = 0;
560
  unsigned int range_end = 0;
561
  unsigned int msn_count = 0;
562

563
  buf_reset(buf);
×
564
  if (msn_end < msn_begin)
×
565
    return 0;
566

567
  const long c_imap_fetch_chunk_size = cs_subset_long(NeoMutt->sub, "imap_fetch_chunk_size");
×
568
  if (c_imap_fetch_chunk_size > 0)
×
569
    max_headers_per_fetch = c_imap_fetch_chunk_size;
570

571
  if (!evalhc)
×
572
  {
573
    if ((msn_end - msn_begin + 1) <= max_headers_per_fetch)
×
574
      *fetch_msn_end = msn_end;
×
575
    else
576
      *fetch_msn_end = msn_begin + max_headers_per_fetch - 1;
×
577
    buf_printf(buf, "%u:%u", msn_begin, *fetch_msn_end);
×
578
    return (*fetch_msn_end - msn_begin + 1);
×
579
  }
580

581
  for (msn = msn_begin; msn <= (msn_end + 1); msn++)
×
582
  {
583
    if ((msn_count < max_headers_per_fetch) && (msn <= msn_end) &&
×
584
        !imap_msn_get(&mdata->msn, msn - 1))
×
585
    {
586
      msn_count++;
×
587

588
      switch (state)
×
589
      {
590
        case 1: /* single: convert to a range */
591
          state = 2;
592
          FALLTHROUGH;
593

594
        case 2: /* extend range ending */
×
595
          range_end = msn;
596
          break;
×
597
        default:
598
          state = 1;
599
          range_begin = msn;
600
          break;
601
      }
602
    }
603
    else if (state)
×
604
    {
605
      if (first_chunk)
×
606
        first_chunk = false;
607
      else
608
        buf_addch(buf, ',');
×
609

610
      if (state == 1)
×
611
        buf_add_printf(buf, "%u", range_begin);
×
612
      else if (state == 2)
613
        buf_add_printf(buf, "%u:%u", range_begin, range_end);
×
614
      state = 0;
615

616
      if ((buf_len(buf) > 500) || (msn_count >= max_headers_per_fetch))
×
617
        break;
618
    }
619
  }
620

621
  /* The loop index goes one past to terminate the range if needed. */
622
  *fetch_msn_end = msn - 1;
×
623

624
  return msn_count;
×
625
}
626

627
/**
628
 * set_changed_flag - Have the flags of an email changed
629
 * @param[in]  m              Mailbox
630
 * @param[in]  e              Email
631
 * @param[in]  local_changes  Has the local mailbox been changed?
632
 * @param[out] server_changes Set to true if the flag has changed
633
 * @param[in]  flag_name      Flag to check, e.g. #MUTT_FLAG
634
 * @param[in]  old_hd_flag    Old header flags
635
 * @param[in]  new_hd_flag    New header flags
636
 * @param[in]  h_flag         Email's value for flag_name
637
 *
638
 * Sets server_changes to 1 if a change to a flag is made, or in the
639
 * case of local_changes, if a change to a flag _would_ have been
640
 * made.
641
 */
642
static void set_changed_flag(struct Mailbox *m, struct Email *e, int local_changes,
×
643
                             bool *server_changes, enum MessageType flag_name,
644
                             bool old_hd_flag, bool new_hd_flag, bool h_flag)
645
{
646
  /* If there are local_changes, we only want to note if the server
647
   * flags have changed, so we can set a reopen flag in
648
   * cmd_parse_fetch().  We don't want to count a local modification
649
   * to the header flag as a "change".  */
650
  if ((old_hd_flag == new_hd_flag) && (local_changes != 0))
×
651
    return;
652

653
  if (new_hd_flag == h_flag)
×
654
    return;
655

656
  if (server_changes)
×
657
    *server_changes = true;
×
658

659
  /* Local changes have priority */
660
  if (local_changes == 0)
×
661
    mutt_set_flag(m, e, flag_name, new_hd_flag, true);
×
662
}
663

664
#ifdef USE_HCACHE
665
/**
666
 * read_headers_normal_eval_cache - Retrieve data from the header cache
667
 * @param adata              Imap Account data
668
 * @param msn_end            Last Message Sequence number
669
 * @param uid_next           UID of next email
670
 * @param store_flag_updates if true, save flags to the header cache
671
 * @param eval_condstore     if true, use CONDSTORE to fetch flags
672
 * @retval  0 Success
673
 * @retval -1 Error
674
 *
675
 * Without CONDSTORE or QRESYNC, we need to query all the current
676
 * UIDs and update their flag state and current MSN.
677
 *
678
 * For CONDSTORE, we still need to grab the existing UIDs and
679
 * their MSN.  The current flag state will be queried in
680
 * read_headers_condstore_qresync_updates().
681
 */
682
static int read_headers_normal_eval_cache(struct ImapAccountData *adata,
×
683
                                          unsigned int msn_end, unsigned int uid_next,
684
                                          bool store_flag_updates, bool eval_condstore)
685
{
686
  struct Progress *progress = NULL;
×
687
  char buf[1024] = { 0 };
×
688
  int rc = -1;
689

690
  struct Mailbox *m = adata->mailbox;
×
691
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
692
  int idx = m->msg_count;
×
693

694
  if (m->verbose)
×
695
  {
696
    /* L10N: Comparing the cached data with the IMAP server's data */
697
    progress = progress_new(MUTT_PROGRESS_READ, msn_end);
×
698
    progress_set_message(progress, _("Evaluating cache..."));
×
699
  }
700

701
  /* If we are using CONDSTORE's "FETCH CHANGEDSINCE", then we keep
702
   * the flags in the header cache, and update them further below.
703
   * Otherwise, we fetch the current state of the flags here. */
704
  snprintf(buf, sizeof(buf), "UID FETCH 1:%u (UID%s)", uid_next - 1,
×
705
           eval_condstore ? "" : " FLAGS");
706

707
  imap_cmd_start(adata, buf);
×
708

709
  rc = IMAP_RES_CONTINUE;
710
  int mfhrc = 0;
711
  struct ImapHeader h = { 0 };
×
712
  for (int msgno = 1; rc == IMAP_RES_CONTINUE; msgno++)
×
713
  {
714
    if (SigInt && query_abort_header_download(adata))
×
715
      goto fail;
×
716

717
    progress_update(progress, msgno, -1);
×
718

719
    memset(&h, 0, sizeof(h));
720
    h.edata = imap_edata_new();
×
721
    do
722
    {
723
      rc = imap_cmd_step(adata);
×
724
      if (rc != IMAP_RES_CONTINUE)
×
725
        break;
726

727
      mfhrc = msg_fetch_header(m, &h, adata->buf, NULL);
×
728
      if (mfhrc < 0)
×
729
        continue;
×
730

731
      if (!h.edata->uid)
×
732
      {
733
        mutt_debug(LL_DEBUG2, "skipping hcache FETCH response for message number %d missing a UID\n",
×
734
                   h.edata->msn);
735
        continue;
×
736
      }
737

738
      if ((h.edata->msn < 1) || (h.edata->msn > msn_end))
×
739
      {
740
        mutt_debug(LL_DEBUG1, "skipping hcache FETCH response for unknown message number %d\n",
×
741
                   h.edata->msn);
742
        continue;
×
743
      }
744

745
      if (imap_msn_get(&mdata->msn, h.edata->msn - 1))
×
746
      {
747
        mutt_debug(LL_DEBUG2, "skipping hcache FETCH for duplicate message %d\n",
×
748
                   h.edata->msn);
749
        continue;
×
750
      }
751

752
      struct Email *e = imap_hcache_get(mdata, h.edata->uid);
×
753
      m->emails[idx] = e;
×
754
      if (e)
×
755
      {
756
        imap_msn_set(&mdata->msn, h.edata->msn - 1, e);
×
757
        mutt_hash_int_insert(mdata->uid_hash, h.edata->uid, e);
×
758

759
        e->index = h.edata->uid;
×
760
        /* messages which have not been expunged are ACTIVE (borrowed from mh
761
         * folders) */
762
        e->active = true;
×
763
        e->changed = false;
×
764
        if (eval_condstore)
×
765
        {
766
          h.edata->read = e->read;
×
767
          h.edata->old = e->old;
×
768
          h.edata->deleted = e->deleted;
×
769
          h.edata->flagged = e->flagged;
×
770
          h.edata->replied = e->replied;
×
771
        }
772
        else
773
        {
774
          e->read = h.edata->read;
×
775
          e->old = h.edata->old;
×
776
          e->deleted = h.edata->deleted;
×
777
          e->flagged = h.edata->flagged;
×
778
          e->replied = h.edata->replied;
×
779
        }
780

781
        /*  mailbox->emails[msgno]->received is restored from hcache_fetch_email() */
782
        e->edata = h.edata;
×
783
        e->edata_free = imap_edata_free;
×
784

785
        /* We take a copy of the tags so we can split the string */
786
        char *tags_copy = mutt_str_dup(h.edata->flags_remote);
×
787
        driver_tags_replace(&e->tags, tags_copy);
×
788
        FREE(&tags_copy);
×
789

790
        m->msg_count++;
×
791
        mailbox_size_add(m, e);
×
792

793
        /* If this is the first time we are fetching, we need to
794
         * store the current state of flags back into the header cache */
795
        if (!eval_condstore && store_flag_updates)
×
796
          imap_hcache_put(mdata, e);
×
797

798
        h.edata = NULL;
×
799
        idx++;
×
800
      }
801
    } while (mfhrc == -1);
×
802

803
    imap_edata_free((void **) &h.edata);
×
804

805
    if ((mfhrc < -1) || ((rc != IMAP_RES_CONTINUE) && (rc != IMAP_RES_OK)))
×
806
      goto fail;
×
807
  }
808

809
  rc = 0;
810
fail:
×
811
  progress_free(&progress);
×
812
  return rc;
×
813
}
814

815
/**
816
 * read_headers_qresync_eval_cache - Retrieve data from the header cache
817
 * @param adata Imap Account data
818
 * @param uid_seqset Sequence Set of UIDs
819
 * @retval >=0 Success
820
 * @retval  -1 Error
821
 *
822
 * For QRESYNC, we grab the UIDs in order by MSN from the header cache.
823
 *
824
 * In read_headers_condstore_qresync_updates().  We will update change flags
825
 * using CHANGEDSINCE and find out what UIDs have been expunged using VANISHED.
826
 */
827
static int read_headers_qresync_eval_cache(struct ImapAccountData *adata, char *uid_seqset)
×
828
{
829
  int rc;
830
  unsigned int uid = 0;
×
831

832
  mutt_debug(LL_DEBUG2, "Reading uid seqset from header cache\n");
×
833
  struct Mailbox *m = adata->mailbox;
×
834
  struct ImapMboxData *mdata = adata->mailbox->mdata;
×
835
  unsigned int msn = 1;
836

837
  if (m->verbose)
×
838
    mutt_message(_("Evaluating cache..."));
×
839

840
  struct SeqsetIterator *iter = mutt_seqset_iterator_new(uid_seqset);
×
841
  if (!iter)
×
842
    return -1;
843

844
  while ((rc = mutt_seqset_iterator_next(iter, &uid)) == 0)
×
845
  {
846
    /* The seqset may contain more headers than the fetch request, so
847
     * we need to watch and reallocate the context and msn_index */
848
    imap_msn_reserve(&mdata->msn, msn);
×
849

850
    struct Email *e = imap_hcache_get(mdata, uid);
×
851
    if (e)
×
852
    {
853
      imap_msn_set(&mdata->msn, msn - 1, e);
×
854

855
      mx_alloc_memory(m, m->msg_count);
×
856

857
      struct ImapEmailData *edata = imap_edata_new();
×
858
      e->edata = edata;
×
859
      e->edata_free = imap_edata_free;
×
860

861
      e->index = uid;
×
862
      e->active = true;
×
863
      e->changed = false;
×
864
      edata->read = e->read;
×
865
      edata->old = e->old;
×
866
      edata->deleted = e->deleted;
×
867
      edata->flagged = e->flagged;
×
868
      edata->replied = e->replied;
×
869

870
      edata->msn = msn;
×
871
      edata->uid = uid;
×
872
      mutt_hash_int_insert(mdata->uid_hash, uid, e);
×
873

874
      mailbox_size_add(m, e);
×
875
      m->emails[m->msg_count++] = e;
×
876

877
      msn++;
×
878
    }
879
    else if (!uid)
×
880
    {
881
      /* A non-zero uid missing from the header cache is either the
882
       * result of an expunged message (not recorded in the uid seqset)
883
       * or a hole in the header cache.
884
       *
885
       * We have to assume it's an earlier expunge and compact the msn's
886
       * in that case, because cmd_parse_vanished() won't find it in the
887
       * uid_hash and decrement later msn's there.
888
       *
889
       * Thus we only increment the uid if the uid was 0: an actual
890
       * stored "blank" in the uid seqset.
891
       */
892
      msn++;
×
893
    }
894
  }
895

896
  mutt_seqset_iterator_free(&iter);
×
897

898
  return rc;
×
899
}
900

901
/**
902
 * read_headers_condstore_qresync_updates - Retrieve updates from the server
903
 * @param adata        Imap Account data
904
 * @param msn_end      Last Message Sequence number
905
 * @param uid_next     UID of next email
906
 * @param hc_modseq    Timestamp of last Header Cache update
907
 * @param eval_qresync If true, use QRESYNC
908
 * @retval  0 Success
909
 * @retval -1 Error
910
 *
911
 * CONDSTORE and QRESYNC use FETCH extensions to grab updates.
912
 */
913
static int read_headers_condstore_qresync_updates(struct ImapAccountData *adata,
×
914
                                                  unsigned int msn_end, unsigned int uid_next,
915
                                                  unsigned long long hc_modseq, bool eval_qresync)
916
{
917
  struct Progress *progress = NULL;
×
918
  char buf[1024] = { 0 };
×
919
  unsigned int header_msn = 0;
×
920

921
  struct Mailbox *m = adata->mailbox;
×
922
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
923

924
  if (m->verbose)
×
925
  {
926
    /* L10N: Fetching IMAP flag changes, using the CONDSTORE extension */
927
    progress = progress_new(MUTT_PROGRESS_READ, msn_end);
×
928
    progress_set_message(progress, _("Fetching flag updates..."));
×
929
  }
930

931
  snprintf(buf, sizeof(buf), "UID FETCH 1:%u (FLAGS) (CHANGEDSINCE %llu%s)",
×
932
           uid_next - 1, hc_modseq, eval_qresync ? " VANISHED" : "");
933

934
  imap_cmd_start(adata, buf);
×
935

936
  int rc = IMAP_RES_CONTINUE;
937
  for (int msgno = 1; rc == IMAP_RES_CONTINUE; msgno++)
×
938
  {
939
    if (SigInt && query_abort_header_download(adata))
×
940
      goto fail;
×
941

942
    progress_update(progress, msgno, -1);
×
943

944
    /* cmd_parse_fetch will update the flags */
945
    rc = imap_cmd_step(adata);
×
946
    if (rc != IMAP_RES_CONTINUE)
×
947
      break;
948

949
    /* so we just need to grab the header and persist it back into
950
     * the header cache */
951
    char *fetch_buf = adata->buf;
×
952
    if (fetch_buf[0] != '*')
×
953
      continue;
×
954

955
    fetch_buf = imap_next_word(fetch_buf);
×
956
    if (!mutt_isdigit(*fetch_buf) || !mutt_str_atoui(fetch_buf, &header_msn))
×
957
      continue;
×
958

959
    if ((header_msn < 1) || (header_msn > msn_end) ||
×
960
        !imap_msn_get(&mdata->msn, header_msn - 1))
×
961
    {
962
      mutt_debug(LL_DEBUG1, "skipping CONDSTORE flag update for unknown message number %u\n",
×
963
                 header_msn);
964
      continue;
×
965
    }
966

967
    imap_hcache_put(mdata, imap_msn_get(&mdata->msn, header_msn - 1));
×
968
  }
969

970
  if (rc != IMAP_RES_OK)
×
971
    goto fail;
×
972

973
  /* The IMAP flag setting as part of cmd_parse_fetch() ends up
974
   * flipping these on. */
975
  mdata->check_status &= ~IMAP_FLAGS_PENDING;
×
976
  m->changed = false;
×
977

978
  /* VANISHED handling: we need to empty out the messages */
979
  if (mdata->reopen & IMAP_EXPUNGE_PENDING)
×
980
  {
981
    imap_hcache_close(mdata);
×
982
    imap_expunge_mailbox(m, false);
×
983

984
    imap_hcache_open(adata, mdata, false);
×
985
    mdata->reopen &= ~IMAP_EXPUNGE_PENDING;
×
986
  }
987

988
  /* undo expunge count updates.
989
   * mview_update() will do this at the end of the header fetch. */
990
  m->vcount = 0;
×
991
  m->msg_tagged = 0;
×
992
  m->msg_deleted = 0;
×
993
  m->msg_new = 0;
×
994
  m->msg_unread = 0;
×
995
  m->msg_flagged = 0;
×
996
  m->changed = false;
×
997

998
  rc = 0;
999
fail:
×
1000
  progress_free(&progress);
×
1001
  return rc;
×
1002
}
1003

1004
/**
1005
 * imap_verify_qresync - Check to see if QRESYNC got jumbled
1006
 * @param m  Imap Selected Mailbox
1007
 * @retval  0 Success
1008
 * @retval -1 Error
1009
 *
1010
 * If so, wipe the context and try again with a normal download.
1011
 */
1012
static int imap_verify_qresync(struct Mailbox *m)
×
1013
{
1014
  ASSERT(m);
×
1015
  struct ImapAccountData *adata = imap_adata_get(m);
×
1016
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1017
  if (!adata || (adata->mailbox != m))
×
1018
    return -1;
1019

1020
  const size_t max_msn = imap_msn_highest(&mdata->msn);
×
1021

1022
  unsigned int msn;
1023
  unsigned int uid;
1024
  struct Email *e = NULL;
1025
  struct Email *uidh = NULL;
1026

1027
  for (int i = 0; i < m->msg_count; i++)
×
1028
  {
1029
    e = m->emails[i];
×
1030
    const struct ImapEmailData *edata = imap_edata_get(e);
×
1031
    if (!edata)
×
1032
      goto fail;
×
1033

1034
    msn = imap_edata_get(e)->msn;
×
1035
    uid = imap_edata_get(e)->uid;
×
1036

1037
    if ((msn < 1) || (msn > max_msn) || imap_msn_get(&mdata->msn, msn - 1) != e)
×
1038
      goto fail;
×
1039

1040
    uidh = (struct Email *) mutt_hash_int_find(mdata->uid_hash, uid);
×
1041
    if (uidh != e)
×
1042
      goto fail;
×
1043
  }
1044

1045
  return 0;
1046

1047
fail:
×
1048
  imap_msn_free(&mdata->msn);
×
1049
  mutt_hash_free(&mdata->uid_hash);
×
1050
  mutt_hash_free(&m->subj_hash);
×
1051
  mutt_hash_free(&m->id_hash);
×
1052
  mutt_hash_free(&m->label_hash);
×
1053

1054
  for (int i = 0; i < m->msg_count; i++)
×
1055
  {
1056
    if (m->emails[i] && m->emails[i]->edata)
×
1057
      imap_edata_free(&m->emails[i]->edata);
×
1058
    email_free(&m->emails[i]);
×
1059
  }
1060
  m->msg_count = 0;
×
1061
  m->size = 0;
×
1062
  hcache_delete_raw(mdata->hcache, "MODSEQ", 6);
×
1063
  imap_hcache_clear_uid_seqset(mdata);
×
1064
  imap_hcache_close(mdata);
×
1065

1066
  if (m->verbose)
×
1067
  {
1068
    /* L10N: After opening an IMAP mailbox using QRESYNC, Mutt performs a quick
1069
       sanity check.  If that fails, Mutt reopens the mailbox using a normal
1070
       download.  */
1071
    mutt_error(_("QRESYNC failed.  Reopening mailbox."));
×
1072
  }
1073
  return -1;
1074
}
1075

1076
#endif /* USE_HCACHE */
1077

1078
/**
1079
 * read_headers_fetch_new - Retrieve new messages from the server
1080
 * @param[in]  m                Imap Selected Mailbox
1081
 * @param[in]  msn_begin        First Message Sequence number
1082
 * @param[in]  msn_end          Last Message Sequence number
1083
 * @param[in]  evalhc           If true, check the Header Cache
1084
 * @param[out] maxuid           Highest UID seen
1085
 * @param[in]  initial_download true, if this is the first opening of the mailbox
1086
 * @retval  0 Success
1087
 * @retval -1 Error
1088
 */
1089
static int read_headers_fetch_new(struct Mailbox *m, unsigned int msn_begin,
×
1090
                                  unsigned int msn_end, bool evalhc,
1091
                                  unsigned int *maxuid, bool initial_download)
1092
{
1093
  int rc = -1;
1094
  unsigned int fetch_msn_end = 0;
×
1095
  struct Progress *progress = NULL;
×
1096
  char *hdrreq = NULL;
×
1097
  struct Buffer *tempfile = NULL;
×
1098
  FILE *fp = NULL;
×
1099
  struct ImapHeader h = { 0 };
×
1100
  struct Buffer *buf = NULL;
×
1101
  static const char *const want_headers = "DATE FROM SENDER SUBJECT TO CC MESSAGE-ID REFERENCES "
1102
                                          "CONTENT-TYPE CONTENT-DESCRIPTION IN-REPLY-TO REPLY-TO "
1103
                                          "LINES LIST-POST LIST-SUBSCRIBE LIST-UNSUBSCRIBE X-LABEL "
1104
                                          "X-ORIGINAL-TO";
1105

1106
  struct ImapAccountData *adata = imap_adata_get(m);
×
1107
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1108
  struct ImapEmailData *edata = NULL;
×
1109

1110
  if (!adata || (adata->mailbox != m))
×
1111
    return -1;
1112

1113
  struct Buffer *hdr_list = buf_pool_get();
×
1114
  buf_strcpy(hdr_list, want_headers);
×
1115
  const char *const c_imap_headers = cs_subset_string(NeoMutt->sub, "imap_headers");
×
1116
  if (c_imap_headers)
×
1117
  {
1118
    buf_addch(hdr_list, ' ');
×
1119
    buf_addstr(hdr_list, c_imap_headers);
×
1120
  }
1121
#ifdef USE_AUTOCRYPT
1122
  const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
×
1123
  if (c_autocrypt)
×
1124
  {
1125
    buf_addch(hdr_list, ' ');
×
1126
    buf_addstr(hdr_list, "AUTOCRYPT");
×
1127
  }
1128
#endif
1129

1130
  if (adata->capabilities & IMAP_CAP_IMAP4REV1)
×
1131
  {
1132
    mutt_str_asprintf(&hdrreq, "BODY.PEEK[HEADER.FIELDS (%s)]", buf_string(hdr_list));
×
1133
  }
1134
  else if (adata->capabilities & IMAP_CAP_IMAP4)
×
1135
  {
1136
    mutt_str_asprintf(&hdrreq, "RFC822.HEADER.LINES (%s)", buf_string(hdr_list));
×
1137
  }
1138
  else
1139
  { /* Unable to fetch headers for lower versions */
1140
    mutt_error(_("Unable to fetch headers from this IMAP server version"));
×
1141
    goto bail;
×
1142
  }
1143

1144
  buf_pool_release(&hdr_list);
×
1145

1146
  /* instead of downloading all headers and then parsing them, we parse them
1147
   * as they come in. */
1148
  tempfile = buf_pool_get();
×
1149
  buf_mktemp(tempfile);
×
1150
  fp = mutt_file_fopen(buf_string(tempfile), "w+");
×
1151
  if (!fp)
×
1152
  {
1153
    mutt_error(_("Could not create temporary file %s"), buf_string(tempfile));
×
1154
    goto bail;
×
1155
  }
1156
  unlink(buf_string(tempfile));
×
1157
  buf_pool_release(&tempfile);
×
1158

1159
  if (m->verbose)
×
1160
  {
1161
    progress = progress_new(MUTT_PROGRESS_READ, msn_end);
×
1162
    progress_set_message(progress, _("Fetching message headers..."));
×
1163
  }
1164

1165
  buf = buf_pool_get();
×
1166

1167
  /* NOTE:
1168
   *   The (fetch_msn_end < msn_end) used to be important to prevent
1169
   *   an infinite loop, in the event the server did not return all
1170
   *   the headers (due to a pending expunge, for example).
1171
   *
1172
   *   I believe the new chunking imap_fetch_msn_seqset()
1173
   *   implementation and "msn_begin = fetch_msn_end + 1" assignment
1174
   *   at the end of the loop makes the comparison unneeded, but to be
1175
   *   cautious I'm keeping it.
1176
   */
1177
  edata = imap_edata_new();
×
1178
  while ((fetch_msn_end < msn_end) &&
×
1179
         imap_fetch_msn_seqset(buf, adata, evalhc, msn_begin, msn_end, &fetch_msn_end))
×
1180
  {
1181
    char *cmd = NULL;
×
1182
    mutt_str_asprintf(&cmd, "FETCH %s (UID FLAGS INTERNALDATE RFC822.SIZE %s)",
×
1183
                      buf_string(buf), hdrreq);
1184
    imap_cmd_start(adata, cmd);
×
1185
    FREE(&cmd);
×
1186

1187
    int msgno = msn_begin;
×
1188

1189
    while (true)
1190
    {
1191
      rewind(fp);
×
1192
      memset(&h, 0, sizeof(h));
1193
      h.edata = edata;
×
1194

1195
      if (initial_download && SigInt && query_abort_header_download(adata))
×
1196
      {
1197
        goto bail;
×
1198
      }
1199

1200
      const int rc2 = imap_cmd_step(adata);
×
1201
      if (rc2 != IMAP_RES_CONTINUE)
×
1202
      {
1203
        if (rc2 != IMAP_RES_OK)
×
1204
        {
1205
          goto bail;
×
1206
        }
1207
        break;
×
1208
      }
1209

1210
      switch (msg_fetch_header(m, &h, adata->buf, fp))
×
1211
      {
1212
        case 0:
1213
          break;
1214
        case -1:
×
1215
          continue;
×
1216
        case -2:
×
1217
          goto bail;
×
1218
      }
1219

1220
      if (!ftello(fp))
×
1221
      {
1222
        mutt_debug(LL_DEBUG2, "ignoring fetch response with no body\n");
×
1223
        continue;
×
1224
      }
1225

1226
      /* make sure we don't get remnants from older larger message headers */
1227
      fputs("\n\n", fp);
×
1228

1229
      if ((h.edata->msn < 1) || (h.edata->msn > fetch_msn_end))
×
1230
      {
1231
        mutt_debug(LL_DEBUG1, "skipping FETCH response for unknown message number %d\n",
×
1232
                   h.edata->msn);
1233
        continue;
×
1234
      }
1235

1236
      /* May receive FLAGS updates in a separate untagged response */
1237
      if (imap_msn_get(&mdata->msn, h.edata->msn - 1))
×
1238
      {
1239
        mutt_debug(LL_DEBUG2, "skipping FETCH response for duplicate message %d\n",
×
1240
                   h.edata->msn);
1241
        continue;
×
1242
      }
1243

1244
      progress_update(progress, msgno++, -1);
×
1245

1246
      struct Email *e = email_new();
×
1247
      mx_alloc_memory(m, m->msg_count);
×
1248

1249
      m->emails[m->msg_count++] = e;
×
1250

1251
      imap_msn_set(&mdata->msn, h.edata->msn - 1, e);
×
1252
      mutt_hash_int_insert(mdata->uid_hash, h.edata->uid, e);
×
1253

1254
      e->index = h.edata->uid;
×
1255
      /* messages which have not been expunged are ACTIVE (borrowed from mh
1256
       * folders) */
1257
      e->active = true;
×
1258
      e->changed = false;
×
1259
      e->read = h.edata->read;
×
1260
      e->old = h.edata->old;
×
1261
      e->deleted = h.edata->deleted;
×
1262
      e->flagged = h.edata->flagged;
×
1263
      e->replied = h.edata->replied;
×
1264
      e->received = h.received;
×
1265
      e->edata = (void *) imap_edata_clone(h.edata);
×
1266
      e->edata_free = imap_edata_free;
×
1267
      STAILQ_INIT(&e->tags);
×
1268

1269
      /* We take a copy of the tags so we can split the string */
1270
      char *tags_copy = mutt_str_dup(h.edata->flags_remote);
×
1271
      driver_tags_replace(&e->tags, tags_copy);
×
1272
      FREE(&tags_copy);
×
1273

1274
      if (*maxuid < h.edata->uid)
×
1275
        *maxuid = h.edata->uid;
×
1276

1277
      rewind(fp);
×
1278
      /* NOTE: if Date: header is missing, mutt_rfc822_read_header depends
1279
       *   on h.received being set */
1280
      e->env = mutt_rfc822_read_header(fp, e, false, false);
×
1281
      /* body built as a side-effect of mutt_rfc822_read_header */
1282
      e->body->length = h.content_length;
×
1283
      mailbox_size_add(m, e);
×
1284

1285
#ifdef USE_HCACHE
1286
      imap_hcache_put(mdata, e);
×
1287
#endif /* USE_HCACHE */
1288
    }
1289

1290
    /* In case we get new mail while fetching the headers. */
1291
    if (mdata->reopen & IMAP_NEWMAIL_PENDING)
×
1292
    {
1293
      msn_end = mdata->new_mail_count;
×
1294
      mx_alloc_memory(m, msn_end);
×
1295
      imap_msn_reserve(&mdata->msn, msn_end);
×
1296
      mdata->reopen &= ~IMAP_NEWMAIL_PENDING;
×
1297
      mdata->new_mail_count = 0;
×
1298
    }
1299

1300
    /* Note: RFC3501 section 7.4.1 and RFC7162 section 3.2.10.2 say we
1301
     * must not get any EXPUNGE/VANISHED responses in the middle of a
1302
     * FETCH, nor when no command is in progress (e.g. between the
1303
     * chunked FETCH commands).  We previously tried to be robust by
1304
     * setting:
1305
     *   msn_begin = mdata->max_msn + 1;
1306
     * but with chunking and header cache holes this
1307
     * may not be correct.  So here we must assume the msn values have
1308
     * not been altered during or after the fetch.  */
1309
    msn_begin = fetch_msn_end + 1;
×
1310
  }
1311

1312
  rc = 0;
1313

1314
bail:
×
1315
  buf_pool_release(&hdr_list);
×
1316
  buf_pool_release(&buf);
×
1317
  buf_pool_release(&tempfile);
×
1318
  mutt_file_fclose(&fp);
×
1319
  FREE(&hdrreq);
×
1320
  imap_edata_free((void **) &edata);
×
1321
  progress_free(&progress);
×
1322

1323
  return rc;
×
1324
}
1325

1326
/**
1327
 * imap_read_headers - Read headers from the server
1328
 * @param m                Imap Selected Mailbox
1329
 * @param msn_begin        First Message Sequence Number
1330
 * @param msn_end          Last Message Sequence Number
1331
 * @param initial_download true, if this is the first opening of the mailbox
1332
 * @retval num Last MSN
1333
 * @retval -1  Failure
1334
 *
1335
 * Changed to read many headers instead of just one. It will return the msn of
1336
 * the last message read. It will return a value other than msn_end if mail
1337
 * comes in while downloading headers (in theory).
1338
 */
1339
int imap_read_headers(struct Mailbox *m, unsigned int msn_begin,
×
1340
                      unsigned int msn_end, bool initial_download)
1341
{
1342
  unsigned int maxuid = 0;
×
1343
  int rc = -1;
1344
  bool evalhc = false;
1345

1346
#ifdef USE_HCACHE
1347
  uint32_t uidvalidity = 0;
×
1348
  unsigned int uid_next = 0;
×
1349
  unsigned long long modseq = 0;
×
1350
  bool has_condstore = false;
1351
  bool has_qresync = false;
1352
  bool eval_condstore = false;
1353
  bool eval_qresync = false;
1354
  char *uid_seqset = NULL;
×
1355
  const unsigned int msn_begin_save = msn_begin;
1356
#endif /* USE_HCACHE */
1357

1358
  struct ImapAccountData *adata = imap_adata_get(m);
×
1359
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1360
  if (!adata || (adata->mailbox != m))
×
1361
    return -1;
1362

1363
#ifdef USE_HCACHE
1364
retry:
×
1365
#endif /* USE_HCACHE */
1366

1367
  /* make sure context has room to hold the mailbox */
1368
  mx_alloc_memory(m, msn_end);
×
1369
  imap_msn_reserve(&mdata->msn, msn_end);
×
1370
  imap_alloc_uid_hash(adata, msn_end);
×
1371

1372
  mdata->reopen &= ~(IMAP_REOPEN_ALLOW | IMAP_NEWMAIL_PENDING);
×
1373
  mdata->new_mail_count = 0;
×
1374

1375
#ifdef USE_HCACHE
1376
  imap_hcache_open(adata, mdata, true);
×
1377

1378
  if (mdata->hcache && initial_download)
×
1379
  {
1380
    hcache_fetch_raw_obj(mdata->hcache, "UIDVALIDITY", 11, &uidvalidity);
×
1381
    hcache_fetch_raw_obj(mdata->hcache, "UIDNEXT", 7, &uid_next);
×
1382
    if (mdata->modseq)
×
1383
    {
1384
      const bool c_imap_condstore = cs_subset_bool(NeoMutt->sub, "imap_condstore");
×
1385
      if ((adata->capabilities & IMAP_CAP_CONDSTORE) && c_imap_condstore)
×
1386
        has_condstore = true;
1387

1388
      /* If IMAP_CAP_QRESYNC and ImapQResync then NeoMutt sends ENABLE QRESYNC.
1389
       * If we receive an ENABLED response back, then adata->qresync is set.  */
1390
      if (adata->qresync)
×
1391
        has_qresync = true;
1392
    }
1393

1394
    if (uidvalidity && uid_next && (uidvalidity == mdata->uidvalidity))
×
1395
    {
1396
      evalhc = true;
1397
      if (hcache_fetch_raw_obj(mdata->hcache, "MODSEQ", 6, &modseq))
×
1398
      {
1399
        if (has_qresync)
×
1400
        {
1401
          uid_seqset = imap_hcache_get_uid_seqset(mdata);
×
1402
          if (uid_seqset)
×
1403
            eval_qresync = true;
1404
        }
1405

1406
        if (!eval_qresync && has_condstore)
×
1407
          eval_condstore = true;
1408
      }
1409
    }
1410
  }
1411
  if (evalhc)
×
1412
  {
1413
    if (eval_qresync)
×
1414
    {
1415
      if (read_headers_qresync_eval_cache(adata, uid_seqset) < 0)
×
1416
        goto bail;
×
1417
    }
1418
    else
1419
    {
1420
      if (read_headers_normal_eval_cache(adata, msn_end, uid_next, has_condstore || has_qresync,
×
1421
                                         eval_condstore) < 0)
1422
        goto bail;
×
1423
    }
1424

1425
    if ((eval_condstore || eval_qresync) && (modseq != mdata->modseq))
×
1426
    {
1427
      if (read_headers_condstore_qresync_updates(adata, msn_end, uid_next,
×
1428
                                                 modseq, eval_qresync) < 0)
1429
      {
1430
        goto bail;
×
1431
      }
1432
    }
1433

1434
    /* Look for the first empty MSN and start there */
1435
    while (msn_begin <= msn_end)
×
1436
    {
1437
      if (!imap_msn_get(&mdata->msn, msn_begin - 1))
×
1438
        break;
1439
      msn_begin++;
×
1440
    }
1441
  }
1442
#endif /* USE_HCACHE */
1443

1444
  if (read_headers_fetch_new(m, msn_begin, msn_end, evalhc, &maxuid, initial_download) < 0)
×
1445
    goto bail;
×
1446

1447
#ifdef USE_HCACHE
1448
  if (eval_qresync && initial_download)
×
1449
  {
1450
    if (imap_verify_qresync(m) != 0)
×
1451
    {
1452
      eval_qresync = false;
1453
      eval_condstore = false;
1454
      evalhc = false;
1455
      modseq = 0;
×
1456
      maxuid = 0;
×
1457
      FREE(&uid_seqset);
×
1458
      uidvalidity = 0;
×
1459
      uid_next = 0;
×
1460
      msn_begin = msn_begin_save;
1461

1462
      goto retry;
×
1463
    }
1464
  }
1465
#endif /* USE_HCACHE */
1466

1467
  if (maxuid && (mdata->uid_next < maxuid + 1))
×
1468
    mdata->uid_next = maxuid + 1;
×
1469

1470
#ifdef USE_HCACHE
1471
  hcache_store_raw(mdata->hcache, "UIDVALIDITY", 11, &mdata->uidvalidity,
×
1472
                   sizeof(mdata->uidvalidity));
1473
  if (maxuid && (mdata->uid_next < maxuid + 1))
×
1474
  {
1475
    mutt_debug(LL_DEBUG2, "Overriding UIDNEXT: %u -> %u\n", mdata->uid_next, maxuid + 1);
×
1476
    mdata->uid_next = maxuid + 1;
×
1477
  }
1478
  if (mdata->uid_next > 1)
×
1479
  {
1480
    hcache_store_raw(mdata->hcache, "UIDNEXT", 7, &mdata->uid_next, sizeof(mdata->uid_next));
×
1481
  }
1482

1483
  /* We currently only sync CONDSTORE and QRESYNC on the initial download.
1484
   * To do it more often, we'll need to deal with flag updates combined with
1485
   * unsync'ed local flag changes.  We'll also need to properly sync flags to
1486
   * the header cache on close.  I'm not sure it's worth the added complexity.  */
1487
  if (initial_download)
×
1488
  {
1489
    if (has_condstore || has_qresync)
×
1490
    {
1491
      hcache_store_raw(mdata->hcache, "MODSEQ", 6, &mdata->modseq, sizeof(mdata->modseq));
×
1492
    }
1493
    else
1494
    {
1495
      hcache_delete_raw(mdata->hcache, "MODSEQ", 6);
×
1496
    }
1497

1498
    if (has_qresync)
×
1499
      imap_hcache_store_uid_seqset(mdata);
×
1500
    else
1501
      imap_hcache_clear_uid_seqset(mdata);
×
1502
  }
1503
#endif /* USE_HCACHE */
1504

1505
  /* TODO: it's not clear to me why we are calling mx_alloc_memory yet again. */
1506
  mx_alloc_memory(m, m->msg_count);
×
1507

1508
  mdata->reopen |= IMAP_REOPEN_ALLOW;
×
1509

1510
  rc = msn_end;
1511

1512
bail:
×
1513
#ifdef USE_HCACHE
1514
  imap_hcache_close(mdata);
×
1515
  FREE(&uid_seqset);
×
1516
#endif /* USE_HCACHE */
1517

1518
  return rc;
×
1519
}
1520

1521
/**
1522
 * imap_append_message - Write an email back to the server
1523
 * @param m   Mailbox
1524
 * @param msg Message to save
1525
 * @retval  0 Success
1526
 * @retval -1 Failure
1527
 */
1528
int imap_append_message(struct Mailbox *m, struct Message *msg)
×
1529
{
1530
  if (!m || !msg)
×
1531
    return -1;
1532

1533
  FILE *fp = NULL;
×
1534
  char buf[2048] = { 0 };
×
1535
  struct Buffer *internaldate = NULL;
×
1536
  struct Buffer *imap_flags = NULL;
×
1537
  size_t len;
1538
  struct Progress *progress = NULL;
×
1539
  size_t sent;
1540
  int c, last;
1541
  int rc;
1542

1543
  struct ImapAccountData *adata = imap_adata_get(m);
×
1544
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1545

1546
  fp = mutt_file_fopen(msg->path, "r");
×
1547
  if (!fp)
×
1548
  {
1549
    mutt_perror("%s", msg->path);
×
1550
    goto fail;
×
1551
  }
1552

1553
  /* currently we set the \Seen flag on all messages, but probably we
1554
   * should scan the message Status header for flag info. Since we're
1555
   * already rereading the whole file for length it isn't any more
1556
   * expensive (it'd be nice if we had the file size passed in already
1557
   * by the code that writes the file, but that's a lot of changes.
1558
   * Ideally we'd have an Email structure with flag info here... */
1559
  for (last = EOF, len = 0; (c = fgetc(fp)) != EOF; last = c)
×
1560
  {
1561
    if ((c == '\n') && (last != '\r'))
×
1562
      len++;
×
1563

1564
    len++;
×
1565
  }
1566
  rewind(fp);
×
1567

1568
  if (m->verbose)
×
1569
  {
1570
    progress = progress_new(MUTT_PROGRESS_NET, len);
×
1571
    progress_set_message(progress, _("Uploading message..."));
×
1572
  }
1573

1574
  internaldate = buf_pool_get();
×
1575
  mutt_date_make_imap(internaldate, msg->received);
×
1576

1577
  imap_flags = buf_pool_get();
×
1578

1579
  if (msg->flags.read)
×
1580
    buf_addstr(imap_flags, " \\Seen");
×
1581
  if (msg->flags.replied)
×
1582
    buf_addstr(imap_flags, " \\Answered");
×
1583
  if (msg->flags.flagged)
×
1584
    buf_addstr(imap_flags, " \\Flagged");
×
1585
  if (msg->flags.draft)
×
1586
    buf_addstr(imap_flags, " \\Draft");
×
1587

1588
  snprintf(buf, sizeof(buf), "APPEND %s (%s) \"%s\" {%lu}", mdata->munge_name,
×
1589
           imap_flags->data + 1, buf_string(internaldate), (unsigned long) len);
×
1590
  buf_pool_release(&internaldate);
×
1591

1592
  imap_cmd_start(adata, buf);
×
1593

1594
  do
1595
  {
1596
    rc = imap_cmd_step(adata);
×
1597
  } while (rc == IMAP_RES_CONTINUE);
×
1598

1599
  if (rc != IMAP_RES_RESPOND)
×
1600
    goto cmd_step_fail;
×
1601

1602
  for (last = EOF, sent = len = 0; (c = fgetc(fp)) != EOF; last = c)
×
1603
  {
1604
    if ((c == '\n') && (last != '\r'))
×
1605
      buf[len++] = '\r';
×
1606

1607
    buf[len++] = c;
×
1608

1609
    if (len > sizeof(buf) - 3)
×
1610
    {
1611
      sent += len;
×
1612
      if (flush_buffer(buf, &len, adata->conn) < 0)
×
1613
        goto fail;
×
1614
      progress_update(progress, sent, -1);
×
1615
    }
1616
  }
1617

1618
  if (len > 0)
×
1619
    if (flush_buffer(buf, &len, adata->conn) < 0)
×
1620
      goto fail;
×
1621

1622
  if (mutt_socket_send(adata->conn, "\r\n") < 0)
×
1623
    goto fail;
×
1624
  mutt_file_fclose(&fp);
×
1625

1626
  do
1627
  {
1628
    rc = imap_cmd_step(adata);
×
1629
  } while (rc == IMAP_RES_CONTINUE);
×
1630

1631
  if (rc != IMAP_RES_OK)
×
1632
    goto cmd_step_fail;
×
1633

1634
  progress_free(&progress);
×
1635
  buf_pool_release(&imap_flags);
×
1636
  return 0;
×
1637

1638
cmd_step_fail:
×
1639
  mutt_debug(LL_DEBUG1, "command failed: %s\n", adata->buf);
×
1640
  if (rc != IMAP_RES_BAD)
×
1641
  {
1642
    char *pc = imap_next_word(adata->buf); /* skip sequence number or token */
×
1643
    pc = imap_next_word(pc);               /* skip response code */
×
1644
    if (*pc != '\0')
×
1645
      mutt_error("%s", pc);
×
1646
  }
1647

1648
fail:
×
1649
  mutt_file_fclose(&fp);
×
1650
  progress_free(&progress);
×
1651
  buf_pool_release(&imap_flags);
×
1652
  return -1;
×
1653
}
1654

1655
/**
1656
 * emails_to_uid_array - Extract IMAP UIDs from Emails
1657
 * @param ea   Array of Emails
1658
 * @param uida Empty UID array
1659
 * @retval num Number of UIDs in the array
1660
 */
1661
static int emails_to_uid_array(struct EmailArray *ea, struct UidArray *uida)
×
1662
{
1663
  struct Email **ep = NULL;
1664
  ARRAY_FOREACH(ep, ea)
×
1665
  {
1666
    struct Email *e = *ep;
×
1667
    struct ImapEmailData *edata = imap_edata_get(e);
×
1668

1669
    ARRAY_ADD(uida, edata->uid);
×
1670
  }
1671
  ARRAY_SORT(uida, imap_sort_uid, NULL);
×
1672

1673
  return ARRAY_SIZE(uida);
×
1674
}
1675

1676
/**
1677
 * imap_copy_messages - Server COPY messages to another folder
1678
 * @param m        Mailbox
1679
 * @param ea       Array of Emails to copy
1680
 * @param dest     Destination folder
1681
 * @param save_opt Copy or move, e.g. #SAVE_MOVE
1682
 * @retval -1 Error
1683
 * @retval  0 Success
1684
 * @retval  1 Non-fatal error - try fetch/append
1685
 */
1686
int imap_copy_messages(struct Mailbox *m, struct EmailArray *ea,
×
1687
                       const char *dest, enum MessageSaveOpt save_opt)
1688
{
1689
  if (!m || !ea || ARRAY_EMPTY(ea) || !dest)
×
1690
    return -1;
1691

1692
  char buf[PATH_MAX] = { 0 };
×
1693
  char mbox[PATH_MAX] = { 0 };
×
1694
  char mmbox[PATH_MAX] = { 0 };
×
1695
  char prompt[PATH_MAX + 64];
1696
  int rc;
1697
  struct ConnAccount cac = { { 0 } };
×
1698
  enum QuadOption err_continue = MUTT_NO;
×
1699
  int triedcreate = 0;
1700
  struct Email *e_cur = *ARRAY_GET(ea, 0);
×
1701
  bool single = (ARRAY_SIZE(ea) == 1);
1702
  struct ImapAccountData *adata = imap_adata_get(m);
×
1703

1704
  if (single && e_cur->attach_del)
×
1705
  {
1706
    mutt_debug(LL_DEBUG3, "#1 Message contains attachments to be deleted\n");
×
1707
    return 1;
×
1708
  }
1709

1710
  if (imap_parse_path(dest, &cac, buf, sizeof(buf)))
×
1711
  {
1712
    mutt_debug(LL_DEBUG1, "bad destination %s\n", dest);
×
1713
    return -1;
×
1714
  }
1715

1716
  /* check that the save-to folder is in the same account */
1717
  if (!imap_account_match(&adata->conn->account, &cac))
×
1718
  {
1719
    mutt_debug(LL_DEBUG3, "%s not same server as %s\n", dest, mailbox_path(m));
×
1720
    return 1;
×
1721
  }
1722

1723
  imap_fix_path_with_delim(adata->delim, buf, mbox, sizeof(mbox));
×
1724
  if (*mbox == '\0')
×
1725
    mutt_str_copy(mbox, "INBOX", sizeof(mbox));
×
1726
  imap_munge_mbox_name(adata->unicode, mmbox, sizeof(mmbox), mbox);
×
1727

1728
  /* loop in case of TRYCREATE */
1729
  struct Buffer *cmd = buf_pool_get();
×
1730
  struct Buffer *sync_cmd = buf_pool_get();
×
1731
  do
1732
  {
1733
    buf_reset(sync_cmd);
×
1734
    buf_reset(cmd);
×
1735

1736
    if (single)
×
1737
    {
1738
      mutt_message(_("Copying message %d to %s..."), e_cur->index + 1, mbox);
×
1739
      buf_add_printf(cmd, "UID COPY %u %s", imap_edata_get(e_cur)->uid, mmbox);
×
1740

1741
      if (e_cur->active && e_cur->changed)
×
1742
      {
1743
        rc = imap_sync_message_for_copy(m, e_cur, sync_cmd, &err_continue);
×
1744
        if (rc < 0)
×
1745
        {
1746
          mutt_debug(LL_DEBUG1, "#2 could not sync\n");
×
1747
          goto out;
×
1748
        }
1749
      }
1750
      rc = imap_exec(adata, buf_string(cmd), IMAP_CMD_QUEUE);
×
1751
      if (rc != IMAP_EXEC_SUCCESS)
×
1752
      {
1753
        mutt_debug(LL_DEBUG1, "#2 could not queue copy\n");
×
1754
        goto out;
×
1755
      }
1756
    }
1757
    else /* copy tagged messages */
1758
    {
1759
      /* if any messages have attachments to delete, fall through to FETCH
1760
       * and APPEND. TODO: Copy what we can with COPY, fall through for the
1761
       * remainder. */
1762
      struct Email **ep = NULL;
1763
      ARRAY_FOREACH(ep, ea)
×
1764
      {
1765
        struct Email *e = *ep;
×
1766
        if (e->attach_del)
×
1767
        {
1768
          mutt_debug(LL_DEBUG3, "#2 Message contains attachments to be deleted\n");
×
1769
          rc = 1;
1770
          goto out;
×
1771
        }
1772

1773
        if (e->active && e->changed)
×
1774
        {
1775
          rc = imap_sync_message_for_copy(m, e, sync_cmd, &err_continue);
×
1776
          if (rc < 0)
×
1777
          {
1778
            mutt_debug(LL_DEBUG1, "#1 could not sync\n");
×
1779
            goto out;
×
1780
          }
1781
        }
1782
      }
1783

1784
      struct UidArray uida = ARRAY_HEAD_INITIALIZER;
×
1785
      emails_to_uid_array(ea, &uida);
×
1786
      rc = imap_exec_msg_set(adata, "UID COPY", mmbox, &uida);
×
1787
      ARRAY_FREE(&uida);
×
1788

1789
      if (rc == 0)
×
1790
      {
1791
        mutt_debug(LL_DEBUG1, "No messages tagged\n");
×
1792
        rc = -1;
1793
        goto out;
×
1794
      }
1795
      else if (rc < 0)
×
1796
      {
1797
        mutt_debug(LL_DEBUG1, "#1 could not queue copy\n");
×
1798
        goto out;
×
1799
      }
1800
      else
1801
      {
1802
        mutt_message(ngettext("Copying %d message to %s...", "Copying %d messages to %s...", rc),
×
1803
                     rc, mbox);
1804
      }
1805
    }
1806

1807
    /* let's get it on */
1808
    rc = imap_exec(adata, NULL, IMAP_CMD_NO_FLAGS);
×
1809
    if (rc == IMAP_EXEC_ERROR)
×
1810
    {
1811
      if (triedcreate)
×
1812
      {
1813
        mutt_debug(LL_DEBUG1, "Already tried to create mailbox %s\n", mbox);
×
1814
        break;
×
1815
      }
1816
      /* bail out if command failed for reasons other than nonexistent target */
1817
      if (!mutt_istr_startswith(imap_get_qualifier(adata->buf), "[TRYCREATE]"))
×
1818
        break;
1819
      mutt_debug(LL_DEBUG3, "server suggests TRYCREATE\n");
×
1820
      snprintf(prompt, sizeof(prompt), _("Create %s?"), mbox);
×
1821
      const bool c_confirm_create = cs_subset_bool(NeoMutt->sub, "confirm_create");
×
1822
      if (c_confirm_create &&
×
1823
          (query_yesorno_help(prompt, MUTT_YES, NeoMutt->sub, "confirm_create") != MUTT_YES))
×
1824
      {
1825
        mutt_clear_error();
×
1826
        goto out;
×
1827
      }
1828
      if (imap_create_mailbox(adata, mbox) < 0)
×
1829
        break;
1830
      triedcreate = 1;
1831
    }
1832
  } while (rc == IMAP_EXEC_ERROR);
×
1833

1834
  if (rc != 0)
×
1835
  {
1836
    imap_error("imap_copy_messages", adata->buf);
×
1837
    goto out;
×
1838
  }
1839

1840
  /* cleanup */
1841
  if (save_opt == SAVE_MOVE)
×
1842
  {
1843
    struct Email **ep = NULL;
1844
    ARRAY_FOREACH(ep, ea)
×
1845
    {
1846
      struct Email *e = *ep;
×
1847
      mutt_set_flag(m, e, MUTT_DELETE, true, true);
×
1848
      mutt_set_flag(m, e, MUTT_PURGE, true, true);
×
1849
    }
1850
  }
1851

1852
  rc = 0;
1853

1854
out:
×
1855
  buf_pool_release(&cmd);
×
1856
  buf_pool_release(&sync_cmd);
×
1857

1858
  return (rc < 0) ? -1 : rc;
×
1859
}
1860

1861
/**
1862
 * imap_cache_del - Delete an email from the body cache
1863
 * @param m     Selected Imap Mailbox
1864
 * @param e     Email
1865
 * @retval  0 Success
1866
 * @retval -1 Failure
1867
 */
1868
int imap_cache_del(struct Mailbox *m, struct Email *e)
×
1869
{
1870
  struct ImapAccountData *adata = imap_adata_get(m);
×
1871
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1872

1873
  if (!e || !adata || (adata->mailbox != m))
×
1874
    return -1;
1875

1876
  mdata->bcache = imap_bcache_open(m);
×
1877
  char id[64] = { 0 };
×
1878
  snprintf(id, sizeof(id), "%u-%u", mdata->uidvalidity, imap_edata_get(e)->uid);
×
1879
  return mutt_bcache_del(mdata->bcache, id);
×
1880
}
1881

1882
/**
1883
 * imap_cache_clean - Delete all the entries in the message cache
1884
 * @param m  SelectedImap Mailbox
1885
 * @retval 0 Always
1886
 */
1887
int imap_cache_clean(struct Mailbox *m)
×
1888
{
1889
  struct ImapAccountData *adata = imap_adata_get(m);
×
1890
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
1891

1892
  if (!adata || (adata->mailbox != m))
×
1893
    return -1;
1894

1895
  mdata->bcache = imap_bcache_open(m);
×
1896
  mutt_bcache_list(mdata->bcache, imap_bcache_delete, mdata);
×
1897

1898
  return 0;
×
1899
}
1900

1901
/**
1902
 * imap_set_flags - Fill the message header according to the server flags
1903
 * @param[in]  m              Imap Selected Mailbox
1904
 * @param[in]  e              Email
1905
 * @param[in]  s              Command string
1906
 * @param[out] server_changes Set to true if the flags have changed
1907
 * @retval ptr  The end of flags string
1908
 * @retval NULL Failure
1909
 *
1910
 * Expects a flags line of the form "FLAGS (flag flag ...)"
1911
 *
1912
 * imap_set_flags: fill out the message header according to the flags from
1913
 * the server. Expects a flags line of the form "FLAGS (flag flag ...)"
1914
 *
1915
 * Sets server_changes to 1 if a change to a flag is made, or in the
1916
 * case of e->changed, if a change to a flag _would_ have been
1917
 * made.
1918
 */
1919
char *imap_set_flags(struct Mailbox *m, struct Email *e, char *s, bool *server_changes)
×
1920
{
1921
  struct ImapAccountData *adata = imap_adata_get(m);
×
1922
  if (!adata || (adata->mailbox != m))
×
1923
    return NULL;
1924

1925
  struct ImapHeader newh = { 0 };
×
1926
  struct ImapEmailData old_edata = { 0 };
1927
  int local_changes = e->changed;
×
1928

1929
  struct ImapEmailData *edata = e->edata;
×
1930
  newh.edata = edata;
×
1931

1932
  mutt_debug(LL_DEBUG2, "parsing FLAGS\n");
×
1933
  s = msg_parse_flags(&newh, s);
×
1934
  if (!s)
×
1935
    return NULL;
1936

1937
  /* Update tags system */
1938
  /* We take a copy of the tags so we can split the string */
1939
  char *tags_copy = mutt_str_dup(edata->flags_remote);
×
1940
  driver_tags_replace(&e->tags, tags_copy);
×
1941
  FREE(&tags_copy);
×
1942

1943
  /* YAUH (yet another ugly hack): temporarily set context to
1944
   * read-write even if it's read-only, so *server* updates of
1945
   * flags can be processed by mutt_set_flag. mailbox->changed must
1946
   * be restored afterwards */
1947
  bool readonly = m->readonly;
×
1948
  m->readonly = false;
×
1949

1950
  /* This is redundant with the following two checks. Removing:
1951
   * mutt_set_flag (m, e, MUTT_NEW, !(edata->read || edata->old), true); */
1952
  set_changed_flag(m, e, local_changes, server_changes, MUTT_OLD, old_edata.old,
×
1953
                   edata->old, e->old);
×
1954
  set_changed_flag(m, e, local_changes, server_changes, MUTT_READ,
×
1955
                   old_edata.read, edata->read, e->read);
×
1956
  set_changed_flag(m, e, local_changes, server_changes, MUTT_DELETE,
×
1957
                   old_edata.deleted, edata->deleted, e->deleted);
×
1958
  set_changed_flag(m, e, local_changes, server_changes, MUTT_FLAG,
×
1959
                   old_edata.flagged, edata->flagged, e->flagged);
×
1960
  set_changed_flag(m, e, local_changes, server_changes, MUTT_REPLIED,
×
1961
                   old_edata.replied, edata->replied, e->replied);
×
1962

1963
  /* this message is now definitively *not* changed (mutt_set_flag
1964
   * marks things changed as a side-effect) */
1965
  if (local_changes == 0)
×
1966
    e->changed = false;
×
1967
  m->changed &= !readonly;
×
1968
  m->readonly = readonly;
×
1969

1970
  return s;
×
1971
}
1972

1973
/**
1974
 * imap_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open
1975
 */
1976
bool imap_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
×
1977
{
1978
  struct Envelope *newenv = NULL;
×
1979
  char buf[1024] = { 0 };
×
1980
  char *pc = NULL;
1981
  unsigned int bytes;
1982
  unsigned int uid;
1983
  bool retried = false;
1984
  bool read;
1985
  int rc;
1986

1987
  /* Sam's weird courier server returns an OK response even when FETCH
1988
   * fails. Thanks Sam. */
1989
  bool fetched = false;
1990

1991
  struct ImapAccountData *adata = imap_adata_get(m);
×
1992

1993
  if (!adata || (adata->mailbox != m))
×
1994
    return false;
1995

1996
  msg->fp = msg_cache_get(m, e);
×
1997
  if (msg->fp)
×
1998
  {
1999
    if (imap_edata_get(e)->parsed)
×
2000
      return true;
2001
    goto parsemsg;
×
2002
  }
2003

2004
  /* This function is called in a few places after endwin()
2005
   * e.g. mutt_pipe_message(). */
2006
  bool output_progress = !isendwin() && m->verbose;
×
2007
  if (output_progress)
2008
    mutt_message(_("Fetching message..."));
×
2009

2010
  msg->fp = msg_cache_put(m, e);
×
2011
  if (!msg->fp)
×
2012
  {
2013
    struct Buffer *tempfile = buf_pool_get();
×
2014
    buf_mktemp(tempfile);
×
2015
    msg->fp = mutt_file_fopen(buf_string(tempfile), "w+");
×
2016
    unlink(buf_string(tempfile));
×
2017
    buf_pool_release(&tempfile);
×
2018

2019
    if (!msg->fp)
×
2020
      return false;
×
2021
  }
2022

2023
  /* mark this header as currently inactive so the command handler won't
2024
   * also try to update it. HACK until all this code can be moved into the
2025
   * command handler */
2026
  e->active = false;
×
2027

2028
  const bool c_imap_peek = cs_subset_bool(NeoMutt->sub, "imap_peek");
×
2029
  snprintf(buf, sizeof(buf), "UID FETCH %u %s", imap_edata_get(e)->uid,
×
2030
           ((adata->capabilities & IMAP_CAP_IMAP4REV1) ?
×
2031
                (c_imap_peek ? "BODY.PEEK[]" : "BODY[]") :
×
2032
                "RFC822"));
2033

2034
  imap_cmd_start(adata, buf);
×
2035
  do
2036
  {
2037
    rc = imap_cmd_step(adata);
×
2038
    if (rc != IMAP_RES_CONTINUE)
×
2039
      break;
2040

2041
    pc = adata->buf;
×
2042
    pc = imap_next_word(pc);
×
2043
    pc = imap_next_word(pc);
×
2044

2045
    if (mutt_istr_startswith(pc, "FETCH"))
×
2046
    {
2047
      while (*pc)
×
2048
      {
2049
        pc = imap_next_word(pc);
×
2050
        if (pc[0] == '(')
×
2051
          pc++;
×
2052
        if (mutt_istr_startswith(pc, "UID"))
×
2053
        {
2054
          pc = imap_next_word(pc);
×
2055
          if (!mutt_str_atoui(pc, &uid))
×
2056
            goto bail;
×
2057
          if (uid != imap_edata_get(e)->uid)
×
2058
          {
2059
            mutt_error(_("The message index is incorrect. Try reopening the mailbox."));
×
2060
          }
2061
        }
2062
        else if (mutt_istr_startswith(pc, "RFC822") || mutt_istr_startswith(pc, "BODY[]"))
×
2063
        {
×
2064
          pc = imap_next_word(pc);
×
2065
          if (imap_get_literal_count(pc, &bytes) < 0)
×
2066
          {
2067
            imap_error("imap_msg_open()", buf);
×
2068
            goto bail;
×
2069
          }
2070

2071
          const int res = imap_read_literal(msg->fp, adata, bytes, NULL);
×
2072
          if (res < 0)
×
2073
          {
2074
            goto bail;
×
2075
          }
2076
          /* pick up trailing line */
2077
          rc = imap_cmd_step(adata);
×
2078
          if (rc != IMAP_RES_CONTINUE)
×
2079
            goto bail;
×
2080
          pc = adata->buf;
×
2081

2082
          fetched = true;
2083
        }
2084
        else if (!e->changed && mutt_istr_startswith(pc, "FLAGS"))
×
2085
        {
2086
          /* UW-IMAP will provide a FLAGS update here if the FETCH causes a
2087
           * change (eg from \Unseen to \Seen).
2088
           * Uncommitted changes in neomutt take precedence. If we decide to
2089
           * incrementally update flags later, this won't stop us syncing */
2090
          pc = imap_set_flags(m, e, pc, NULL);
×
2091
          if (!pc)
×
2092
            goto bail;
×
2093
        }
2094
      }
2095
    }
2096
  } while (rc == IMAP_RES_CONTINUE);
×
2097

2098
  /* see comment before command start. */
2099
  e->active = true;
×
2100

2101
  fflush(msg->fp);
×
2102
  if (ferror(msg->fp))
×
2103
    goto bail;
×
2104

2105
  if (rc != IMAP_RES_OK)
×
2106
    goto bail;
×
2107

2108
  if (!fetched || !imap_code(adata->buf))
×
2109
    goto bail;
×
2110

2111
  if (msg_cache_commit(m, e) < 0)
×
2112
    mutt_debug(LL_DEBUG1, "failed to add message to cache\n");
×
2113

2114
parsemsg:
×
2115
  /* Update the header information.  Previously, we only downloaded a
2116
   * portion of the headers, those required for the main display.  */
2117
  rewind(msg->fp);
×
2118
  /* It may be that the Status header indicates a message is read, but the
2119
   * IMAP server doesn't know the message has been \Seen. So we capture
2120
   * the server's notion of 'read' and if it differs from the message info
2121
   * picked up in mutt_rfc822_read_header, we mark the message (and context
2122
   * changed). Another possibility: ignore Status on IMAP? */
2123
  read = e->read;
×
2124
  newenv = mutt_rfc822_read_header(msg->fp, e, false, false);
×
2125
  mutt_env_merge(e->env, &newenv);
×
2126

2127
  /* see above. We want the new status in e->read, so we unset it manually
2128
   * and let mutt_set_flag set it correctly, updating context. */
2129
  if (read != e->read)
×
2130
  {
2131
    e->read = read;
×
2132
    mutt_set_flag(m, e, MUTT_NEW, read, true);
×
2133
  }
2134

2135
  e->lines = 0;
×
2136
  while (fgets(buf, sizeof(buf), msg->fp) && !feof(msg->fp))
×
2137
  {
2138
    e->lines++;
×
2139
  }
2140

2141
  e->body->length = ftell(msg->fp) - e->body->offset;
×
2142

2143
  mutt_clear_error();
×
2144
  rewind(msg->fp);
×
2145
  imap_edata_get(e)->parsed = true;
×
2146

2147
  /* retry message parse if cached message is empty */
2148
  if (!retried && ((e->lines == 0) || (e->body->length == 0)))
×
2149
  {
2150
    imap_cache_del(m, e);
×
2151
    retried = true;
2152
    goto parsemsg;
×
2153
  }
2154

2155
  return true;
2156

2157
bail:
×
2158
  e->active = true;
×
2159
  mutt_file_fclose(&msg->fp);
×
2160
  imap_cache_del(m, e);
×
2161
  return false;
×
2162
}
2163

2164
/**
2165
 * imap_msg_commit - Save changes to an email - Implements MxOps::msg_commit() - @ingroup mx_msg_commit
2166
 *
2167
 * @note May also return EOF Failure, see errno
2168
 */
2169
int imap_msg_commit(struct Mailbox *m, struct Message *msg)
×
2170
{
2171
  int rc = mutt_file_fclose(&msg->fp);
×
2172
  if (rc != 0)
×
2173
    return rc;
2174

2175
  return imap_append_message(m, msg);
×
2176
}
2177

2178
/**
2179
 * imap_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close
2180
 *
2181
 * @note May also return EOF Failure, see errno
2182
 */
2183
int imap_msg_close(struct Mailbox *m, struct Message *msg)
×
2184
{
2185
  return mutt_file_fclose(&msg->fp);
×
2186
}
2187

2188
/**
2189
 * imap_msg_save_hcache - Save message to the header cache - Implements MxOps::msg_save_hcache() - @ingroup mx_msg_save_hcache
2190
 */
2191
int imap_msg_save_hcache(struct Mailbox *m, struct Email *e)
×
2192
{
2193
  int rc = 0;
2194
#ifdef USE_HCACHE
2195
  bool close_hc = true;
2196
  struct ImapAccountData *adata = imap_adata_get(m);
×
2197
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
2198
  if (!mdata || !adata)
×
2199
    return -1;
2200
  if (mdata->hcache)
×
2201
    close_hc = false;
2202
  else
2203
    imap_hcache_open(adata, mdata, true);
×
2204
  rc = imap_hcache_put(mdata, e);
×
2205
  if (close_hc)
×
2206
    imap_hcache_close(mdata);
×
2207
#endif
2208
  return rc;
2209
}
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