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