• 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/command.c
1
/**
2
 * @file
3
 * Send/receive commands to/from an IMAP server
4
 *
5
 * @authors
6
 * Copyright (C) 1996-1998,2010,2012 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 1996-1999 Brandon Long <blong@fiction.net>
8
 * Copyright (C) 1999-2009,2011 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-2023 Pietro Cerutti <gahr@gahr.ch>
12
 * Copyright (C) 2019 Fabian Groffen <grobian@gentoo.org>
13
 * Copyright (C) 2019 Federico Kircheis <federico.kircheis@gmail.com>
14
 * Copyright (C) 2019 Ian Zimmerman <itz@no-use.mooo.com>
15
 *
16
 * @copyright
17
 * This program is free software: you can redistribute it and/or modify it under
18
 * the terms of the GNU General Public License as published by the Free Software
19
 * Foundation, either version 2 of the License, or (at your option) any later
20
 * version.
21
 *
22
 * This program is distributed in the hope that it will be useful, but WITHOUT
23
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
24
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
25
 * details.
26
 *
27
 * You should have received a copy of the GNU General Public License along with
28
 * this program.  If not, see <http://www.gnu.org/licenses/>.
29
 */
30

31
/**
32
 * @page imap_command Send/receive commands
33
 *
34
 * Send/receive commands to/from an IMAP server
35
 */
36

37
#include "config.h"
38
#include <errno.h>
39
#include <limits.h>
40
#include <stdbool.h>
41
#include <stdint.h>
42
#include <stdio.h>
43
#include <stdlib.h>
44
#include <string.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 "adata.h"
52
#include "commands.h"
53
#include "edata.h"
54
#include "mdata.h"
55
#include "msn.h"
56
#include "mutt_logging.h"
57
#include "mx.h"
58

59
#define IMAP_CMD_BUFSIZE 512
60

61
/**
62
 * Capabilities - Server capabilities strings that we understand
63
 *
64
 * @note This must be kept in the same order as ImapCaps.
65
 */
66
static const char *const Capabilities[] = {
67
  "IMAP4",
68
  "IMAP4rev1",
69
  "STATUS",
70
  "ACL",
71
  "NAMESPACE",
72
  "AUTH=CRAM-MD5",
73
  "AUTH=GSSAPI",
74
  "AUTH=ANONYMOUS",
75
  "AUTH=OAUTHBEARER",
76
  "AUTH=XOAUTH2",
77
  "STARTTLS",
78
  "LOGINDISABLED",
79
  "IDLE",
80
  "SASL-IR",
81
  "ENABLE",
82
  "CONDSTORE",
83
  "QRESYNC",
84
  "LIST-EXTENDED",
85
  "COMPRESS=DEFLATE",
86
  "X-GM-EXT-1",
87
  "ID",
88
  NULL,
89
};
90

91
/**
92
 * cmd_queue_full - Is the IMAP command queue full?
93
 * @param adata Imap Account data
94
 * @retval true Queue is full
95
 */
96
static bool cmd_queue_full(struct ImapAccountData *adata)
97
{
98
  if (((adata->nextcmd + 1) % adata->cmdslots) == adata->lastcmd)
×
99
    return true;
100

101
  return false;
102
}
103

104
/**
105
 * cmd_new - Create and queue a new command control block
106
 * @param adata Imap Account data
107
 * @retval NULL The pipeline is full
108
 * @retval ptr New command
109
 */
110
static struct ImapCommand *cmd_new(struct ImapAccountData *adata)
×
111
{
112
  struct ImapCommand *cmd = NULL;
113

114
  if (cmd_queue_full(adata))
115
  {
116
    mutt_debug(LL_DEBUG3, "IMAP command queue full\n");
×
117
    return NULL;
×
118
  }
119

120
  cmd = adata->cmds + adata->nextcmd;
×
121
  adata->nextcmd = (adata->nextcmd + 1) % adata->cmdslots;
×
122

123
  snprintf(cmd->seq, sizeof(cmd->seq), "%c%04u", adata->seqid, adata->seqno++);
×
124
  if (adata->seqno > 9999)
×
125
    adata->seqno = 0;
×
126

127
  cmd->state = IMAP_RES_NEW;
×
128

129
  return cmd;
×
130
}
131

132
/**
133
 * cmd_queue - Add a IMAP command to the queue
134
 * @param adata Imap Account data
135
 * @param cmdstr Command string
136
 * @param flags  Server flags, see #ImapCmdFlags
137
 * @retval  0 Success
138
 * @retval <0 Failure, e.g. #IMAP_RES_BAD
139
 *
140
 * If the queue is full, attempts to drain it.
141
 */
