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) 2018 Richard Russon <rich@flatcap.org>
9 *
10 * @copyright
11 * This program is free software: you can redistribute it and/or modify it under
12 * the terms of the GNU General Public License as published by the Free Software
13 * Foundation, either version 2 of the License, or (at your option) any later
14 * version.
15 *
16 * This program is distributed in the hope that it will be useful, but WITHOUT
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19 * details.
20 *
21 * You should have received a copy of the GNU General Public License along with
22 * this program. If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 /**
26 * @page imap_message IMAP messages
27 *
28 * Manage IMAP messages
29 */
30
31 #include "config.h"
32 #include <assert.h>
33 #include <ctype.h>
34 #include <limits.h>
35 #include <stdbool.h>
36 #include <stdint.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include "private.h"
41 #include "mutt/lib.h"
42 #include "config/lib.h"
43 #include "email/lib.h"
44 #include "core/lib.h"
45 #include "conn/lib.h"
46 #include "gui/lib.h"
47 #include "mutt.h"
48 #include "message.h"
49 #include "lib.h"
50 #include "bcache/lib.h"
51 #include "progress/lib.h"
52 #include "question/lib.h"
53 #include "adata.h"
54 #include "commands.h"
55 #include "edata.h"
56 #include "mdata.h"
57 #include "msn.h"
58 #include "mutt_globals.h"
59 #include "mutt_logging.h"
60 #include "mutt_socket.h"
61 #include "muttlib.h"
62 #include "mx.h"
63 #include "protos.h"
64 #ifdef ENABLE_NLS
65 #include <libintl.h>
66 #endif
67 #ifdef USE_HCACHE
68 #include "hcache/lib.h"
69 #endif
70
71 struct BodyCache;
72
73 /**
74 * msg_cache_open - Open a message cache
75 * @param m Selected Imap Mailbox
76 * @retval ptr Success, using existing cache (or opened new cache)
77 * @retval NULL Failure
78 */
msg_cache_open(struct Mailbox * m)79 static struct BodyCache *msg_cache_open(struct Mailbox *m)
80 {
81 struct ImapAccountData *adata = imap_adata_get(m);
82 struct ImapMboxData *mdata = imap_mdata_get(m);
83
84 if (!adata || (adata->mailbox != m))
85 return NULL;
86
87 if (mdata->bcache)
88 return mdata->bcache;
89
90 struct Buffer *mailbox = mutt_buffer_pool_get();
91 imap_cachepath(adata->delim, mdata->name, mailbox);
92
93 struct BodyCache *bc =
94 mutt_bcache_open(&adata->conn->account, mutt_buffer_string(mailbox));
95 mutt_buffer_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 */
msg_cache_get(struct Mailbox * m,struct Email * e)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 = msg_cache_open(m);
116 char id[64];
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 */
msg_cache_put(struct Mailbox * m,struct Email * e)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 = msg_cache_open(m);
137 char id[64];
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 */
msg_cache_commit(struct Mailbox * m,struct Email * e)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 = msg_cache_open(m);
158 char id[64];
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 * msg_cache_clean_cb - Delete an entry from the message cache - Implements ::bcache_list_t - @ingroup bcache_list_api
166 * @retval 0 Always
167 */
msg_cache_clean_cb(const char * id,struct BodyCache * bcache,void * data)168 static int msg_cache_clean_cb(const char *id, struct BodyCache *bcache, void *data)
169 {
170 uint32_t uv;
171 unsigned int uid;
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 */
msg_parse_flags(struct ImapHeader * h,char * s)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 && !IS_SPACE(*s) && (*s != ')'))
259 s++;
260
261 ctmp = *s;
262 *s = '\0';
263
264 /* store other system flags as well (mainly \\Draft) */
265 if (is_system_keyword)
266 mutt_str_append_item(&edata->flags_system, flag_word, ' ');
267 /* store custom flags as well */
268 else
269 mutt_str_append_item(&edata->flags_remote, flag_word, ' ');
270
271 *s = ctmp;
272 }
273 SKIPWS(s);
274 }
275
276 /* wrap up, or note bad flags response */
277 if (*s == ')')
278 s++;
279 else
280 {
281 mutt_debug(LL_DEBUG1, "Unterminated FLAGS response: %s\n", s);
282 return NULL;
283 }
284
285 return s;
286 }
287
288 /**
289 * msg_parse_fetch - Handle headers returned from header fetch
290 * @param h IMAP Header
291 * @param s Command string
292 * @retval 0 Success
293 * @retval -1 String is corrupted
294 * @retval -2 Fetch contains a body or header lines that still need to be parsed
295 */
msg_parse_fetch(struct ImapHeader * h,char * s)296 static int msg_parse_fetch(struct ImapHeader *h, char *s)
297 {
298 if (!s)
299 return -1;
300
301 char tmp[128];
302 char *ptmp = NULL;
303 size_t plen = 0;
304
305 while (*s)
306 {
307 SKIPWS(s);
308
309 if (mutt_istr_startswith(s, "FLAGS"))
310 {
311 s = msg_parse_flags(h, s);
312 if (!s)
313 return -1;
314 }
315 else if ((plen = mutt_istr_startswith(s, "UID")))
316 {
317 s += plen;
318 SKIPWS(s);
319 if (mutt_str_atoui(s, &h->edata->uid) < 0)
320 return -1;
321
322 s = imap_next_word(s);
323 }
324 else if ((plen = mutt_istr_startswith(s, "INTERNALDATE")))
325 {
326 s += plen;
327 SKIPWS(s);
328 if (*s != '\"')
329 {
330 mutt_debug(LL_DEBUG1, "bogus INTERNALDATE entry: %s\n", s);
331 return -1;
332 }
333 s++;
334 ptmp = tmp;
335 while (*s && (*s != '\"') && (ptmp != (tmp + sizeof(tmp) - 1)))
336 *ptmp++ = *s++;
337 if (*s != '\"')
338 return -1;
339 s++; /* skip past the trailing " */
340 *ptmp = '\0';
341 h->received = mutt_date_parse_imap(tmp);
342 }
343 else if ((plen = mutt_istr_startswith(s, "RFC822.SIZE")))
344 {
345 s += plen;
346 SKIPWS(s);
347 ptmp = tmp;
348 while (isdigit((unsigned char) *s) && (ptmp != (tmp + sizeof(tmp) - 1)))
349 *ptmp++ = *s++;
350 *ptmp = '\0';
351 if (mutt_str_atol(tmp, &h->content_length) < 0)
352 return -1;
353 }
354 else if (mutt_istr_startswith(s, "BODY") ||
355 mutt_istr_startswith(s, "RFC822.HEADER"))
356 {
357 /* handle above, in msg_fetch_header */
358 return -2;
359 }
360 else if ((plen = mutt_istr_startswith(s, "MODSEQ")))
361 {
362 s += plen;
363 SKIPWS(s);
364 if (*s != '(')
365 {
366 mutt_debug(LL_DEBUG1, "bogus MODSEQ response: %s\n", s);
367 return -1;
368 }
369 s++;
370 while (*s && (*s != ')'))
371 s++;
372 if (*s == ')')
373 s++;
374 else
375 {
376 mutt_debug(LL_DEBUG1, "Unterminated MODSEQ response: %s\n", s);
377 return -1;
378 }
379 }
380 else if (*s == ')')
381 s++; /* end of request */
382 else if (*s)
383 {
384 /* got something i don't understand */
385 imap_error("msg_parse_fetch", s);
386 return -1;
387 }
388 }
389
390 return 0;
391 }
392
393 /**
394 * msg_fetch_header - Import IMAP FETCH response into an ImapHeader
395 * @param m Mailbox
396 * @param ih ImapHeader
397 * @param buf Server string containing FETCH response
398 * @param fp Connection to server
399 * @retval 0 Success
400 * @retval -1 String is not a fetch response
401 * @retval -2 String is a corrupt fetch response
402 *
403 * Expects string beginning with * n FETCH.
404 */
msg_fetch_header(struct Mailbox * m,struct ImapHeader * ih,char * buf,FILE * fp)405 static int msg_fetch_header(struct Mailbox *m, struct ImapHeader *ih, char *buf, FILE *fp)
406 {
407 int rc = -1; /* default now is that string isn't FETCH response */
408
409 struct ImapAccountData *adata = imap_adata_get(m);
410
411 if (buf[0] != '*')
412 return rc;
413
414 /* skip to message number */
415 buf = imap_next_word(buf);
416 if (mutt_str_atoui(buf, &ih->edata->msn) < 0)
417 return rc;
418
419 /* find FETCH tag */
420 buf = imap_next_word(buf);
421 if (!mutt_istr_startswith(buf, "FETCH"))
422 return rc;
423
424 rc = -2; /* we've got a FETCH response, for better or worse */
425 buf = strchr(buf, '(');
426 if (!buf)
427 return rc;
428 buf++;
429
430 /* FIXME: current implementation - call msg_parse_fetch - if it returns -2,
431 * read header lines and call it again. Silly. */
432 int parse_rc = msg_parse_fetch(ih, buf);
433 if (parse_rc == 0)
434 return 0;
435 if ((parse_rc != -2) || !fp)
436 return rc;
437
438 unsigned int bytes = 0;
439 if (imap_get_literal_count(buf, &bytes) == 0)
440 {
441 imap_read_literal(fp, adata, bytes, NULL);
442
443 /* we may have other fields of the FETCH _after_ the literal
444 * (eg Domino puts FLAGS here). Nothing wrong with that, either.
445 * This all has to go - we should accept literals and nonliterals
446 * interchangeably at any time. */
447 if (imap_cmd_step(adata) != IMAP_RES_CONTINUE)
448 return rc;
449
450 if (msg_parse_fetch(ih, adata->buf) == -1)
451 return rc;
452 }
453
454 rc = 0; /* success */
455
456 /* subtract headers from message size - unfortunately only the subset of
457 * headers we've requested. */
458 ih->content_length -= bytes;
459
460 return rc;
461 }
462
463 /**
464 * flush_buffer - Write data to a connection
465 * @param buf Buffer containing data
466 * @param len Length of buffer
467 * @param conn Network connection
468 */
flush_buffer(char * buf,size_t * len,struct Connection * conn)469 static int flush_buffer(char *buf, size_t *len, struct Connection *conn)
470 {
471 buf[*len] = '\0';
472 int rc = mutt_socket_write_n(conn, buf, *len);
473 *len = 0;
474 return rc;
475 }
476
477 /**
478 * query_abort_header_download - Ask the user whether to abort the download
479 * @param adata Imap Account data
480 * @retval true Abort the download
481 *
482 * If the user hits ctrl-c during an initial header download for a mailbox,
483 * prompt whether to completely abort the download and close the mailbox.
484 */
query_abort_header_download(struct ImapAccountData * adata)485 static bool query_abort_header_download(struct ImapAccountData *adata)
486 {
487 bool abort = false;
488
489 mutt_flushinp();
490 /* L10N: This prompt is made if the user hits Ctrl-C when opening an IMAP mailbox */
491 if (mutt_yesorno(_("Abort download and close mailbox?"), MUTT_YES) == MUTT_YES)
492 {
493 abort = true;
494 imap_close_connection(adata);
495 }
496 SigInt = false;
497
498 return abort;
499 }
500
501 /**
502 * imap_alloc_uid_hash - Create a Hash Table for the UIDs
503 * @param adata Imap Account data
504 * @param msn_count Number of MSNs in use
505 *
506 * This function is run after imap_imap_msn_reserve, so we skip the
507 * malicious msn_count size check.
508 */
imap_alloc_uid_hash(struct ImapAccountData * adata,unsigned int msn_count)509 static void imap_alloc_uid_hash(struct ImapAccountData *adata, unsigned int msn_count)
510 {
511 struct ImapMboxData *mdata = adata->mailbox->mdata;
512 if (!mdata->uid_hash)
513 mdata->uid_hash = mutt_hash_int_new(MAX(6 * msn_count / 5, 30), MUTT_HASH_NO_FLAGS);
514 }
515
516 /**
517 * imap_fetch_msn_seqset - Generate a sequence set
518 * @param[in] buf Buffer for the result
519 * @param[in] adata Imap Account data
520 * @param[in] evalhc If true, check the Header Cache
521 * @param[in] msn_begin First Message Sequence Number
522 * @param[in] msn_end Last Message Sequence Number
523 * @param[out] fetch_msn_end Highest Message Sequence Number fetched
524 *
525 * Generates a more complicated sequence set after using the header cache,
526 * in case there are missing MSNs in the middle.
527 */
imap_fetch_msn_seqset(struct Buffer * buf,struct ImapAccountData * adata,bool evalhc,unsigned int msn_begin,unsigned int msn_end,unsigned int * fetch_msn_end)528 static unsigned int imap_fetch_msn_seqset(struct Buffer *buf, struct ImapAccountData *adata,
529 bool evalhc, unsigned int msn_begin,
530 unsigned int msn_end, unsigned int *fetch_msn_end)
531 {
532 struct ImapMboxData *mdata = adata->mailbox->mdata;
533 unsigned int max_headers_per_fetch = UINT_MAX;
534 bool first_chunk = true;
535 int state = 0; /* 1: single msn, 2: range of msn */
536 unsigned int msn;
537 unsigned int range_begin = 0;
538 unsigned int range_end = 0;
539 unsigned int msn_count = 0;
540
541 mutt_buffer_reset(buf);
542 if (msn_end < msn_begin)
543 return 0;
544
545 const long c_imap_fetch_chunk_size =
546 cs_subset_long(NeoMutt->sub, "imap_fetch_chunk_size");
547 if (c_imap_fetch_chunk_size > 0)
548 max_headers_per_fetch = c_imap_fetch_chunk_size;
549
550 if (!evalhc)
551 {
552 if (msn_end - msn_begin + 1 <= max_headers_per_fetch)
553 *fetch_msn_end = msn_end;
554 else
555 *fetch_msn_end = msn_begin + max_headers_per_fetch - 1;
556 mutt_buffer_printf(buf, "%u:%u", msn_begin, *fetch_msn_end);
557 return (*fetch_msn_end - msn_begin + 1);
558 }
559
560 for (msn = msn_begin; msn <= (msn_end + 1); msn++)
561 {
562 if (msn_count < max_headers_per_fetch && msn <= msn_end &&
563 !imap_msn_get(&mdata->msn, msn - 1))
564 {
565 msn_count++;
566
567 switch (state)
568 {
569 case 1: /* single: convert to a range */
570 state = 2;
571 /* fallthrough */
572 case 2: /* extend range ending */
573 range_end = msn;
574 break;
575 default:
576 state = 1;
577 range_begin = msn;
578 break;
579 }
580 }
581 else if (state)
582 {
583 if (first_chunk)
584 first_chunk = false;
585 else
586 mutt_buffer_addch(buf, ',');
587
588 if (state == 1)
589 mutt_buffer_add_printf(buf, "%u", range_begin);
590 else if (state == 2)
591 mutt_buffer_add_printf(buf, "%u:%u", range_begin, range_end);
592 state = 0;
593
594 if ((mutt_buffer_len(buf) > 500) || (msn_count >= max_headers_per_fetch))
595 break;
596 }
597 }
598
599 /* The loop index goes one past to terminate the range if needed. */
600 *fetch_msn_end = msn - 1;
601
602 return msn_count;
603 }
604
605 /**
606 * set_changed_flag - Have the flags of an email changed
607 * @param[in] m Mailbox
608 * @param[in] e Email
609 * @param[in] local_changes Has the local mailbox been changed?
610 * @param[out] server_changes Set to true if the flag has changed
611 * @param[in] flag_name Flag to check, e.g. #MUTT_FLAG
612 * @param[in] old_hd_flag Old header flags
613 * @param[in] new_hd_flag New header flags
614 * @param[in] h_flag Email's value for flag_name
615 *
616 * Sets server_changes to 1 if a change to a flag is made, or in the
617 * case of local_changes, if a change to a flag _would_ have been
618 * made.
619 */
set_changed_flag(struct Mailbox * m,struct Email * e,int local_changes,bool * server_changes,enum MessageType flag_name,bool old_hd_flag,bool new_hd_flag,bool h_flag)620 static void set_changed_flag(struct Mailbox *m, struct Email *e, int local_changes,
621 bool *server_changes, enum MessageType flag_name,
622 bool old_hd_flag, bool new_hd_flag, bool h_flag)
623 {
624 /* If there are local_changes, we only want to note if the server
625 * flags have changed, so we can set a reopen flag in
626 * cmd_parse_fetch(). We don't want to count a local modification
627 * to the header flag as a "change". */
628 if ((old_hd_flag == new_hd_flag) && (local_changes != 0))
629 return;
630
631 if (new_hd_flag == h_flag)
632 return;
633
634 if (server_changes)
635 *server_changes = true;
636
637 /* Local changes have priority */
638 if (local_changes == 0)
639 mutt_set_flag(m, e, flag_name, new_hd_flag);
640 }
641
642 #ifdef USE_HCACHE
643 /**
644 * read_headers_normal_eval_cache - Retrieve data from the header cache
645 * @param adata Imap Account data
646 * @param msn_end Last Message Sequence number
647 * @param uid_next UID of next email
648 * @param store_flag_updates if true, save flags to the header cache
649 * @param eval_condstore if true, use CONDSTORE to fetch flags
650 * @retval 0 Success
651 * @retval -1 Error
652 *
653 * Without CONDSTORE or QRESYNC, we need to query all the current
654 * UIDs and update their flag state and current MSN.
655 *
656 * For CONDSTORE, we still need to grab the existing UIDs and
657 * their MSN. The current flag state will be queried in
658 * read_headers_condstore_qresync_updates().
659 */
read_headers_normal_eval_cache(struct ImapAccountData * adata,unsigned int msn_end,unsigned int uid_next,bool store_flag_updates,bool eval_condstore)660 static int read_headers_normal_eval_cache(struct ImapAccountData *adata,
661 unsigned int msn_end, unsigned int uid_next,
662 bool store_flag_updates, bool eval_condstore)
663 {
664 struct Progress *progress = NULL;
665 char buf[1024];
666 int rc = -1;
667
668 struct Mailbox *m = adata->mailbox;
669 struct ImapMboxData *mdata = imap_mdata_get(m);
670 int idx = m->msg_count;
671
672 if (m->verbose)
673 {
674 /* L10N: Comparing the cached data with the IMAP server's data */
675 progress = progress_new(_("Evaluating cache..."), MUTT_PROGRESS_READ, msn_end);
676 }
677
678 /* If we are using CONDSTORE's "FETCH CHANGEDSINCE", then we keep
679 * the flags in the header cache, and update them further below.
680 * Otherwise, we fetch the current state of the flags here. */
681 snprintf(buf, sizeof(buf), "UID FETCH 1:%u (UID%s)", uid_next - 1,
682 eval_condstore ? "" : " FLAGS");
683
684 imap_cmd_start(adata, buf);
685
686 rc = IMAP_RES_CONTINUE;
687 int mfhrc = 0;
688 struct ImapHeader h;
689 for (int msgno = 1; rc == IMAP_RES_CONTINUE; msgno++)
690 {
691 if (SigInt && query_abort_header_download(adata))
692 goto fail;
693
694 if (m->verbose)
695 progress_update(progress, msgno, -1);
696
697 memset(&h, 0, sizeof(h));
698 h.edata = imap_edata_new();
699 do
700 {
701 rc = imap_cmd_step(adata);
702 if (rc != IMAP_RES_CONTINUE)
703 break;
704
705 mfhrc = msg_fetch_header(m, &h, adata->buf, NULL);
706 if (mfhrc < 0)
707 continue;
708
709 if (!h.edata->uid)
710 {
711 mutt_debug(LL_DEBUG2, "skipping hcache FETCH response for message number %d missing a UID\n",
712 h.edata->msn);
713 continue;
714 }
715
716 if ((h.edata->msn < 1) || (h.edata->msn > msn_end))
717 {
718 mutt_debug(LL_DEBUG1, "skipping hcache FETCH response for unknown message number %d\n",
719 h.edata->msn);
720 continue;
721 }
722
723 if (imap_msn_get(&mdata->msn, h.edata->msn - 1))
724 {
725 mutt_debug(LL_DEBUG2, "skipping hcache FETCH for duplicate message %d\n",
726 h.edata->msn);
727 continue;
728 }
729
730 struct Email *e = imap_hcache_get(mdata, h.edata->uid);
731 m->emails[idx] = e;
732 if (e)
733 {
734 imap_msn_set(&mdata->msn, h.edata->msn - 1, e);
735 mutt_hash_int_insert(mdata->uid_hash, h.edata->uid, e);
736
737 e->index = h.edata->uid;
738 /* messages which have not been expunged are ACTIVE (borrowed from mh
739 * folders) */
740 e->active = true;
741 e->changed = false;
742 if (eval_condstore)
743 {
744 h.edata->read = e->read;
745 h.edata->old = e->old;
746 h.edata->deleted = e->deleted;
747 h.edata->flagged = e->flagged;
748 h.edata->replied = e->replied;
749 }
750 else
751 {
752 e->read = h.edata->read;
753 e->old = h.edata->old;
754 e->deleted = h.edata->deleted;
755 e->flagged = h.edata->flagged;
756 e->replied = h.edata->replied;
757 }
758
759 /* mailbox->emails[msgno]->received is restored from mutt_hcache_restore */
760 e->edata = h.edata;
761 e->edata_free = imap_edata_free;
762
763 /* We take a copy of the tags so we can split the string */
764 char *tags_copy = mutt_str_dup(h.edata->flags_remote);
765 driver_tags_replace(&e->tags, tags_copy);
766 FREE(&tags_copy);
767
768 m->msg_count++;
769 mailbox_size_add(m, e);
770
771 /* If this is the first time we are fetching, we need to
772 * store the current state of flags back into the header cache */
773 if (!eval_condstore && store_flag_updates)
774 imap_hcache_put(mdata, e);
775
776 h.edata = NULL;
777 idx++;
778 }
779 } while (mfhrc == -1);
780
781 imap_edata_free((void **) &h.edata);
782
783 if ((mfhrc < -1) || ((rc != IMAP_RES_CONTINUE) && (rc != IMAP_RES_OK)))
784 goto fail;
785 }
786
787 rc = 0;
788 fail:
789 progress_free(&progress);
790 return rc;
791 }
792
793 /**
794 * read_headers_qresync_eval_cache - Retrieve data from the header cache
795 * @param adata Imap Account data
796 * @param uid_seqset Sequence Set of UIDs
797 * @retval >=0 Success
798 * @retval -1 Error
799 *
800 * For QRESYNC, we grab the UIDs in order by MSN from the header cache.
801 *
802 * In read_headers_condstore_qresync_updates(). We will update change flags
803 * using CHANGEDSINCE and find out what UIDs have been expunged using VANISHED.
804 */
read_headers_qresync_eval_cache(struct ImapAccountData * adata,char * uid_seqset)805 static int read_headers_qresync_eval_cache(struct ImapAccountData *adata, char *uid_seqset)
806 {
807 int rc;
808 unsigned int uid = 0;
809
810 mutt_debug(LL_DEBUG2, "Reading uid seqset from header cache\n");
811 struct Mailbox *m = adata->mailbox;
812 struct ImapMboxData *mdata = adata->mailbox->mdata;
813 unsigned int msn = 1;
814
815 if (m->verbose)
816 mutt_message(_("Evaluating cache..."));
817
818 struct SeqsetIterator *iter = mutt_seqset_iterator_new(uid_seqset);
819 if (!iter)
820 return -1;
821
822 while ((rc = mutt_seqset_iterator_next(iter, &uid)) == 0)
823 {
824 /* The seqset may contain more headers than the fetch request, so
825 * we need to watch and reallocate the context and msn_index */
826 imap_msn_reserve(&mdata->msn, msn);
827
828 struct Email *e = imap_hcache_get(mdata, uid);
829 if (e)
830 {
831 imap_msn_set(&mdata->msn, msn - 1, e);
832
833 if (m->msg_count >= m->email_max)
834 mx_alloc_memory(m);
835
836 struct ImapEmailData *edata = imap_edata_new();
837 e->edata = edata;
838 e->edata_free = imap_edata_free;
839
840 e->index = uid;
841 e->active = true;
842 e->changed = false;
843 edata->read = e->read;
844 edata->old = e->old;
845 edata->deleted = e->deleted;
846 edata->flagged = e->flagged;
847 edata->replied = e->replied;
848
849 edata->msn = msn;
850 edata->uid = uid;
851 mutt_hash_int_insert(mdata->uid_hash, uid, e);
852
853 mailbox_size_add(m, e);
854 m->emails[m->msg_count++] = e;
855
856 msn++;
857 }
858 }
859
860 mutt_seqset_iterator_free(&iter);
861
862 return rc;
863 }
864
865 /**
866 * read_headers_condstore_qresync_updates - Retrieve updates from the server
867 * @param adata Imap Account data
868 * @param msn_end Last Message Sequence number
869 * @param uid_next UID of next email
870 * @param hc_modseq Timestamp of last Header Cache update
871 * @param eval_qresync If true, use QRESYNC
872 * @retval 0 Success
873 * @retval -1 Error
874 *
875 * CONDSTORE and QRESYNC use FETCH extensions to grab updates.
876 */
read_headers_condstore_qresync_updates(struct ImapAccountData * adata,unsigned int msn_end,unsigned int uid_next,unsigned long long hc_modseq,bool eval_qresync)877 static int read_headers_condstore_qresync_updates(struct ImapAccountData *adata,
878 unsigned int msn_end, unsigned int uid_next,
879 unsigned long long hc_modseq, bool eval_qresync)
880 {
881 struct Progress *progress = NULL;
882 char buf[1024];
883 unsigned int header_msn = 0;
884
885 struct Mailbox *m = adata->mailbox;
886 struct ImapMboxData *mdata = imap_mdata_get(m);
887
888 if (m->verbose)
889 {
890 /* L10N: Fetching IMAP flag changes, using the CONDSTORE extension */
891 progress = progress_new(_("Fetching flag updates..."), MUTT_PROGRESS_READ, msn_end);
892 }
893
894 snprintf(buf, sizeof(buf), "UID FETCH 1:%u (FLAGS) (CHANGEDSINCE %llu%s)",
895 uid_next - 1, hc_modseq, eval_qresync ? " VANISHED" : "");
896
897 imap_cmd_start(adata, buf);
898
899 int rc = IMAP_RES_CONTINUE;
900 for (int msgno = 1; rc == IMAP_RES_CONTINUE; msgno++)
901 {
902 if (SigInt && query_abort_header_download(adata))
903 goto fail;
904
905 if (m->verbose)
906 progress_update(progress, msgno, -1);
907
908 /* cmd_parse_fetch will update the flags */
909 rc = imap_cmd_step(adata);
910 if (rc != IMAP_RES_CONTINUE)
911 break;
912
913 /* so we just need to grab the header and persist it back into
914 * the header cache */
915 char *fetch_buf = adata->buf;
916 if (fetch_buf[0] != '*')
917 continue;
918
919 fetch_buf = imap_next_word(fetch_buf);
920 if (!isdigit((unsigned char) *fetch_buf) || (mutt_str_atoui(fetch_buf, &header_msn) < 0))
921 continue;
922
923 if ((header_msn < 1) || (header_msn > msn_end) ||
924 !imap_msn_get(&mdata->msn, header_msn - 1))
925 {
926 mutt_debug(LL_DEBUG1, "skipping CONDSTORE flag update for unknown message number %u\n",
927 header_msn);
928 continue;
929 }
930
931 imap_hcache_put(mdata, imap_msn_get(&mdata->msn, header_msn - 1));
932 }
933
934 if (rc != IMAP_RES_OK)
935 goto fail;
936
937 /* The IMAP flag setting as part of cmd_parse_fetch() ends up
938 * flipping these on. */
939 mdata->check_status &= ~IMAP_FLAGS_PENDING;
940 m->changed = false;
941
942 /* VANISHED handling: we need to empty out the messages */
943 if (mdata->reopen & IMAP_EXPUNGE_PENDING)
944 {
945 imap_hcache_close(mdata);
946 imap_expunge_mailbox(m);
947
948 imap_hcache_open(adata, mdata);
949 mdata->reopen &= ~IMAP_EXPUNGE_PENDING;
950 }
951
952 /* undo expunge count updates.
953 * ctx_update() will do this at the end of the header fetch. */
954 m->vcount = 0;
955 m->msg_tagged = 0;
956 m->msg_deleted = 0;
957 m->msg_new = 0;
958 m->msg_unread = 0;
959 m->msg_flagged = 0;
960 m->changed = false;
961
962 rc = 0;
963 fail:
964 progress_free(&progress);
965 return rc;
966 }
967
968 /**
969 * imap_verify_qresync - Check to see if QRESYNC got jumbled
970 * @param m Imap Selected Mailbox
971 * @retval 0 Success
972 * @retval -1 Error
973 *
974 * If so, wipe the context and try again with a normal download.
975 */
imap_verify_qresync(struct Mailbox * m)976 static int imap_verify_qresync(struct Mailbox *m)
977 {
978 assert(m);
979 struct ImapAccountData *adata = imap_adata_get(m);
980 struct ImapMboxData *mdata = imap_mdata_get(m);
981 if (!adata || (adata->mailbox != m))
982 return -1;
983
984 const size_t max_msn = imap_msn_highest(&mdata->msn);
985
986 unsigned int msn;
987 unsigned int uid;
988 struct Email *e = NULL;
989 struct Email *uidh = NULL;
990
991 for (int i = 0; i < m->msg_count; i++)
992 {
993 e = m->emails[i];
994 const struct ImapEmailData *edata = imap_edata_get(e);
995 if (!edata)
996 goto fail;
997
998 msn = imap_edata_get(e)->msn;
999 uid = imap_edata_get(e)->uid;
1000
1001 if ((msn < 1) || (msn > max_msn) || imap_msn_get(&mdata->msn, msn - 1) != e)
1002 goto fail;
1003
1004 uidh = (struct Email *) mutt_hash_int_find(mdata->uid_hash, uid);
1005 if (uidh != e)
1006 goto fail;
1007 }
1008
1009 return 0;
1010
1011 fail:
1012 imap_msn_free(&mdata->msn);
1013 mutt_hash_free(&mdata->uid_hash);
1014
1015 for (int i = 0; i < m->msg_count; i++)
1016 {
1017 if (m->emails[i] && m->emails[i]->edata)
1018 imap_edata_free(&m->emails[i]->edata);
1019 email_free(&m->emails[i]);
1020 }
1021 m->msg_count = 0;
1022 mutt_hcache_delete_record(mdata->hcache, "/MODSEQ", 7);
1023 imap_hcache_clear_uid_seqset(mdata);
1024 imap_hcache_close(mdata);
1025
1026 if (m->verbose)
1027 {
1028 /* L10N: After opening an IMAP mailbox using QRESYNC, Mutt performs a quick
1029 sanity check. If that fails, Mutt reopens the mailbox using a normal
1030 download. */
1031 mutt_error(_("QRESYNC failed. Reopening mailbox."));
1032 }
1033 return -1;
1034 }
1035
1036 #endif /* USE_HCACHE */
1037
1038 /**
1039 * read_headers_fetch_new - Retrieve new messages from the server
1040 * @param[in] m Imap Selected Mailbox
1041 * @param[in] msn_begin First Message Sequence number
1042 * @param[in] msn_end Last Message Sequence number
1043 * @param[in] evalhc If true, check the Header Cache
1044 * @param[out] maxuid Highest UID seen
1045 * @param[in] initial_download true, if this is the first opening of the mailbox
1046 * @retval 0 Success
1047 * @retval -1 Error
1048 */
read_headers_fetch_new(struct Mailbox * m,unsigned int msn_begin,unsigned int msn_end,bool evalhc,unsigned int * maxuid,bool initial_download)1049 static int read_headers_fetch_new(struct Mailbox *m, unsigned int msn_begin,
1050 unsigned int msn_end, bool evalhc,
1051 unsigned int *maxuid, bool initial_download)
1052 {
1053 int rc, mfhrc = 0, retval = -1;
1054 unsigned int fetch_msn_end = 0;
1055 struct Progress *progress = NULL;
1056 char *hdrreq = NULL;
1057 struct Buffer *tempfile = NULL;
1058 FILE *fp = NULL;
1059 struct ImapHeader h;
1060 struct Buffer *buf = NULL;
1061 static const char *const want_headers =
1062 "DATE FROM SENDER SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE "
1063 "CONTENT-DESCRIPTION IN-REPLY-TO REPLY-TO LINES LIST-POST "
1064 "LIST-SUBSCRIBE LIST-UNSUBSCRIBE X-LABEL X-ORIGINAL-TO";
1065
1066 struct ImapAccountData *adata = imap_adata_get(m);
1067 struct ImapMboxData *mdata = imap_mdata_get(m);
1068 int idx = m->msg_count;
1069
1070 if (!adata || (adata->mailbox != m))
1071 return -1;
1072
1073 struct Buffer *hdr_list = mutt_buffer_pool_get();
1074 mutt_buffer_strcpy(hdr_list, want_headers);
1075 const char *const c_imap_headers =
1076 cs_subset_string(NeoMutt->sub, "imap_headers");
1077 if (c_imap_headers)
1078 {
1079 mutt_buffer_addch(hdr_list, ' ');
1080 mutt_buffer_addstr(hdr_list, c_imap_headers);
1081 }
1082 #ifdef USE_AUTOCRYPT
1083 const bool c_autocrypt = cs_subset_bool(NeoMutt->sub, "autocrypt");
1084 if (c_autocrypt)
1085 {
1086 mutt_buffer_addch(hdr_list, ' ');
1087 mutt_buffer_addstr(hdr_list, "AUTOCRYPT");
1088 }
1089 #endif
1090
1091 if (adata->capabilities & IMAP_CAP_IMAP4REV1)
1092 {
1093 mutt_str_asprintf(&hdrreq, "BODY.PEEK[HEADER.FIELDS (%s)]", mutt_buffer_string(hdr_list));
1094 }
1095 else if (adata->capabilities & IMAP_CAP_IMAP4)
1096 {
1097 mutt_str_asprintf(&hdrreq, "RFC822.HEADER.LINES (%s)", mutt_buffer_string(hdr_list));
1098 }
1099 else
1100 { /* Unable to fetch headers for lower versions */
1101 mutt_error(_("Unable to fetch headers from this IMAP server version"));
1102 goto bail;
1103 }
1104
1105 mutt_buffer_pool_release(&hdr_list);
1106
1107 /* instead of downloading all headers and then parsing them, we parse them
1108 * as they come in. */
1109 tempfile = mutt_buffer_pool_get();
1110 mutt_buffer_mktemp(tempfile);
1111 fp = mutt_file_fopen(mutt_buffer_string(tempfile), "w+");
1112 if (!fp)
1113 {
1114 mutt_error(_("Could not create temporary file %s"), mutt_buffer_string(tempfile));
1115 goto bail;
1116 }
1117 unlink(mutt_buffer_string(tempfile));
1118 mutt_buffer_pool_release(&tempfile);
1119
1120 if (m->verbose)
1121 {
1122 progress = progress_new(_("Fetching message headers..."), MUTT_PROGRESS_READ, msn_end);
1123 }
1124
1125 buf = mutt_buffer_pool_get();
1126
1127 /* NOTE:
1128 * The (fetch_msn_end < msn_end) used to be important to prevent
1129 * an infinite loop, in the event the server did not return all
1130 * the headers (due to a pending expunge, for example).
1131 *
1132 * I believe the new chunking imap_fetch_msn_seqset()
1133 * implementation and "msn_begin = fetch_msn_end + 1" assignment
1134 * at the end of the loop makes the comparison unneeded, but to be
1135 * cautious I'm keeping it.
1136 */
1137 while ((fetch_msn_end < msn_end) &&
1138 imap_fetch_msn_seqset(buf, adata, evalhc, msn_begin, msn_end, &fetch_msn_end))
1139 {
1140 char *cmd = NULL;
1141 mutt_str_asprintf(&cmd, "FETCH %s (UID FLAGS INTERNALDATE RFC822.SIZE %s)",
1142 mutt_buffer_string(buf), hdrreq);
1143 imap_cmd_start(adata, cmd);
1144 FREE(&cmd);
1145
1146 rc = IMAP_RES_CONTINUE;
1147 for (int msgno = msn_begin; rc == IMAP_RES_CONTINUE; msgno++)
1148 {
1149 if (initial_download && SigInt && query_abort_header_download(adata))
1150 goto bail;
1151
1152 if (m->verbose)
1153 progress_update(progress, msgno, -1);
1154
1155 rewind(fp);
1156 memset(&h, 0, sizeof(h));
1157 h.edata = imap_edata_new();
1158
1159 /* this DO loop does two things:
1160 * 1. handles untagged messages, so we can try again on the same msg
1161 * 2. fetches the tagged response at the end of the last message. */
1162 do
1163 {
1164 rc = imap_cmd_step(adata);
1165 if (rc != IMAP_RES_CONTINUE)
1166 break;
1167
1168 mfhrc = msg_fetch_header(m, &h, adata->buf, fp);
1169 if (mfhrc < 0)
1170 continue;
1171
1172 if (!ftello(fp))
1173 {
1174 mutt_debug(LL_DEBUG2, "ignoring fetch response with no body\n");
1175 continue;
1176 }
1177
1178 /* make sure we don't get remnants from older larger message headers */
1179 fputs("\n\n", fp);
1180
1181 if ((h.edata->msn < 1) || (h.edata->msn > fetch_msn_end))
1182 {
1183 mutt_debug(LL_DEBUG1, "skipping FETCH response for unknown message number %d\n",
1184 h.edata->msn);
1185 continue;
1186 }
1187
1188 /* May receive FLAGS updates in a separate untagged response */
1189 if (imap_msn_get(&mdata->msn, h.edata->msn - 1))
1190 {
1191 mutt_debug(LL_DEBUG2, "skipping FETCH response for duplicate message %d\n",
1192 h.edata->msn);
1193 continue;
1194 }
1195
1196 struct Email *e = email_new();
1197 m->emails[idx] = e;
1198
1199 imap_msn_set(&mdata->msn, h.edata->msn - 1, e);
1200 mutt_hash_int_insert(mdata->uid_hash, h.edata->uid, e);
1201
1202 e->index = h.edata->uid;
1203 /* messages which have not been expunged are ACTIVE (borrowed from mh
1204 * folders) */
1205 e->active = true;
1206 e->changed = false;
1207 e->read = h.edata->read;
1208 e->old = h.edata->old;
1209 e->deleted = h.edata->deleted;
1210 e->flagged = h.edata->flagged;
1211 e->replied = h.edata->replied;
1212 e->received = h.received;
1213 e->edata = (void *) (h.edata);
1214 e->edata_free = imap_edata_free;
1215 STAILQ_INIT(&e->tags);
1216
1217 /* We take a copy of the tags so we can split the string */
1218 char *tags_copy = mutt_str_dup(h.edata->flags_remote);
1219 driver_tags_replace(&e->tags, tags_copy);
1220 FREE(&tags_copy);
1221
1222 if (*maxuid < h.edata->uid)
1223 *maxuid = h.edata->uid;
1224
1225 rewind(fp);
1226 /* NOTE: if Date: header is missing, mutt_rfc822_read_header depends
1227 * on h.received being set */
1228 e->env = mutt_rfc822_read_header(fp, e, false, false);
1229 /* body built as a side-effect of mutt_rfc822_read_header */
1230 e->body->length = h.content_length;
1231 mailbox_size_add(m, e);
1232
1233 #ifdef USE_HCACHE
1234 imap_hcache_put(mdata, e);
1235 #endif /* USE_HCACHE */
1236
1237 m->msg_count++;
1238
1239 h.edata = NULL;
1240 idx++;
1241 } while (mfhrc == -1);
1242
1243 imap_edata_free((void **) &h.edata);
1244
1245 if ((mfhrc < -1) || ((rc != IMAP_RES_CONTINUE) && (rc != IMAP_RES_OK)))
1246 goto bail;
1247 }
1248
1249 /* In case we get new mail while fetching the headers. */
1250 if (mdata->reopen & IMAP_NEWMAIL_PENDING)
1251 {
1252 msn_end = mdata->new_mail_count;
1253 while (msn_end > m->email_max)
1254 mx_alloc_memory(m);
1255 imap_msn_reserve(&mdata->msn, msn_end);
1256 mdata->reopen &= ~IMAP_NEWMAIL_PENDING;
1257 mdata->new_mail_count = 0;
1258 }
1259
1260 /* Note: RFC3501 section 7.4.1 and RFC7162 section 3.2.10.2 say we
1261 * must not get any EXPUNGE/VANISHED responses in the middle of a
1262 * FETCH, nor when no command is in progress (e.g. between the
1263 * chunked FETCH commands). We previously tried to be robust by
1264 * setting:
1265 * msn_begin = mdata->max_msn + 1;
1266 * but with chunking (and the mythical header cache holes) this
1267 * may not be correct. So here we must assume the msn values have
1268 * not been altered during or after the fetch. */
1269 msn_begin = fetch_msn_end + 1;
1270 }
1271
1272 retval = 0;
1273
1274 bail:
1275 mutt_buffer_pool_release(&hdr_list);
1276 mutt_buffer_pool_release(&buf);
1277 mutt_buffer_pool_release(&tempfile);
1278 mutt_file_fclose(&fp);
1279 FREE(&hdrreq);
1280 progress_free(&progress);
1281
1282 return retval;
1283 }
1284
1285 /**
1286 * imap_read_headers - Read headers from the server
1287 * @param m Imap Selected Mailbox
1288 * @param msn_begin First Message Sequence Number
1289 * @param msn_end Last Message Sequence Number
1290 * @param initial_download true, if this is the first opening of the mailbox
1291 * @retval num Last MSN
1292 * @retval -1 Failure
1293 *
1294 * Changed to read many headers instead of just one. It will return the msn of
1295 * the last message read. It will return a value other than msn_end if mail
1296 * comes in while downloading headers (in theory).
1297 */
imap_read_headers(struct Mailbox * m,unsigned int msn_begin,unsigned int msn_end,bool initial_download)1298 int imap_read_headers(struct Mailbox *m, unsigned int msn_begin,
1299 unsigned int msn_end, bool initial_download)
1300 {
1301 int oldmsgcount;
1302 unsigned int maxuid = 0;
1303 int retval = -1;
1304 bool evalhc = false;
1305
1306 #ifdef USE_HCACHE
1307 void *uidvalidity = NULL;
1308 void *puid_next = NULL;
1309 unsigned int uid_next = 0;
1310 bool has_condstore = false;
1311 bool has_qresync = false;
1312 bool eval_condstore = false;
1313 bool eval_qresync = false;
1314 unsigned long long *pmodseq = NULL;
1315 unsigned long long hc_modseq = 0;
1316 char *uid_seqset = NULL;
1317 #endif /* USE_HCACHE */
1318
1319 struct ImapAccountData *adata = imap_adata_get(m);
1320 struct ImapMboxData *mdata = imap_mdata_get(m);
1321 if (!adata || (adata->mailbox != m))
1322 return -1;
1323
1324 #ifdef USE_HCACHE
1325 retry:
1326 #endif /* USE_HCACHE */
1327
1328 /* make sure context has room to hold the mailbox */
1329 while (msn_end > m->email_max)
1330 mx_alloc_memory(m);
1331 imap_msn_reserve(&mdata->msn, msn_end);
1332 imap_alloc_uid_hash(adata, msn_end);
1333
1334 oldmsgcount = m->msg_count;
1335 mdata->reopen &= ~(IMAP_REOPEN_ALLOW | IMAP_NEWMAIL_PENDING);
1336 mdata->new_mail_count = 0;
1337
1338 #ifdef USE_HCACHE
1339 imap_hcache_open(adata, mdata);
1340
1341 if (mdata->hcache && initial_download)
1342 {
1343 size_t dlen = 0;
1344 uidvalidity = mutt_hcache_fetch_raw(mdata->hcache, "/UIDVALIDITY", 12, &dlen);
1345 puid_next = mutt_hcache_fetch_raw(mdata->hcache, "/UIDNEXT", 8, &dlen);
1346 if (puid_next)
1347 {
1348 uid_next = *(unsigned int *) puid_next;
1349 mutt_hcache_free_raw(mdata->hcache, &puid_next);
1350 }
1351
1352 if (mdata->modseq)
1353 {
1354 const bool c_imap_condstore =
1355 cs_subset_bool(NeoMutt->sub, "imap_condstore");
1356 if ((adata->capabilities & IMAP_CAP_CONDSTORE) && c_imap_condstore)
1357 has_condstore = true;
1358
1359 /* If IMAP_CAP_QRESYNC and ImapQResync then NeoMutt sends ENABLE QRESYNC.
1360 * If we receive an ENABLED response back, then adata->qresync is set. */
1361 if (adata->qresync)
1362 has_qresync = true;
1363 }
1364
1365 if (uidvalidity && uid_next && (*(uint32_t *) uidvalidity == mdata->uidvalidity))
1366 {
1367 size_t dlen2 = 0;
1368 evalhc = true;
1369 pmodseq = mutt_hcache_fetch_raw(mdata->hcache, "/MODSEQ", 7, &dlen2);
1370 if (pmodseq)
1371 {
1372 hc_modseq = *pmodseq;
1373 mutt_hcache_free_raw(mdata->hcache, (void **) &pmodseq);
1374 }
1375 if (hc_modseq)
1376 {
1377 if (has_qresync)
1378 {
1379 uid_seqset = imap_hcache_get_uid_seqset(mdata);
1380 if (uid_seqset)
1381 eval_qresync = true;
1382 }
1383
1384 if (!eval_qresync && has_condstore)
1385 eval_condstore = true;
1386 }
1387 }
1388 mutt_hcache_free_raw(mdata->hcache, &uidvalidity);
1389 }
1390 if (evalhc)
1391 {
1392 if (eval_qresync)
1393 {
1394 if (read_headers_qresync_eval_cache(adata, uid_seqset) < 0)
1395 goto bail;
1396 }
1397 else
1398 {
1399 if (read_headers_normal_eval_cache(adata, msn_end, uid_next, has_condstore || has_qresync,
1400 eval_condstore) < 0)
1401 goto bail;
1402 }
1403
1404 if ((eval_condstore || eval_qresync) && (hc_modseq != mdata->modseq))
1405 {
1406 if (read_headers_condstore_qresync_updates(adata, msn_end, uid_next,
1407 hc_modseq, eval_qresync) < 0)
1408 {
1409 goto bail;
1410 }
1411 }
1412
1413 /* Look for the first empty MSN and start there */
1414 while (msn_begin <= msn_end)
1415 {
1416 if (!imap_msn_get(&mdata->msn, msn_begin - 1))
1417 break;
1418 msn_begin++;
1419 }
1420 }
1421 #endif /* USE_HCACHE */
1422
1423 if (read_headers_fetch_new(m, msn_begin, msn_end, evalhc, &maxuid, initial_download) < 0)
1424 goto bail;
1425
1426 #ifdef USE_HCACHE
1427 if (eval_qresync && initial_download)
1428 {
1429 if (imap_verify_qresync(m) != 0)
1430 {
1431 eval_qresync = false;
1432 eval_condstore = false;
1433 evalhc = false;
1434 hc_modseq = 0;
1435 maxuid = 0;
1436 FREE(&uid_seqset);
1437 uidvalidity = NULL;
1438 uid_next = 0;
1439
1440 goto retry;
1441 }
1442 }
1443 #endif /* USE_HCACHE */
1444
1445 if (maxuid && (mdata->uid_next < maxuid + 1))
1446 mdata->uid_next = maxuid + 1;
1447
1448 #ifdef USE_HCACHE
1449 mutt_hcache_store_raw(mdata->hcache, "/UIDVALIDITY", 12, &mdata->uidvalidity,
1450 sizeof(mdata->uidvalidity));
1451 if (maxuid && (mdata->uid_next < maxuid + 1))
1452 {
1453 mutt_debug(LL_DEBUG2, "Overriding UIDNEXT: %u -> %u\n", mdata->uid_next, maxuid + 1);
1454 mdata->uid_next = maxuid + 1;
1455 }
1456 if (mdata->uid_next > 1)
1457 {
1458 mutt_hcache_store_raw(mdata->hcache, "/UIDNEXT", 8, &mdata->uid_next,
1459 sizeof(mdata->uid_next));
1460 }
1461
1462 /* We currently only sync CONDSTORE and QRESYNC on the initial download.
1463 * To do it more often, we'll need to deal with flag updates combined with
1464 * unsync'ed local flag changes. We'll also need to properly sync flags to
1465 * the header cache on close. I'm not sure it's worth the added complexity. */
1466 if (initial_download)
1467 {
1468 if (has_condstore || has_qresync)
1469 {
1470 mutt_hcache_store_raw(mdata->hcache, "/MODSEQ", 7, &mdata->modseq,
1471 sizeof(mdata->modseq));
1472 }
1473 else
1474 mutt_hcache_delete_record(mdata->hcache, "/MODSEQ", 7);
1475
1476 if (has_qresync)
1477 imap_hcache_store_uid_seqset(mdata);
1478 else
1479 imap_hcache_clear_uid_seqset(mdata);
1480 }
1481 #endif /* USE_HCACHE */
1482
1483 if (m->msg_count > oldmsgcount)
1484 {
1485 /* TODO: it's not clear to me why we are calling mx_alloc_memory
1486 * yet again. */
1487 mx_alloc_memory(m);
1488 }
1489
1490 mdata->reopen |= IMAP_REOPEN_ALLOW;
1491
1492 retval = msn_end;
1493
1494 bail:
1495 #ifdef USE_HCACHE
1496 imap_hcache_close(mdata);
1497 FREE(&uid_seqset);
1498 #endif /* USE_HCACHE */
1499
1500 return retval;
1501 }
1502
1503 /**
1504 * imap_append_message - Write an email back to the server
1505 * @param m Mailbox
1506 * @param msg Message to save
1507 * @retval 0 Success
1508 * @retval -1 Failure
1509 */
imap_append_message(struct Mailbox * m,struct Message * msg)1510 int imap_append_message(struct Mailbox *m, struct Message *msg)
1511 {
1512 if (!m || !msg)
1513 return -1;
1514
1515 FILE *fp = NULL;
1516 char buf[1024 * 2];
1517 char internaldate[IMAP_DATELEN];
1518 char imap_flags[128];
1519 size_t len;
1520 struct Progress *progress = NULL;
1521 size_t sent;
1522 int c, last;
1523 int rc;
1524
1525 struct ImapAccountData *adata = imap_adata_get(m);
1526 struct ImapMboxData *mdata = imap_mdata_get(m);
1527
1528 fp = fopen(msg->path, "r");
1529 if (!fp)
1530 {
1531 mutt_perror(msg->path);
1532 goto fail;
1533 }
1534
1535 /* currently we set the \Seen flag on all messages, but probably we
1536 * should scan the message Status header for flag info. Since we're
1537 * already rereading the whole file for length it isn't any more
1538 * expensive (it'd be nice if we had the file size passed in already
1539 * by the code that writes the file, but that's a lot of changes.
1540 * Ideally we'd have an Email structure with flag info here... */
1541 for (last = EOF, len = 0; (c = fgetc(fp)) != EOF; last = c)
1542 {
1543 if ((c == '\n') && (last != '\r'))
1544 len++;
1545
1546 len++;
1547 }
1548 rewind(fp);
1549
1550 if (m->verbose)
1551 progress = progress_new(_("Uploading message..."), MUTT_PROGRESS_NET, len);
1552
1553 mutt_date_make_imap(internaldate, sizeof(internaldate), msg->received);
1554
1555 imap_flags[0] = '\0';
1556 imap_flags[1] = '\0';
1557
1558 if (msg->flags.read)
1559 mutt_str_cat(imap_flags, sizeof(imap_flags), " \\Seen");
1560 if (msg->flags.replied)
1561 mutt_str_cat(imap_flags, sizeof(imap_flags), " \\Answered");
1562 if (msg->flags.flagged)
1563 mutt_str_cat(imap_flags, sizeof(imap_flags), " \\Flagged");
1564 if (msg->flags.draft)
1565 mutt_str_cat(imap_flags, sizeof(imap_flags), " \\Draft");
1566
1567 snprintf(buf, sizeof(buf), "APPEND %s (%s) \"%s\" {%lu}", mdata->munge_name,
1568 imap_flags + 1, internaldate, (unsigned long) len);
1569
1570 imap_cmd_start(adata, buf);
1571
1572 do
1573 {
1574 rc = imap_cmd_step(adata);
1575 } while (rc == IMAP_RES_CONTINUE);
1576
1577 if (rc != IMAP_RES_RESPOND)
1578 goto cmd_step_fail;
1579
1580 for (last = EOF, sent = len = 0; (c = fgetc(fp)) != EOF; last = c)
1581 {
1582 if ((c == '\n') && (last != '\r'))
1583 buf[len++] = '\r';
1584
1585 buf[len++] = c;
1586
1587 if (len > sizeof(buf) - 3)
1588 {
1589 sent += len;
1590 if (flush_buffer(buf, &len, adata->conn) < 0)
1591 goto fail;
1592 if (m->verbose)
1593 progress_update(progress, sent, -1);
1594 }
1595 }
1596
1597 if (len)
1598 if (flush_buffer(buf, &len, adata->conn) < 0)
1599 goto fail;
1600
1601 if (mutt_socket_send(adata->conn, "\r\n") < 0)
1602 goto fail;
1603 mutt_file_fclose(&fp);
1604
1605 do
1606 {
1607 rc = imap_cmd_step(adata);
1608 } while (rc == IMAP_RES_CONTINUE);
1609
1610 if (rc != IMAP_RES_OK)
1611 goto cmd_step_fail;
1612
1613 progress_free(&progress);
1614 return 0;
1615
1616 cmd_step_fail:
1617 mutt_debug(LL_DEBUG1, "command failed: %s\n", adata->buf);
1618 if (rc != IMAP_RES_BAD)
1619 {
1620 char *pc = imap_next_word(adata->buf); /* skip sequence number or token */
1621 pc = imap_next_word(pc); /* skip response code */
1622 if (*pc != '\0')
1623 mutt_error("%s", pc);
1624 }
1625
1626 fail:
1627 mutt_file_fclose(&fp);
1628 progress_free(&progress);
1629 return -1;
1630 }
1631
1632 /**
1633 * imap_copy_messages - Server COPY messages to another folder
1634 * @param m Mailbox
1635 * @param el List of Emails to copy
1636 * @param dest Destination folder
1637 * @param save_opt Copy or move, e.g. #SAVE_MOVE
1638 * @retval -1 Error
1639 * @retval 0 Success
1640 * @retval 1 Non-fatal error - try fetch/append
1641 */
imap_copy_messages(struct Mailbox * m,struct EmailList * el,const char * dest,enum MessageSaveOpt save_opt)1642 int imap_copy_messages(struct Mailbox *m, struct EmailList *el,
1643 const char *dest, enum MessageSaveOpt save_opt)
1644 {
1645 if (!m || !el || !dest)
1646 return -1;
1647
1648 struct Buffer cmd, sync_cmd;
1649 char buf[PATH_MAX];
1650 char mbox[PATH_MAX];
1651 char mmbox[PATH_MAX];
1652 char prompt[PATH_MAX + 64];
1653 int rc;
1654 struct ConnAccount cac = { { 0 } };
1655 enum QuadOption err_continue = MUTT_NO;
1656 int triedcreate = 0;
1657 struct EmailNode *en = STAILQ_FIRST(el);
1658 bool single = !STAILQ_NEXT(en, entries);
1659 struct ImapAccountData *adata = imap_adata_get(m);
1660
1661 if (single && en->email->attach_del)
1662 {
1663 mutt_debug(LL_DEBUG3, "#1 Message contains attachments to be deleted\n");
1664 return 1;
1665 }
1666
1667 if (imap_parse_path(dest, &cac, buf, sizeof(buf)))
1668 {
1669 mutt_debug(LL_DEBUG1, "bad destination %s\n", dest);
1670 return -1;
1671 }
1672
1673 /* check that the save-to folder is in the same account */
1674 if (!imap_account_match(&adata->conn->account, &cac))
1675 {
1676 mutt_debug(LL_DEBUG3, "%s not same server as %s\n", dest, mailbox_path(m));
1677 return 1;
1678 }
1679
1680 imap_fix_path(adata->delim, buf, mbox, sizeof(mbox));
1681 if (*mbox == '\0')
1682 mutt_str_copy(mbox, "INBOX", sizeof(mbox));
1683 imap_munge_mbox_name(adata->unicode, mmbox, sizeof(mmbox), mbox);
1684
1685 /* loop in case of TRYCREATE */
1686 do
1687 {
1688 mutt_buffer_init(&sync_cmd);
1689 mutt_buffer_init(&cmd);
1690
1691 if (!single) /* copy tagged messages */
1692 {
1693 /* if any messages have attachments to delete, fall through to FETCH
1694 * and APPEND. TODO: Copy what we can with COPY, fall through for the
1695 * remainder. */
1696 STAILQ_FOREACH(en, el, entries)
1697 {
1698 if (en->email->attach_del)
1699 {
1700 mutt_debug(LL_DEBUG3,
1701 "#2 Message contains attachments to be deleted\n");
1702 return 1;
1703 }
1704
1705 if (en->email->active && en->email->changed)
1706 {
1707 rc = imap_sync_message_for_copy(m, en->email, &sync_cmd, &err_continue);
1708 if (rc < 0)
1709 {
1710 mutt_debug(LL_DEBUG1, "#1 could not sync\n");
1711 goto out;
1712 }
1713 }
1714 }
1715
1716 rc = imap_exec_msgset(m, "UID COPY", mmbox, MUTT_TAG, false, false);
1717 if (rc == 0)
1718 {
1719 mutt_debug(LL_DEBUG1, "No messages tagged\n");
1720 rc = -1;
1721 goto out;
1722 }
1723 else if (rc < 0)
1724 {
1725 mutt_debug(LL_DEBUG1, "#1 could not queue copy\n");
1726 goto out;
1727 }
1728 else
1729 {
1730 mutt_message(ngettext("Copying %d message to %s...", "Copying %d messages to %s...", rc),
1731 rc, mbox);
1732 }
1733 }
1734 else
1735 {
1736 mutt_message(_("Copying message %d to %s..."), en->email->index + 1, mbox);
1737 mutt_buffer_add_printf(&cmd, "UID COPY %u %s", imap_edata_get(en->email)->uid, mmbox);
1738
1739 if (en->email->active && en->email->changed)
1740 {
1741 rc = imap_sync_message_for_copy(m, en->email, &sync_cmd, &err_continue);
1742 if (rc < 0)
1743 {
1744 mutt_debug(LL_DEBUG1, "#2 could not sync\n");
1745 goto out;
1746 }
1747 }
1748 rc = imap_exec(adata, cmd.data, IMAP_CMD_QUEUE);
1749 if (rc != IMAP_EXEC_SUCCESS)
1750 {
1751 mutt_debug(LL_DEBUG1, "#2 could not queue copy\n");
1752 goto out;
1753 }
1754 }
1755
1756 /* let's get it on */
1757 rc = imap_exec(adata, NULL, IMAP_CMD_NO_FLAGS);
1758 if (rc == IMAP_EXEC_ERROR)
1759 {
1760 if (triedcreate)
1761 {
1762 mutt_debug(LL_DEBUG1, "Already tried to create mailbox %s\n", mbox);
1763 break;
1764 }
1765 /* bail out if command failed for reasons other than nonexistent target */
1766 if (!mutt_istr_startswith(imap_get_qualifier(adata->buf), "[TRYCREATE]"))
1767 break;
1768 mutt_debug(LL_DEBUG3, "server suggests TRYCREATE\n");
1769 snprintf(prompt, sizeof(prompt), _("Create %s?"), mbox);
1770 const bool c_confirm_create =
1771 cs_subset_bool(NeoMutt->sub, "confirm_create");
1772 if (c_confirm_create && (mutt_yesorno(prompt, MUTT_YES) != MUTT_YES))
1773 {
1774 mutt_clear_error();
1775 goto out;
1776 }
1777 if (imap_create_mailbox(adata, mbox) < 0)
1778 break;
1779 triedcreate = 1;
1780 }
1781 } while (rc == IMAP_EXEC_ERROR);
1782
1783 if (rc != 0)
1784 {
1785 imap_error("imap_copy_messages", adata->buf);
1786 goto out;
1787 }
1788
1789 /* cleanup */
1790 if (save_opt == SAVE_MOVE)
1791 {
1792 const bool c_delete_untag = cs_subset_bool(NeoMutt->sub, "delete_untag");
1793 STAILQ_FOREACH(en, el, entries)
1794 {
1795 mutt_set_flag(m, en->email, MUTT_DELETE, true);
1796 mutt_set_flag(m, en->email, MUTT_PURGE, true);
1797 if (c_delete_untag)
1798 mutt_set_flag(m, en->email, MUTT_TAG, false);
1799 }
1800 }
1801
1802 rc = 0;
1803
1804 out:
1805 FREE(&cmd.data);
1806 FREE(&sync_cmd.data);
1807
1808 return (rc < 0) ? -1 : rc;
1809 }
1810
1811 /**
1812 * imap_cache_del - Delete an email from the body cache
1813 * @param m Selected Imap Mailbox
1814 * @param e Email
1815 * @retval 0 Success
1816 * @retval -1 Failure
1817 */
imap_cache_del(struct Mailbox * m,struct Email * e)1818 int imap_cache_del(struct Mailbox *m, struct Email *e)
1819 {
1820 struct ImapAccountData *adata = imap_adata_get(m);
1821 struct ImapMboxData *mdata = imap_mdata_get(m);
1822
1823 if (!e || !adata || (adata->mailbox != m))
1824 return -1;
1825
1826 mdata->bcache = msg_cache_open(m);
1827 char id[64];
1828 snprintf(id, sizeof(id), "%u-%u", mdata->uidvalidity, imap_edata_get(e)->uid);
1829 return mutt_bcache_del(mdata->bcache, id);
1830 }
1831
1832 /**
1833 * imap_cache_clean - Delete all the entries in the message cache
1834 * @param m SelectedImap Mailbox
1835 * @retval 0 Always
1836 */
imap_cache_clean(struct Mailbox * m)1837 int imap_cache_clean(struct Mailbox *m)
1838 {
1839 struct ImapAccountData *adata = imap_adata_get(m);
1840 struct ImapMboxData *mdata = imap_mdata_get(m);
1841
1842 if (!adata || (adata->mailbox != m))
1843 return -1;
1844
1845 mdata->bcache = msg_cache_open(m);
1846 mutt_bcache_list(mdata->bcache, msg_cache_clean_cb, mdata);
1847
1848 return 0;
1849 }
1850
1851 /**
1852 * imap_set_flags - Fill the message header according to the server flags
1853 * @param[in] m Imap Selected Mailbox
1854 * @param[in] e Email
1855 * @param[in] s Command string
1856 * @param[out] server_changes Set to true if the flags have changed
1857 * @retval ptr The end of flags string
1858 * @retval NULL Failure
1859 *
1860 * Expects a flags line of the form "FLAGS (flag flag ...)"
1861 *
1862 * imap_set_flags: fill out the message header according to the flags from
1863 * the server. Expects a flags line of the form "FLAGS (flag flag ...)"
1864 *
1865 * Sets server_changes to 1 if a change to a flag is made, or in the
1866 * case of e->changed, if a change to a flag _would_ have been
1867 * made.
1868 */
imap_set_flags(struct Mailbox * m,struct Email * e,char * s,bool * server_changes)1869 char *imap_set_flags(struct Mailbox *m, struct Email *e, char *s, bool *server_changes)
1870 {
1871 struct ImapAccountData *adata = imap_adata_get(m);
1872 if (!adata || (adata->mailbox != m))
1873 return NULL;
1874
1875 struct ImapHeader newh = { 0 };
1876 struct ImapEmailData old_edata = { 0 };
1877 int local_changes = e->changed;
1878
1879 struct ImapEmailData *edata = e->edata;
1880 newh.edata = edata;
1881
1882 mutt_debug(LL_DEBUG2, "parsing FLAGS\n");
1883 s = msg_parse_flags(&newh, s);
1884 if (!s)
1885 return NULL;
1886
1887 /* Update tags system */
1888 /* We take a copy of the tags so we can split the string */
1889 char *tags_copy = mutt_str_dup(edata->flags_remote);
1890 driver_tags_replace(&e->tags, tags_copy);
1891 FREE(&tags_copy);
1892
1893 /* YAUH (yet another ugly hack): temporarily set context to
1894 * read-write even if it's read-only, so *server* updates of
1895 * flags can be processed by mutt_set_flag. mailbox->changed must
1896 * be restored afterwards */
1897 bool readonly = m->readonly;
1898 m->readonly = false;
1899
1900 /* This is redundant with the following two checks. Removing:
1901 * mutt_set_flag (m, e, MUTT_NEW, !(edata->read || edata->old)); */
1902 set_changed_flag(m, e, local_changes, server_changes, MUTT_OLD, old_edata.old,
1903 edata->old, e->old);
1904 set_changed_flag(m, e, local_changes, server_changes, MUTT_READ,
1905 old_edata.read, edata->read, e->read);
1906 set_changed_flag(m, e, local_changes, server_changes, MUTT_DELETE,
1907 old_edata.deleted, edata->deleted, e->deleted);
1908 set_changed_flag(m, e, local_changes, server_changes, MUTT_FLAG,
1909 old_edata.flagged, edata->flagged, e->flagged);
1910 set_changed_flag(m, e, local_changes, server_changes, MUTT_REPLIED,
1911 old_edata.replied, edata->replied, e->replied);
1912
1913 /* this message is now definitively *not* changed (mutt_set_flag
1914 * marks things changed as a side-effect) */
1915 if (local_changes == 0)
1916 e->changed = false;
1917 m->changed &= !readonly;
1918 m->readonly = readonly;
1919
1920 return s;
1921 }
1922
1923 /**
1924 * imap_msg_open - Open an email message in a Mailbox - Implements MxOps::msg_open() - @ingroup mx_msg_open
1925 */
imap_msg_open(struct Mailbox * m,struct Message * msg,int msgno)1926 bool imap_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
1927 {
1928 struct Envelope *newenv = NULL;
1929 char buf[1024];
1930 char *pc = NULL;
1931 unsigned int bytes;
1932 struct Progress *progress = NULL;
1933 unsigned int uid;
1934 bool retried = false;
1935 bool read;
1936 int rc;
1937
1938 /* Sam's weird courier server returns an OK response even when FETCH
1939 * fails. Thanks Sam. */
1940 bool fetched = false;
1941
1942 struct ImapAccountData *adata = imap_adata_get(m);
1943
1944 if (!adata || (adata->mailbox != m))
1945 return false;
1946
1947 struct Email *e = m->emails[msgno];
1948 if (!e)
1949 return false;
1950
1951 msg->fp = msg_cache_get(m, e);
1952 if (msg->fp)
1953 {
1954 if (imap_edata_get(e)->parsed)
1955 return true;
1956 goto parsemsg;
1957 }
1958
1959 /* This function is called in a few places after endwin()
1960 * e.g. mutt_pipe_message(). */
1961 bool output_progress = !isendwin() && m->verbose;
1962 if (output_progress)
1963 mutt_message(_("Fetching message..."));
1964
1965 msg->fp = msg_cache_put(m, e);
1966 if (!msg->fp)
1967 {
1968 struct Buffer *path = mutt_buffer_pool_get();
1969 mutt_buffer_mktemp(path);
1970 msg->fp = mutt_file_fopen(mutt_buffer_string(path), "w+");
1971 unlink(mutt_buffer_string(path));
1972 mutt_buffer_pool_release(&path);
1973
1974 if (!msg->fp)
1975 return false;
1976 }
1977
1978 /* mark this header as currently inactive so the command handler won't
1979 * also try to update it. HACK until all this code can be moved into the
1980 * command handler */
1981 e->active = false;
1982
1983 const bool c_imap_peek = cs_subset_bool(NeoMutt->sub, "imap_peek");
1984 snprintf(buf, sizeof(buf), "UID FETCH %u %s", imap_edata_get(e)->uid,
1985 ((adata->capabilities & IMAP_CAP_IMAP4REV1) ?
1986 (c_imap_peek ? "BODY.PEEK[]" : "BODY[]") :
1987 "RFC822"));
1988
1989 imap_cmd_start(adata, buf);
1990 do
1991 {
1992 rc = imap_cmd_step(adata);
1993 if (rc != IMAP_RES_CONTINUE)
1994 break;
1995
1996 pc = adata->buf;
1997 pc = imap_next_word(pc);
1998 pc = imap_next_word(pc);
1999
2000 if (mutt_istr_startswith(pc, "FETCH"))
2001 {
2002 while (*pc)
2003 {
2004 pc = imap_next_word(pc);
2005 if (pc[0] == '(')
2006 pc++;
2007 if (mutt_istr_startswith(pc, "UID"))
2008 {
2009 pc = imap_next_word(pc);
2010 if (mutt_str_atoui(pc, &uid) < 0)
2011 goto bail;
2012 if (uid != imap_edata_get(e)->uid)
2013 {
2014 mutt_error(_(
2015 "The message index is incorrect. Try reopening the mailbox."));
2016 }
2017 }
2018 else if (mutt_istr_startswith(pc, "RFC822") || mutt_istr_startswith(pc, "BODY[]"))
2019 {
2020 pc = imap_next_word(pc);
2021 if (imap_get_literal_count(pc, &bytes) < 0)
2022 {
2023 imap_error("imap_msg_open()", buf);
2024 goto bail;
2025 }
2026 if (output_progress)
2027 {
2028 progress = progress_new(_("Fetching message..."), MUTT_PROGRESS_NET, bytes);
2029 }
2030 if (imap_read_literal(msg->fp, adata, bytes, output_progress ? progress : NULL) < 0)
2031 {
2032 goto bail;
2033 }
2034 /* pick up trailing line */
2035 rc = imap_cmd_step(adata);
2036 if (rc != IMAP_RES_CONTINUE)
2037 goto bail;
2038 pc = adata->buf;
2039
2040 fetched = true;
2041 }
2042 /* UW-IMAP will provide a FLAGS update here if the FETCH causes a
2043 * change (eg from \Unseen to \Seen).
2044 * Uncommitted changes in neomutt take precedence. If we decide to
2045 * incrementally update flags later, this won't stop us syncing */
2046 else if (!e->changed && mutt_istr_startswith(pc, "FLAGS"))
2047 {
2048 pc = imap_set_flags(m, e, pc, NULL);
2049 if (!pc)
2050 goto bail;
2051 }
2052 }
2053 }
2054 } while (rc == IMAP_RES_CONTINUE);
2055
2056 /* see comment before command start. */
2057 e->active = true;
2058
2059 fflush(msg->fp);
2060 if (ferror(msg->fp))
2061 goto bail;
2062
2063 if (rc != IMAP_RES_OK)
2064 goto bail;
2065
2066 if (!fetched || !imap_code(adata->buf))
2067 goto bail;
2068
2069 msg_cache_commit(m, e);
2070
2071 parsemsg:
2072 /* Update the header information. Previously, we only downloaded a
2073 * portion of the headers, those required for the main display. */
2074 rewind(msg->fp);
2075 /* It may be that the Status header indicates a message is read, but the
2076 * IMAP server doesn't know the message has been \Seen. So we capture
2077 * the server's notion of 'read' and if it differs from the message info
2078 * picked up in mutt_rfc822_read_header, we mark the message (and context
2079 * changed). Another possibility: ignore Status on IMAP? */
2080 read = e->read;
2081 newenv = mutt_rfc822_read_header(msg->fp, e, false, false);
2082 mutt_env_merge(e->env, &newenv);
2083
2084 /* see above. We want the new status in e->read, so we unset it manually
2085 * and let mutt_set_flag set it correctly, updating context. */
2086 if (read != e->read)
2087 {
2088 e->read = read;
2089 mutt_set_flag(m, e, MUTT_NEW, read);
2090 }
2091
2092 e->lines = 0;
2093 fgets(buf, sizeof(buf), msg->fp);
2094 while (!feof(msg->fp))
2095 {
2096 e->lines++;
2097 fgets(buf, sizeof(buf), msg->fp);
2098 }
2099
2100 e->body->length = ftell(msg->fp) - e->body->offset;
2101
2102 mutt_clear_error();
2103 rewind(msg->fp);
2104 imap_edata_get(e)->parsed = true;
2105
2106 /* retry message parse if cached message is empty */
2107 if (!retried && ((e->lines == 0) || (e->body->length == 0)))
2108 {
2109 imap_cache_del(m, e);
2110 retried = true;
2111 goto parsemsg;
2112 }
2113
2114 progress_free(&progress);
2115 return true;
2116
2117 bail:
2118 e->active = true;
2119 mutt_file_fclose(&msg->fp);
2120 imap_cache_del(m, e);
2121 progress_free(&progress);
2122 return false;
2123 }
2124
2125 /**
2126 * imap_msg_commit - Save changes to an email - Implements MxOps::msg_commit() - @ingroup mx_msg_commit
2127 *
2128 * @note May also return EOF Failure, see errno
2129 */
imap_msg_commit(struct Mailbox * m,struct Message * msg)2130 int imap_msg_commit(struct Mailbox *m, struct Message *msg)
2131 {
2132 int rc = mutt_file_fclose(&msg->fp);
2133 if (rc != 0)
2134 return rc;
2135
2136 return imap_append_message(m, msg);
2137 }
2138
2139 /**
2140 * imap_msg_close - Close an email - Implements MxOps::msg_close() - @ingroup mx_msg_close
2141 *
2142 * @note May also return EOF Failure, see errno
2143 */
imap_msg_close(struct Mailbox * m,struct Message * msg)2144 int imap_msg_close(struct Mailbox *m, struct Message *msg)
2145 {
2146 return mutt_file_fclose(&msg->fp);
2147 }
2148
2149 /**
2150 * imap_msg_save_hcache - Save message to the header cache - Implements MxOps::msg_save_hcache() - @ingroup mx_msg_save_hcache
2151 */
imap_msg_save_hcache(struct Mailbox * m,struct Email * e)2152 int imap_msg_save_hcache(struct Mailbox *m, struct Email *e)
2153 {
2154 int rc = 0;
2155 #ifdef USE_HCACHE
2156 bool close_hc = true;
2157 struct ImapAccountData *adata = imap_adata_get(m);
2158 struct ImapMboxData *mdata = imap_mdata_get(m);
2159 if (!mdata || !adata)
2160 return -1;
2161 if (mdata->hcache)
2162 close_hc = false;
2163 else
2164 imap_hcache_open(adata, mdata);
2165 rc = imap_hcache_put(mdata, e);
2166 if (close_hc)
2167 imap_hcache_close(mdata);
2168 #endif
2169 return rc;
2170 }
2171