142
static int cmd_queue(struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
×
143
{
144
  if (cmd_queue_full(adata))
145
  {
146
    mutt_debug(LL_DEBUG3, "Draining IMAP command pipeline\n");
×
147

148
    const int rc = imap_exec(adata, NULL, flags & IMAP_CMD_POLL);
×
149

150
    if (rc == IMAP_EXEC_ERROR)
×
151
      return IMAP_RES_BAD;
152
  }
153

154
  struct ImapCommand *cmd = cmd_new(adata);
×
155
  if (!cmd)
×
156
    return IMAP_RES_BAD;
157

158
  if (buf_add_printf(&adata->cmdbuf, "%s %s\r\n", cmd->seq, cmdstr) < 0)
×
159
    return IMAP_RES_BAD;
160

161
  return 0;
162
}
163

164
/**
165
 * cmd_handle_fatal - When ImapAccountData is in fatal state, do what we can
166
 * @param adata Imap Account data
167
 */
168
static void cmd_handle_fatal(struct ImapAccountData *adata)
×
169
{
170
  adata->status = IMAP_FATAL;
×
171

172
  if (!adata->mailbox)
×
173
    return;
174

175
  struct ImapMboxData *mdata = adata->mailbox->mdata;
×
176

177
  if ((adata->state >= IMAP_SELECTED) && (mdata->reopen & IMAP_REOPEN_ALLOW))
×
178
  {
179
    mx_fastclose_mailbox(adata->mailbox, true);
×
180
    mutt_error(_("Mailbox %s@%s closed"), adata->conn->account.user,
×
181
               adata->conn->account.host);
182
  }
183

184
  imap_close_connection(adata);
×
185
  if (!adata->recovering)
×
186
  {
187
    adata->recovering = true;
×
188
    if (imap_login(adata))
×
189
      mutt_clear_error();
×
190
    adata->recovering = false;
×
191
  }
192
}
193

194
/**
195
 * cmd_start - Start a new IMAP command
196
 * @param adata Imap Account data
197
 * @param cmdstr Command string
198
 * @param flags  Command flags, see #ImapCmdFlags
199
 * @retval  0 Success
200
 * @retval <0 Failure, e.g. #IMAP_RES_BAD
201
 */
202
static int cmd_start(struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
×
203
{
204
  int rc;
205

206
  if (adata->status == IMAP_FATAL)
×
207
  {
208
    cmd_handle_fatal(adata);
×
209
    return -1;
×
210
  }
211

212
  if (cmdstr && ((rc = cmd_queue(adata, cmdstr, flags)) < 0))
×
213
    return rc;
214

215
  if (flags & IMAP_CMD_QUEUE)
×
216
    return 0;
217

218
  if (buf_is_empty(&adata->cmdbuf))
×
219
    return IMAP_RES_BAD;
220

221
  rc = mutt_socket_send_d(adata->conn, adata->cmdbuf.data,
×
222
                          (flags & IMAP_CMD_PASS) ? IMAP_LOG_PASS : IMAP_LOG_CMD);
223
  buf_reset(&adata->cmdbuf);
×
224

225
  /* unidle when command queue is flushed */
226
  if (adata->state == IMAP_IDLE)
×
227
    adata->state = IMAP_SELECTED;
×
228

229
  return (rc < 0) ? IMAP_RES_BAD : 0;
×
230
}
231

232
/**
233
 * cmd_status - Parse response line for tagged OK/NO/BAD
234
 * @param s Status string from server
235
 * @retval  0 Success
236
 * @retval <0 Failure, e.g. #IMAP_RES_BAD
237
 */
238
static int cmd_status(const char *s)
×
239
{
240
  s = imap_next_word((char *) s);
×
241

242
  if (mutt_istr_startswith(s, "OK"))
×
243
    return IMAP_RES_OK;
244
  if (mutt_istr_startswith(s, "NO"))
×
245
    return IMAP_RES_NO;
×
246

247
  return IMAP_RES_BAD;
248
}
249

250
/**
251
 * cmd_parse_expunge - Parse expunge command
252
 * @param adata Imap Account data
253
 * @param s     String containing MSN of message to expunge
254
 *
255
 * cmd_parse_expunge: mark headers with new sequence ID and mark adata to be
256
 * reopened at our earliest convenience
257
 */
258
static void cmd_parse_expunge(struct ImapAccountData *adata, const char *s)
×
259
{
260
  unsigned int exp_msn;
261
  struct Email *e = NULL;
262

263
  mutt_debug(LL_DEBUG2, "Handling EXPUNGE\n");
×
264

265
  struct ImapMboxData *mdata = adata->mailbox->mdata;
×
266

267
  if (!mutt_str_atoui(s, &exp_msn) || (exp_msn < 1) ||
×
268
      (exp_msn > imap_msn_highest(&mdata->msn)))
×
269
  {
270
    return;
×
271
  }
272

273
  e = imap_msn_get(&mdata->msn, exp_msn - 1);
×
274
  if (e)
×
275
  {
276
    /* imap_expunge_mailbox() will rewrite e->index.
277
     * It needs to resort using EMAIL_SORT_UNSORTED anyway, so setting to INT_MAX
278
     * makes the code simpler and possibly more efficient. */
279
    e->index = INT_MAX;
×
280
    imap_edata_get(e)->msn = 0;
×
281
  }
282

283
  /* decrement seqno of those above. */
284
  const size_t max_msn = imap_msn_highest(&mdata->msn);
×
285
  for (unsigned int cur = exp_msn; cur < max_msn; cur++)
×
286
  {
287
    e = imap_msn_get(&mdata->msn, cur);
×
288
    if (e)
×
289
      imap_edata_get(e)->msn--;
×
290
    imap_msn_set(&mdata->msn, cur - 1, e);
×
291
  }
292
  imap_msn_shrink(&mdata->msn, 1);
×
293

294
  mdata->reopen |= IMAP_EXPUNGE_PENDING;
×
295
}
296

297
/**
298
 * cmd_parse_vanished - Parse vanished command
299
 * @param adata Imap Account data
300
 * @param s     String containing MSN of message to expunge
301
 *
302
 * Handle VANISHED (RFC7162), which is like expunge, but passes a seqset of UIDs.
303
 * An optional (EARLIER) argument specifies not to decrement subsequent MSNs.
304
 */
305
static void cmd_parse_vanished(struct ImapAccountData *adata, char *s)
×
306
{
307
  bool earlier = false;
308
  int rc;
309
  unsigned int uid = 0;
×
310

311
  struct ImapMboxData *mdata = adata->mailbox->mdata;
×
312

313
  mutt_debug(LL_DEBUG2, "Handling VANISHED\n");
×
314

315
  if (mutt_istr_startswith(s, "(EARLIER)"))
×
316
  {
317
    /* The RFC says we should not decrement msns with the VANISHED EARLIER tag.
318
     * My experimentation says that's crap. */
319
    earlier = true;
320
    s = imap_next_word(s);
×
321
  }
322

323
  char *end_of_seqset = s;
324
  while (*end_of_seqset)
×
325
  {
326
    if (!strchr("0123456789:,", *end_of_seqset))
×
327
      *end_of_seqset = '\0';
×
328
    else
329
      end_of_seqset++;
×
330
  }
331

332
  struct SeqsetIterator *iter = mutt_seqset_iterator_new(s);
×
333
  if (!iter)
×
334
  {
335
    mutt_debug(LL_DEBUG2, "VANISHED: empty seqset [%s]?\n", s);
×
336
    return;
×
337
  }
338

339
  while ((rc = mutt_seqset_iterator_next(iter, &uid)) == 0)
×
340
  {
341
    struct Email *e = mutt_hash_int_find(mdata->uid_hash, uid);
×
342
    if (!e)
×
343
      continue;
×
344

345
    unsigned int exp_msn = imap_edata_get(e)->msn;
×
346

347
    /* imap_expunge_mailbox() will rewrite e->index.
348
     * It needs to resort using EMAIL_SORT_UNSORTED anyway, so setting to INT_MAX
349
     * makes the code simpler and possibly more efficient. */
350
    e->index = INT_MAX;
×
351
    imap_edata_get(e)->msn = 0;
×
352

353
    if ((exp_msn < 1) || (exp_msn > imap_msn_highest(&mdata->msn)))
×
354
    {
355
      mutt_debug(LL_DEBUG1, "VANISHED: msn for UID %u is incorrect\n", uid);
×
356
      continue;
×
357
    }
358
    if (imap_msn_get(&mdata->msn, exp_msn - 1) != e)
×
359
    {
360
      mutt_debug(LL_DEBUG1, "VANISHED: msn_index for UID %u is incorrect\n", uid);
×
361
      continue;
×
362
    }
363

364
    imap_msn_remove(&mdata->msn, exp_msn - 1);
×
365

366
    if (!earlier)
×
367
    {
368
      /* decrement seqno of those above. */
369
      const size_t max_msn = imap_msn_highest(&mdata->msn);
×
370
      for (unsigned int cur = exp_msn; cur < max_msn; cur++)
×
371
      {
372
        e = imap_msn_get(&mdata->msn, cur);
×
373
        if (e)
×
374
          imap_edata_get(e)->msn--;
×
375
        imap_msn_set(&mdata->msn, cur - 1, e);
×
376
      }
377

378
      imap_msn_shrink(&mdata->msn, 1);
×
379
    }
380
  }
381

382
  if (rc < 0)
×
383
    mutt_debug(LL_DEBUG1, "VANISHED: illegal seqset %s\n", s);
×
384

385
  mdata->reopen |= IMAP_EXPUNGE_PENDING;
×
386

387
  mutt_seqset_iterator_free(&iter);
×
388
}
389

390
/**
391
 * cmd_parse_fetch - Load fetch response into ImapAccountData
392
 * @param adata Imap Account data
393
 * @param s     String containing MSN of message to fetch
394
 *
395
 * Currently only handles unanticipated FETCH responses, and only FLAGS data.
396
 * We get these if another client has changed flags for a mailbox we've
397
 * selected.  Of course, a lot of code here duplicates code in message.c.
398
 */
399
static void cmd_parse_fetch(struct ImapAccountData *adata, char *s)
×
400
{
401
  unsigned int msn, uid;
402
  struct Email *e = NULL;
403
  char *flags = NULL;
404
  int uid_checked = 0;
405
  bool server_changes = false;
×
406

407
  struct ImapMboxData *mdata = imap_mdata_get(adata->mailbox);
×
408

409
  mutt_debug(LL_DEBUG3, "Handling FETCH\n");
×
410

411
  if (!mutt_str_atoui(s, &msn))
×
412
  {
413
    mutt_debug(LL_DEBUG3, "Skipping FETCH response - illegal MSN\n");
×
414
    return;
×
415
  }
416

417
  if ((msn < 1) || (msn > imap_msn_highest(&mdata->msn)))
×
418
  {
419
    mutt_debug(LL_DEBUG3, "Skipping FETCH response - MSN %u out of range\n", msn);
×
420
    return;
×
421
  }
422

423
  e = imap_msn_get(&mdata->msn, msn - 1);
×
424
  if (!e || !e->active)
×
425
  {
426
    mutt_debug(LL_DEBUG3, "Skipping FETCH response - MSN %u not in msn_index\n", msn);
×
427
    return;
×
428
  }
429

430
  mutt_debug(LL_DEBUG2, "Message UID %u updated\n", imap_edata_get(e)->uid);
×
431
  /* skip FETCH */
432
  s = imap_next_word(s);
×
433
  s = imap_next_word(s);
×
434

435
  if (*s != '(')
×
436
  {
437
    mutt_debug(LL_DEBUG1, "Malformed FETCH response\n");
×
438
    return;
×
439
  }
440
  s++;
×
441

442
  while (*s)
×
443
  {
444
    SKIPWS(s);
×
445
    size_t plen = mutt_istr_startswith(s, "FLAGS");
×
446
    if (plen != 0)
×
447
    {
448
      flags = s;
449
      if (uid_checked)
×
450
        break;
451

452
      s += plen;
×
453
      SKIPWS(s);
×
454
      if (*s != '(')
×
455
      {
456
        mutt_debug(LL_DEBUG1, "bogus FLAGS response: %s\n", s);
×
457
        return;
×
458
      }
459
      s++;
×
460
      while (*s && (*s != ')'))
×
461
        s++;
×
462
      if (*s == ')')
×
463
      {
464
        s++;
×
465
      }
466
      else
467
      {
468
        mutt_debug(LL_DEBUG1, "Unterminated FLAGS response: %s\n", s);
×
469
        return;
×
470
      }
471
    }
472
    else if ((plen = mutt_istr_startswith(s, "UID")))
×
473
    {
474
      s += plen;
×
475
      SKIPWS(s);
×
476
      if (!mutt_str_atoui(s, &uid))
×
477
      {
478
        mutt_debug(LL_DEBUG1, "Illegal UID.  Skipping update\n");
×
479
        return;
×
480
      }
481
      if (uid != imap_edata_get(e)->uid)
×
482
      {
483
        mutt_debug(LL_DEBUG1, "UID vs MSN mismatch.  Skipping update\n");
×
484
        return;
×
485
      }
486
      uid_checked = 1;
487
      if (flags)
×
488
        break;
489
      s = imap_next_word(s);
×
490
    }
491
    else if ((plen = mutt_istr_startswith(s, "MODSEQ")))
×
492
    {
493
      s += plen;
×
494
      SKIPWS(s);
×
495
      if (*s != '(')
×
496
      {
497
        mutt_debug(LL_DEBUG1, "bogus MODSEQ response: %s\n", s);
×
498
        return;
×
499
      }
500
      s++;
×
501
      while (*s && (*s != ')'))
×
502
        s++;
×
503
      if (*s == ')')
×
504
      {
505
        s++;
×
506
      }
507
      else
508
      {
509
        mutt_debug(LL_DEBUG1, "Unterminated MODSEQ response: %s\n", s);
×
510
        return;
×
511
      }
512
    }
513
    else if (*s == ')')
×
514
    {
515
      break; /* end of request */
516
    }
517
    else if (*s)
×
518
    {
519
      mutt_debug(LL_DEBUG2, "Only handle FLAGS updates\n");
×
520
      break;
×
521
    }
522
  }
523

524
  if (flags)
×
525
  {
526
    imap_set_flags(adata->mailbox, e, flags, &server_changes);
×
527
    if (server_changes)
×
528
    {
529
      /* If server flags could conflict with NeoMutt's flags, reopen the mailbox. */
530
      if (e->changed)
×
531
        mdata->reopen |= IMAP_EXPUNGE_PENDING;
×
532
      else
533
        mdata->check_status |= IMAP_FLAGS_PENDING;
×
534
    }
535
  }
536
}
537

538
/**
539
 * cmd_parse_capability - Set capability bits according to CAPABILITY response
540
 * @param adata Imap Account data
541
 * @param s     Command string with capabilities
542
 */
543
static void cmd_parse_capability(struct ImapAccountData *adata, char *s)
×
544
{
545
  mutt_debug(LL_DEBUG3, "Handling CAPABILITY\n");
×
546

547
  s = imap_next_word(s);
×
548
  char *bracket = strchr(s, ']');
×
549
  if (bracket)
×
550
    *bracket = '\0';
×
551
  FREE(&adata->capstr);
×
552
  adata->capstr = mutt_str_dup(s);
×
553
  adata->capabilities = 0;
×
554

555
  while (*s)
×
556
  {
557
    for (size_t i = 0; Capabilities[i]; i++)
×
558
    {
559
      size_t len = mutt_istr_startswith(s, Capabilities[i]);
×
560
      if (len != 0 && ((s[len] == '\0') || mutt_isspace(s[len])))
×
561
      {
562
        adata->capabilities |= (1 << i);
×
563
        mutt_debug(LL_DEBUG3, " Found capability \"%s\": %zu\n", Capabilities[i], i);
×
564
        break;
×
565
      }
566
    }
567
    s = imap_next_word(s);
×
568
  }
569
}
×
570

571
/**
572
 * cmd_parse_list - Parse a server LIST command (list mailboxes)
573
 * @param adata Imap Account data
574
 * @param s     Command string with folder list
575
 */
576
static void cmd_parse_list(struct ImapAccountData *adata, char *s)
×
577
{
578
  struct ImapList *list = NULL;
579
  struct ImapList lb = { 0 };
×
580
  unsigned int litlen;
581

582
  if (adata->cmdresult)
×
583
    list = adata->cmdresult;
584
  else
585
    list = &lb;
586

587
  memset(list, 0, sizeof(struct ImapList));
588

589
  /* flags */
590
  s = imap_next_word(s);
×
591
  if (*s != '(')
×
592
  {
593
    mutt_debug(LL_DEBUG1, "Bad LIST response\n");
×
594
    return;
×
595
  }
596
  s++;
×
597
  while (*s)
×
598
  {
599
    if (mutt_istr_startswith(s, "\\NoSelect"))
×
600
      list->noselect = true;
×
601
    else if (mutt_istr_startswith(s, "\\NonExistent")) /* rfc5258 */
×
602
      list->noselect = true;
×
603
    else if (mutt_istr_startswith(s, "\\NoInferiors"))
×
604
      list->noinferiors = true;
×
605
    else if (mutt_istr_startswith(s, "\\HasNoChildren")) /* rfc5258*/
×
606
      list->noinferiors = true;
×
607

608
    s = imap_next_word(s);
×
609
    if (*(s - 2) == ')')
×
610
      break;
611
  }
612

613
  /* Delimiter */
614
  if (!mutt_istr_startswith(s, "NIL"))
×
615
  {
616
    char delimbuf[5] = { 0 }; // worst case: "\\"\0
×
617
    snprintf(delimbuf, sizeof(delimbuf), "%s", s);
618
    imap_unquote_string(delimbuf);
×
619
    list->delim = delimbuf[0];
×
620
  }
621

622
  /* Name */
623
  s = imap_next_word(s);
×
624
  /* Notes often responds with literals here. We need a real tokenizer. */
625
  if (imap_get_literal_count(s, &litlen) == 0)
×
626
  {
627
    if (imap_cmd_step(adata) != IMAP_RES_CONTINUE)
×
628
    {
629
      adata->status = IMAP_FATAL;
×
630
      return;
×
631
    }
632

633
    if (strlen(adata->buf) < litlen)
×
634
    {
635
      mutt_debug(LL_DEBUG1, "Error parsing LIST mailbox\n");
×
636
      return;
×
637
    }
638

639
    list->name = adata->buf;
×
640
    s = list->name + litlen;
×
641
    if (s[0] != '\0')
×
642
    {
643
      s[0] = '\0';
×
644
      s++;
×
645
      SKIPWS(s);
×
646
    }
647
  }
648
  else
649
  {
650
    list->name = s;
×
651
    /* Exclude rfc5258 RECURSIVEMATCH CHILDINFO suffix */
652
    s = imap_next_word(s);
×
653
    if (s[0] != '\0')
×
654
      s[-1] = '\0';
×
655
    imap_unmunge_mbox_name(adata->unicode, list->name);
×
656
  }
657

658
  if (list->name[0] == '\0')
×
659
  {
660
    adata->delim = list->delim;
×
661
    mutt_debug(LL_DEBUG3, "Root delimiter: %c\n", adata->delim);
×
662
  }
663
}
664

665
/**
666
 * cmd_parse_lsub - Parse a server LSUB (list subscribed mailboxes)
667
 * @param adata Imap Account data
668
 * @param s     Command string with folder list
669
 */
670
static void cmd_parse_lsub(struct ImapAccountData *adata, char *s)
×
671
{
672
  if (adata->cmdresult)
×
673
  {
674
    /* caller will handle response itself */
675
    cmd_parse_list(adata, s);
×
676
    return;
×
677
  }
678

679
  const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
×
680
  if (!c_imap_check_subscribed)
×
681
    return;
682

683
  struct ImapList list = { 0 };
×
684

685
  adata->cmdresult = &list;
×
686
  cmd_parse_list(adata, s);
×
687
  adata->cmdresult = NULL;
×
688
  /* noselect is for a gmail quirk */
689
  if (!list.name || list.noselect)
×
690
    return;
691

692
  mutt_debug(LL_DEBUG3, "Subscribing to %s\n", list.name);
×
693

694
  struct Buffer *buf = buf_pool_get();
×
695
  struct Buffer *err = buf_pool_get();
×
696
  struct Url url = { 0 };
×
697

698
  account_to_url(&adata->conn->account, &url);
×
699
  url.path = list.name;
×
700

701
  const char *const c_imap_user = cs_subset_string(NeoMutt->sub, "imap_user");
×
702
  if (mutt_str_equal(url.user, c_imap_user))
×
703
    url.user = NULL;
×
704
  url_tobuffer(&url, buf, U_NO_FLAGS);
×
705

706
  if (!mailbox_add_simple(buf_string(buf), err))
×
707
    mutt_debug(LL_DEBUG1, "Error adding subscribed mailbox: %s\n", buf_string(err));
×
708

709
  buf_pool_release(&buf);
×
710
  buf_pool_release(&err);
×
711
}
712

713
/**
714
 * cmd_parse_myrights - Set rights bits according to MYRIGHTS response
715
 * @param adata Imap Account data
716
 * @param s     Command string with rights info
717
 */
718
static void cmd_parse_myrights(struct ImapAccountData *adata, const char *s)
×
719
{
720
  mutt_debug(LL_DEBUG2, "Handling MYRIGHTS\n");
×
721

722
  s = imap_next_word((char *) s);
×
723
  s = imap_next_word((char *) s);
×
724

725
  /* zero out current rights set */
726
  adata->mailbox->rights = 0;
×
727

728
  while (*s && !mutt_isspace(*s))
×
729
  {
730
    switch (*s)
×
731
    {
732
      case 'a':
×
733
        adata->mailbox->rights |= MUTT_ACL_ADMIN;
×
734
        break;
×
735
      case 'e':
×
736
        adata->mailbox->rights |= MUTT_ACL_EXPUNGE;
×
737
        break;
×
738
      case 'i':
×
739
        adata->mailbox->rights |= MUTT_ACL_INSERT;
×
740
        break;
×
741
      case 'k':
×
742
        adata->mailbox->rights |= MUTT_ACL_CREATE;
×
743
        break;
×
744
      case 'l':
×
745
        adata->mailbox->rights |= MUTT_ACL_LOOKUP;
×
746
        break;
×
747
      case 'p':
×
748
        adata->mailbox->rights |= MUTT_ACL_POST;
×
749
        break;
×
750
      case 'r':
×
751
        adata->mailbox->rights |= MUTT_ACL_READ;
×
752
        break;
×
753
      case 's':
×
754
        adata->mailbox->rights |= MUTT_ACL_SEEN;
×
755
        break;
×
756
      case 't':
×
757
        adata->mailbox->rights |= MUTT_ACL_DELETE;
×
758
        break;
×
759
      case 'w':
×
760
        adata->mailbox->rights |= MUTT_ACL_WRITE;
×
761
        break;
×
762
      case 'x':
×
763
        adata->mailbox->rights |= MUTT_ACL_DELMX;
×
764
        break;
×
765

766
      /* obsolete rights */
767
      case 'c':
×
768
        adata->mailbox->rights |= MUTT_ACL_CREATE | MUTT_ACL_DELMX;
×
769
        break;
×
770
      case 'd':
×
771
        adata->mailbox->rights |= MUTT_ACL_DELETE | MUTT_ACL_EXPUNGE;
×
772
        break;
×
773
      default:
×
774
        mutt_debug(LL_DEBUG1, "Unknown right: %c\n", *s);
×
775
    }
776
    s++;
×
777
  }
778
}
×
779

780
/**
781
 * find_mailbox - Find a Mailbox by its name
782
 * @param adata Imap Account data
783
 * @param name  Mailbox to find
784
 * @retval ptr Mailbox
785
 */
786
static struct Mailbox *find_mailbox(struct ImapAccountData *adata, const char *name)
×
787
{
788
  if (!adata || !adata->account || !name)
×
789
    return NULL;
790

791
  struct MailboxNode *np = NULL;
792
  STAILQ_FOREACH(np, &adata->account->mailboxes, entries)
×
793
  {
794
    struct ImapMboxData *mdata = imap_mdata_get(np->mailbox);
×
795
    if (mutt_str_equal(name, mdata->name))
×
796
      return np->mailbox;
×
797
  }
798

799
  return NULL;
800
}
801

802
/**
803
 * cmd_parse_status - Parse status from server
804
 * @param adata Imap Account data
805
 * @param s     Command string with status info
806
 *
807
 * first cut: just do mailbox update. Later we may wish to cache all mailbox
808
 * information, even that not desired by mailbox
809
 */
810
static void cmd_parse_status(struct ImapAccountData *adata, char *s)
×
811
{
812
  unsigned int litlen = 0;
×
813

814
  char *mailbox = imap_next_word(s);
×
815

816
  /* We need a real tokenizer. */
817
  if (imap_get_literal_count(mailbox, &litlen) == 0)
×
818
  {
819
    if (imap_cmd_step(adata) != IMAP_RES_CONTINUE)
×
820
    {
821
      adata->status = IMAP_FATAL;
×
822
      return;
×
823
    }
824

825
    if (strlen(adata->buf) < litlen)
×
826
    {
827
      mutt_debug(LL_DEBUG1, "Error parsing STATUS mailbox\n");
×
828
      return;
×
829
    }
830

831
    mailbox = adata->buf;
832
    s = mailbox + litlen;
×
833
    s[0] = '\0';
×
834
    s++;
×
835
    SKIPWS(s);
×
836
  }
837
  else
838
  {
839
    s = imap_next_word(mailbox);
×
840
    s[-1] = '\0';
×
841
    imap_unmunge_mbox_name(adata->unicode, mailbox);
×
842
  }
843

844
  struct Mailbox *m = find_mailbox(adata, mailbox);
×
845
  struct ImapMboxData *mdata = imap_mdata_get(m);
×
846
  if (!mdata)
×
847
  {
848
    mutt_debug(LL_DEBUG3, "Received status for an unexpected mailbox: %s\n", mailbox);
×
849
    return;
×
850
  }
851
  uint32_t olduv = mdata->uidvalidity;
×
852
  unsigned int oldun = mdata->uid_next;
×
853

854
  if (*s++ != '(')
×
855
  {
856
    mutt_debug(LL_DEBUG1, "Error parsing STATUS\n");
×
857
    return;
×
858
  }
859
  while ((s[0] != '\0') && (s[0] != ')'))
×
860
  {
861
    char *value = imap_next_word(s);
×
862

863
    errno = 0;
×
864
    const unsigned long ulcount = strtoul(value, &value, 10);
×
865
    const bool truncated = ((errno == ERANGE) && (ulcount == ULONG_MAX)) ||
×
866
                           ((unsigned int) ulcount != ulcount);
867
    const unsigned int count = (unsigned int) ulcount;
×
868

869
    // we accept truncating a larger value only for UIDVALIDITY, to accommodate
870
    // IMAP servers that use 64-bits for it. This seems to be what Thunderbird
871
    // is also doing, see #3830
872
    if (mutt_str_startswith(s, "UIDVALIDITY"))
×
873
    {
874
      if (truncated)
×
875
      {
876
        mutt_debug(LL_DEBUG1,
×
877
                   "UIDVALIDITY [%lu] exceeds 32 bits, "
878
                   "truncated to [%u]\n",
879
                   ulcount, count);
880
      }
881
      mdata->uidvalidity = count;
×
882
    }
883
    else
884
    {
885
      if (truncated)
×
886
      {
887
        mutt_debug(LL_DEBUG1, "Number in [%s] exceeds 32 bits\n", s);
×
888
        return;
×
889
      }
890
      else
891
      {
892
        if (mutt_str_startswith(s, "MESSAGES"))
×
893
          mdata->messages = count;
×
894
        else if (mutt_str_startswith(s, "RECENT"))
×
895
          mdata->recent = count;
×
896
        else if (mutt_str_startswith(s, "UIDNEXT"))
×
897
          mdata->uid_next = count;
×
898
        else if (mutt_str_startswith(s, "UNSEEN"))
×
899
          mdata->unseen = count;
×
900
      }
901
    }
902

903
    s = value;
×
904
    if ((s[0] != '\0') && (*s != ')'))
×
905
      s = imap_next_word(s);
×
906
  }
907
  mutt_debug(LL_DEBUG3, "%s (UIDVALIDITY: %u, UIDNEXT: %u) %d messages, %d recent, %d unseen\n",
×
908
             mdata->name, mdata->uidvalidity, mdata->uid_next, mdata->messages,
909
             mdata->recent, mdata->unseen);
910

911
  mutt_debug(LL_DEBUG3, "Running default STATUS handler\n");
×
912

913
  mutt_debug(LL_DEBUG3, "Found %s in mailbox list (OV: %u ON: %u U: %d)\n",
×
914
             mailbox, olduv, oldun, mdata->unseen);
915

916
  bool new_mail = false;
917
  const bool c_mail_check_recent = cs_subset_bool(NeoMutt->sub, "mail_check_recent");
×
918
  if (c_mail_check_recent)
×
919
  {
920
    if ((olduv != 0) && (olduv == mdata->uidvalidity))
×
921
    {
922
      if (oldun < mdata->uid_next)
×
923
        new_mail = (mdata->unseen > 0);
×
924
    }
925
    else if ((olduv == 0) && (oldun == 0))
×
926
    {
927
      /* first check per session, use recent. might need a flag for this. */
928
      new_mail = (mdata->recent > 0);
×
929
    }
930
    else
931
    {
932
      new_mail = (mdata->unseen > 0);
×
933
    }
934
  }
935
  else
936
  {
937
    new_mail = (mdata->unseen > 0);
×
938
  }
939

940
  m->has_new = new_mail;
×
941
  m->msg_count = mdata->messages;
×
942
  m->msg_unread = mdata->unseen;
×
943

944
  // force back to keep detecting new mail until the mailbox is opened
945
  if (m->has_new)
×
946
    mdata->uid_next = oldun;
×
947

948
  struct EventMailbox ev_m = { m };
×
949
  notify_send(m->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m);
×
950
}
951

952
/**
953
 * cmd_parse_enabled - Record what the server has enabled
954
 * @param adata Imap Account data
955
 * @param s     Command string containing acceptable encodings
956
 */
957
static void cmd_parse_enabled(struct ImapAccountData *adata, const char *s)
×
958
{
959
  mutt_debug(LL_DEBUG2, "Handling ENABLED\n");
×
960

961
  while ((s = imap_next_word((char *) s)) && (*s != '\0'))
×
962
  {
963
    if (mutt_istr_startswith(s, "UTF8=ACCEPT") || mutt_istr_startswith(s, "UTF8=ONLY"))
×
964
    {
965
      adata->unicode = true;
×
966
    }
967
    if (mutt_istr_startswith(s, "QRESYNC"))
×
968
      adata->qresync = true;
×
969
  }
970
}
×
971

972
/**
973
 * cmd_parse_exists - Parse EXISTS message from serer
974
 * @param adata  Imap Account data
975
 * @param pn     String containing the total number of messages for the selected mailbox
976
 */
977
static void cmd_parse_exists(struct ImapAccountData *adata, const char *pn)
×
978
{
979
  unsigned int count = 0;
×
980
  mutt_debug(LL_DEBUG2, "Handling EXISTS\n");
×
981

982
  if (!mutt_str_atoui(pn, &count))
×
983
  {
984
    mutt_debug(LL_DEBUG1, "Malformed EXISTS: '%s'\n", pn);
×
985
    return;
×
986
  }
987

988
  struct ImapMboxData *mdata = adata->mailbox->mdata;
×
989

990
  /* new mail arrived */
991
  if (count < imap_msn_highest(&mdata->msn))
×
992
  {
993
    /* Notes 6.0.3 has a tendency to report fewer messages exist than
994
     * it should. */
995
    mutt_debug(LL_DEBUG1, "Message count is out of sync\n");
×
996
  }
997
  else if (count == imap_msn_highest(&mdata->msn))
×
998
  {
999
    /* at least the InterChange server sends EXISTS messages freely,
1000
     * even when there is no new mail */
1001
    mutt_debug(LL_DEBUG3, "superfluous EXISTS message\n");
×
1002
  }
1003
  else
1004
  {
1005
    mutt_debug(LL_DEBUG2, "New mail in %s - %d messages total\n", mdata->name, count);
×
1006
    mdata->reopen |= IMAP_NEWMAIL_PENDING;
×
1007
    mdata->new_mail_count = count;
×
1008
  }
1009
}
1010

1011
/**
1012
 * cmd_handle_untagged - Fallback parser for otherwise unhandled messages
1013
 * @param adata Imap Account data
1014
 * @retval  0 Success
1015
 * @retval -1 Failure
1016
 */
1017
static int cmd_handle_untagged(struct ImapAccountData *adata)
×
1018
{
1019
  char *s = imap_next_word(adata->buf);
×
1020
  char *pn = imap_next_word(s);
×
1021

1022
  const bool c_imap_server_noise = cs_subset_bool(NeoMutt->sub, "imap_server_noise");
×
1023
  if ((adata->state >= IMAP_SELECTED) && mutt_isdigit(*s))
×
1024
  {
1025
    /* pn vs. s: need initial seqno */
1026
    pn = s;
1027
    s = imap_next_word(s);
×
1028

1029
    /* EXISTS, EXPUNGE, FETCH are always related to the SELECTED mailbox */
1030
    if (mutt_istr_startswith(s, "EXISTS"))
×
1031
      cmd_parse_exists(adata, pn);
×
1032
    else if (mutt_istr_startswith(s, "EXPUNGE"))
×
1033
      cmd_parse_expunge(adata, pn);
×
1034
    else if (mutt_istr_startswith(s, "FETCH"))
×
1035
      cmd_parse_fetch(adata, pn);
×
1036
  }
1037
  else if ((adata->state >= IMAP_SELECTED) && mutt_istr_startswith(s, "VANISHED"))
×
1038
  {
1039
    cmd_parse_vanished(adata, pn);
×
1040
  }
1041
  else if (mutt_istr_startswith(s, "CAPABILITY"))
×
1042
  {
1043
    cmd_parse_capability(adata, s);
×
1044
  }
1045
  else if (mutt_istr_startswith(s, "OK [CAPABILITY"))
×
1046
  {
1047
    cmd_parse_capability(adata, pn);
×
1048
  }
1049
  else if (mutt_istr_startswith(pn, "OK [CAPABILITY"))
×
1050
  {
1051
    cmd_parse_capability(adata, imap_next_word(pn));
×
1052
  }
1053
  else if (mutt_istr_startswith(s, "LIST"))
×
1054
  {
1055
    cmd_parse_list(adata, s);
×
1056
  }
1057
  else if (mutt_istr_startswith(s, "LSUB"))
×
1058
  {
1059
    cmd_parse_lsub(adata, s);
×
1060
  }
1061
  else if (mutt_istr_startswith(s, "MYRIGHTS"))
×
1062
  {
1063
    cmd_parse_myrights(adata, s);
×
1064
  }
1065
  else if (mutt_istr_startswith(s, "SEARCH"))
×
1066
  {
1067
    cmd_parse_search(adata, s);
×
1068
  }
1069
  else if (mutt_istr_startswith(s, "STATUS"))
×
1070
  {
1071
    cmd_parse_status(adata, s);
×
1072
  }
1073
  else if (mutt_istr_startswith(s, "ENABLED"))
×
1074
  {
1075
    cmd_parse_enabled(adata, s);
×
1076
  }
1077
  else if (mutt_istr_startswith(s, "BYE"))
×
1078
  {
1079
    mutt_debug(LL_DEBUG2, "Handling BYE\n");
×
1080

1081
    /* check if we're logging out */
1082
    if (adata->status == IMAP_BYE)
×
1083
      return 0;
1084

1085
    /* server shut down our connection */
1086
    s += 3;
×
1087
    SKIPWS(s);
×
1088
    mutt_error("%s", s);
×
1089
    cmd_handle_fatal(adata);
×
1090

1091
    return -1;
×
1092
  }
1093
  else if (c_imap_server_noise && mutt_istr_startswith(s, "NO"))
×
1094
  {
1095
    mutt_debug(LL_DEBUG2, "Handling untagged NO\n");
×
1096

1097
    /* Display the warning message from the server */
1098
    mutt_error("%s", s + 2);
×
1099
  }
1100

1101
  return 0;
1102
}
1103

1104
/**
1105
 * imap_cmd_start - Given an IMAP command, send it to the server
1106
 * @param adata Imap Account data
1107
 * @param cmdstr Command string to send
1108
 * @retval  0 Success
1109
 * @retval <0 Failure, e.g. #IMAP_RES_BAD
1110
 *
1111
 * If cmdstr is NULL, sends queued commands.
1112
 */
1113
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
×
1114
{
1115
  return cmd_start(adata, cmdstr, IMAP_CMD_NO_FLAGS);
×
1116
}
1117

1118
/**
1119
 * imap_cmd_step - Reads server responses from an IMAP command
1120
 * @param adata Imap Account data
1121
 * @retval  0 Success
1122
 * @retval <0 Failure, e.g. #IMAP_RES_BAD
1123
 *
1124
 * detects tagged completion response, handles untagged messages, can read
1125
 * arbitrarily large strings (using malloc, so don't make it _too_ large!).
1126
 */
1127
int imap_cmd_step(struct ImapAccountData *adata)
×
1128
{
1129
  if (!adata)
×
1130
    return -1;
1131

1132
  size_t len = 0;
1133
  int c;
1134
  int rc;
1135
  int stillrunning = 0;
1136
  struct ImapCommand *cmd = NULL;
1137

1138
  if (adata->status == IMAP_FATAL)
×
1139
  {
1140
    cmd_handle_fatal(adata);
×
1141
    return IMAP_RES_BAD;
×
1142
  }
1143

1144
  /* read into buffer, expanding buffer as necessary until we have a full
1145
   * line */
1146
  do
1147
  {
1148
    if (len == adata->blen)
×
1149
    {
1150
      MUTT_MEM_REALLOC(&adata->buf, adata->blen + IMAP_CMD_BUFSIZE, char);
×
1151
      adata->blen = adata->blen + IMAP_CMD_BUFSIZE;
×
1152
      mutt_debug(LL_DEBUG3, "grew buffer to %zu bytes\n", adata->blen);
×
1153
    }
1154

1155
    /* back up over '\0' */
1156
    if (len)
×
1157
      len--;
×
1158
    c = mutt_socket_readln_d(adata->buf + len, adata->blen - len, adata->conn, MUTT_SOCK_LOG_FULL);
×
1159
    if (c <= 0)
×
1160
    {
1161
      mutt_debug(LL_DEBUG1, "Error reading server response\n");
×
1162
      cmd_handle_fatal(adata);
×
1163
      return IMAP_RES_BAD;
×
1164
    }
1165

1166
    len += c;
×
1167
  }
1168
  /* if we've read all the way to the end of the buffer, we haven't read a
1169
   * full line (mutt_socket_readln strips the \r, so we always have at least
1170
   * one character free when we've read a full line) */
1171
  while (len == adata->blen);
×
1172

1173
  /* don't let one large string make cmd->buf hog memory forever */
1174
  if ((adata->blen > IMAP_CMD_BUFSIZE) && (len <= IMAP_CMD_BUFSIZE))
×
1175
  {
1176
    MUTT_MEM_REALLOC(&adata->buf, IMAP_CMD_BUFSIZE, char);
×
1177
    adata->blen = IMAP_CMD_BUFSIZE;
×
1178
    mutt_debug(LL_DEBUG3, "shrank buffer to %zu bytes\n", adata->blen);
×
1179
  }
1180

1181
  adata->lastread = mutt_date_now();
×
1182

1183
  /* handle untagged messages. The caller still gets its shot afterwards. */
1184
  if ((mutt_str_startswith(adata->buf, "* ") ||
×
1185
       mutt_str_startswith(imap_next_word(adata->buf), "OK [")) &&
×
1186
      cmd_handle_untagged(adata))
×
1187
  {
1188
    return IMAP_RES_BAD;
1189
  }
1190

1191
  /* server demands a continuation response from us */
1192
  if (adata->buf[0] == '+')
×
1193
    return IMAP_RES_RESPOND;
1194

1195
  /* Look for tagged command completions.
1196
   *
1197
   * Some response handlers can end up recursively calling
1198
   * imap_cmd_step() and end up handling all tagged command
1199
   * completions.
1200
   * (e.g. FETCH->set_flag->set_header_color->~h pattern match.)
1201
   *
1202
   * Other callers don't even create an adata->cmds entry.
1203
   *
1204
   * For both these cases, we default to returning OK */
1205
  rc = IMAP_RES_OK;
1206
  c = adata->lastcmd;
×
1207
  do
1208
  {
1209
    cmd = &adata->cmds[c];
×
1210
    if (cmd->state == IMAP_RES_NEW)
×
1211
    {
1212
      if (mutt_str_startswith(adata->buf, cmd->seq))
×
1213
      {
1214
        if (!stillrunning)
×
1215
        {
1216
          /* first command in queue has finished - move queue pointer up */
1217
          adata->lastcmd = (adata->lastcmd + 1) % adata->cmdslots;
×
1218
        }
1219
        cmd->state = cmd_status(adata->buf);
×
1220
        rc = cmd->state;
1221
        if (cmd->state == IMAP_RES_NO || cmd->state == IMAP_RES_BAD)
×
1222
        {
1223
          mutt_message(_("IMAP command failed: %s"), adata->buf);
×
1224
        }
1225
      }
1226
      else
1227
      {
1228
        stillrunning++;
×
1229
      }
1230
    }
1231

1232
    c = (c + 1) % adata->cmdslots;
×
1233
  } while (c != adata->nextcmd);
×
1234

1235
  if (stillrunning)
×
1236
  {
1237
    rc = IMAP_RES_CONTINUE;
1238
  }
1239
  else
1240
  {
1241
    mutt_debug(LL_DEBUG3, "IMAP queue drained\n");
×
1242
    imap_cmd_finish(adata);
×
1243
  }
1244

1245
  return rc;
1246
}
1247

1248
/**
1249
 * imap_code - Was the command successful
1250
 * @param s IMAP command status
1251
 * @retval 1 Command result was OK
1252
 * @retval 0 NO or BAD
1253
 */
1254
bool imap_code(const char *s)
×
1255
{
1256
  return cmd_status(s) == IMAP_RES_OK;
×
1257
}
1258

1259
/**
1260
 * imap_cmd_trailer - Extra information after tagged command response if any
1261
 * @param adata Imap Account data
1262
 * @retval ptr Extra command information (pointer into adata->buf)
1263
 * @retval ""  Error (static string)
1264
 */
1265
const char *imap_cmd_trailer(struct ImapAccountData *adata)
×
1266
{
1267
  static const char *notrailer = "";
1268
  const char *s = adata->buf;
×
1269

1270
  if (!s)
×
1271
  {
1272
    mutt_debug(LL_DEBUG2, "not a tagged response\n");
×
1273
    return notrailer;
×
1274
  }
1275

1276
  s = imap_next_word((char *) s);
×
1277
  if (!s || (!mutt_istr_startswith(s, "OK") && !mutt_istr_startswith(s, "NO") &&
×
1278
             !mutt_istr_startswith(s, "BAD")))
×
1279
  {
1280
    mutt_debug(LL_DEBUG2, "not a command completion: %s\n", adata->buf);
×
1281
    return notrailer;
×
1282
  }
1283

1284
  s = imap_next_word((char *) s);
×
1285
  if (!s)
×
1286
    return notrailer;
×
1287

1288
  return s;
1289
}
1290

1291
/**
1292
 * imap_exec - Execute a command and wait for the response from the server
1293
 * @param adata Imap Account data
1294
 * @param cmdstr Command to execute
1295
 * @param flags  Flags, see #ImapCmdFlags
1296
 * @retval #IMAP_EXEC_SUCCESS Command successful or queued
1297
 * @retval #IMAP_EXEC_ERROR   Command returned an error
1298
 * @retval #IMAP_EXEC_FATAL   Imap connection failure
1299
 *
1300
 * Also, handle untagged responses.
1301
 */
1302
int imap_exec(struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
×
1303
{
1304
  int rc;
1305

1306
  if (flags & IMAP_CMD_SINGLE)
×
1307
  {
1308
    // Process any existing commands
1309
    if (adata->nextcmd != adata->lastcmd)
×
1310
      imap_exec(adata, NULL, IMAP_CMD_POLL);
×
1311
  }
1312

1313
  rc = cmd_start(adata, cmdstr, flags);
×
1314
  if (rc < 0)
×
1315
  {
1316
    cmd_handle_fatal(adata);
×
1317
    return IMAP_EXEC_FATAL;
×
1318
  }
1319

1320
  if (flags & IMAP_CMD_QUEUE)
×
1321
    return IMAP_EXEC_SUCCESS;
1322

1323
  const short c_imap_poll_timeout = cs_subset_number(NeoMutt->sub, "imap_poll_timeout");
×
1324
  if ((flags & IMAP_CMD_POLL) && (c_imap_poll_timeout > 0) &&
×
1325
      ((mutt_socket_poll(adata->conn, c_imap_poll_timeout)) == 0))
×
1326
  {
1327
    mutt_error(_("Connection to %s timed out"), adata->conn->account.host);
×
1328
    cmd_handle_fatal(adata);
×
1329
    return IMAP_EXEC_FATAL;
×
1330
  }
1331

1332
  /* Allow interruptions, particularly useful if there are network problems. */
1333
  mutt_sig_allow_interrupt(true);
×
1334
  do
1335
  {
1336
    rc = imap_cmd_step(adata);
×
1337
    // The queue is empty, so the single command has been processed
1338
    if ((flags & IMAP_CMD_SINGLE) && (adata->nextcmd == adata->lastcmd))
×
1339
      break;
1340
  } while (rc == IMAP_RES_CONTINUE);
×
1341
  mutt_sig_allow_interrupt(false);
×
1342

1343
  if (rc == IMAP_RES_NO)
×
1344
    return IMAP_EXEC_ERROR;
1345
  if (rc != IMAP_RES_OK)
×
1346
  {
1347
    if (adata->status != IMAP_FATAL)
×
1348
      return IMAP_EXEC_ERROR;
1349

1350
    mutt_debug(LL_DEBUG1, "command failed: %s\n", adata->buf);
×
1351
    return IMAP_EXEC_FATAL;
×
1352
  }
1353

1354
  return IMAP_EXEC_SUCCESS;
1355
}
1356

1357
/**
1358
 * imap_cmd_finish - Attempt to perform cleanup
1359
 * @param adata Imap Account data
1360
 *
1361
 * If a reopen is allowed, it attempts to perform cleanup (eg fetch new mail if
1362
 * detected, do expunge). Called automatically by imap_cmd_step(), but may be
1363
 * called at any time.
1364
 *
1365
 * mdata->check_status is set and will be used later by imap_check_mailbox().
1366
 */
1367
void imap_cmd_finish(struct ImapAccountData *adata)
×
1368
{
1369
  if (!adata)
×
1370
    return;
1371

1372
  if (adata->status == IMAP_FATAL)
×
1373
  {
1374
    adata->closing = false;
×
1375
    cmd_handle_fatal(adata);
×
1376
    return;
×
1377
  }
1378

1379
  if (!(adata->state >= IMAP_SELECTED) || (adata->mailbox && adata->closing))
×
1380
  {
1381
    adata->closing = false;
×
1382
    return;
×
1383
  }
1384

1385
  adata->closing = false;
×
1386

1387
  struct ImapMboxData *mdata = imap_mdata_get(adata->mailbox);
×
1388

1389
  if (mdata && mdata->reopen & IMAP_REOPEN_ALLOW)
×
1390
  {
1391
    // First remove expunged emails from the msn_index
1392
    if (mdata->reopen & IMAP_EXPUNGE_PENDING)
×
1393
    {
1394
      mutt_debug(LL_DEBUG2, "Expunging mailbox\n");
×
1395
      imap_expunge_mailbox(adata->mailbox, true);
×
1396
      /* Detect whether we've gotten unexpected EXPUNGE messages */
1397
      if (!(mdata->reopen & IMAP_EXPUNGE_EXPECTED))
×
1398
        mdata->check_status |= IMAP_EXPUNGE_PENDING;
×
1399
      mdata->reopen &= ~(IMAP_EXPUNGE_PENDING | IMAP_EXPUNGE_EXPECTED);
×
1400
    }
1401

1402
    // Then add new emails to it
1403
    if (mdata->reopen & IMAP_NEWMAIL_PENDING)
×
1404
    {
1405
      const size_t max_msn = imap_msn_highest(&mdata->msn);
×
1406
      if (mdata->new_mail_count > max_msn)
×
1407
      {
1408
        if (!(mdata->reopen & IMAP_EXPUNGE_PENDING))
×
1409
          mdata->check_status |= IMAP_NEWMAIL_PENDING;
×
1410

1411
        mutt_debug(LL_DEBUG2, "Fetching new mails from %zd to %u\n",
×
1412
                   max_msn + 1, mdata->new_mail_count);
1413
        imap_read_headers(adata->mailbox, max_msn + 1, mdata->new_mail_count, false);
×
1414
      }
1415
    }
1416

1417
    // And to finish inform about MUTT_REOPEN if needed
1418
    if (mdata->reopen & IMAP_EXPUNGE_PENDING && !(mdata->reopen & IMAP_EXPUNGE_EXPECTED))
×
1419
      mdata->check_status |= IMAP_EXPUNGE_PENDING;
×
1420

1421
    if (mdata->reopen & IMAP_EXPUNGE_PENDING)
×
1422
      mdata->reopen &= ~(IMAP_EXPUNGE_PENDING | IMAP_EXPUNGE_EXPECTED);
×
1423
  }
1424

1425
  adata->status = 0;
×
1426
}
1427

1428
/**
1429
 * imap_cmd_idle - Enter the IDLE state
1430
 * @param adata Imap Account data
1431
 * @retval  0 Success
1432
 * @retval <0 Failure, e.g. #IMAP_RES_BAD
1433
 */
1434
int imap_cmd_idle(struct ImapAccountData *adata)
×
1435
{
1436
  int rc;
1437

1438
  if (cmd_start(adata, "IDLE", IMAP_CMD_POLL) < 0)
×
1439
  {
1440
    cmd_handle_fatal(adata);
×
1441
    return -1;
×
1442
  }
1443

1444
  const short c_imap_poll_timeout = cs_subset_number(NeoMutt->sub, "imap_poll_timeout");
×
1445
  if ((c_imap_poll_timeout > 0) &&
×
1446
      ((mutt_socket_poll(adata->conn, c_imap_poll_timeout)) == 0))
×
1447
  {
1448
    mutt_error(_("Connection to %s timed out"), adata->conn->account.host);
×
1449
    cmd_handle_fatal(adata);
×
1450
    return -1;
×
1451
  }
1452

1453
  do
1454
  {
1455
    rc = imap_cmd_step(adata);
×
1456
  } while (rc == IMAP_RES_CONTINUE);
×
1457

1458
  if (rc == IMAP_RES_RESPOND)
×
1459
  {
1460
    /* successfully entered IDLE state */
1461
    adata->state = IMAP_IDLE;
×
1462
    /* queue automatic exit when next command is issued */
1463
    buf_addstr(&adata->cmdbuf, "DONE\r\n");
×
1464
    rc = IMAP_RES_OK;
1465
  }
1466
  if (rc != IMAP_RES_OK)
×
1467
  {
1468
    mutt_debug(LL_DEBUG1, "error starting IDLE\n");
×
1469
    return -1;
×
1470
  }
1471

1472
  return 0;
1473
}
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