#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
+#include <limits.h>
#include "mutt.h"
#include "imap_private.h"
/* Generates a more complicated sequence set after using the header cache,
* in case there are missing MSNs in the middle.
- *
- * There is a suggested limit of 1000 bytes for an IMAP client request.
- * Ideally, we would generate multiple requests if the number of ranges
- * is too big, but for now just abort to using the whole range.
*/
-static void imap_fetch_msn_seqset (BUFFER *b, IMAP_DATA *idata, unsigned int msn_begin,
- unsigned int msn_end)
+static unsigned int imap_fetch_msn_seqset (BUFFER *b, IMAP_DATA *idata, int evalhc,
+ unsigned int msn_begin, unsigned int msn_end,
+ unsigned int *fetch_msn_end)
{
- int chunks = 0;
+ unsigned int max_headers_per_fetch = UINT_MAX;
+ int first_chunk = 1;
int state = 0; /* 1: single msn, 2: range of msn */
- unsigned int msn, range_begin, range_end;
+ unsigned int msn, range_begin, range_end, msn_count = 0;
+
+ mutt_buffer_clear (b);
+ if (msn_end < msn_begin)
+ return 0;
+
+ if (ImapFetchChunkSize > 0)
+ max_headers_per_fetch = ImapFetchChunkSize;
+
+ if (!evalhc)
+ {
+ if (msn_end - msn_begin + 1 <= max_headers_per_fetch)
+ *fetch_msn_end = msn_end;
+ else
+ *fetch_msn_end = msn_begin + max_headers_per_fetch - 1;
+ mutt_buffer_printf (b, "%u:%u", msn_begin, *fetch_msn_end);
+ return (*fetch_msn_end - msn_begin + 1);
+ }
for (msn = msn_begin; msn <= msn_end + 1; msn++)
{
- if (msn <= msn_end && !idata->msn_index[msn-1])
+ if (msn_count < max_headers_per_fetch &&
+ msn <= msn_end &&
+ !idata->msn_index[msn-1])
{
+ msn_count++;
+
switch (state)
{
case 1: /* single: convert to a range */
}
else if (state)
{
- if (chunks++)
+ if (first_chunk)
+ first_chunk = 0;
+ else
mutt_buffer_addch (b, ',');
- if (chunks == 150)
- break;
if (state == 1)
mutt_buffer_add_printf (b, "%u", range_begin);
else if (state == 2)
mutt_buffer_add_printf (b, "%u:%u", range_begin, range_end);
state = 0;
+
+ if ((mutt_buffer_len (b) > 500) ||
+ msn_count >= max_headers_per_fetch)
+ break;
}
}
- /* Too big. Just query the whole range then. */
- if (chunks == 150 || mutt_strlen (b->data) > 500)
- {
- mutt_buffer_clear (b);
- mutt_buffer_add_printf (b, "%u:%u", msn_begin, msn_end);
- }
+ /* The loop index goes one past to terminate the range if needed. */
+ *fetch_msn_end = msn - 1;
+
+ return msn_count;
}
/* imap_read_headers:
char tempfile[_POSIX_PATH_MAX];
FILE *fp = NULL;
IMAP_HEADER h;
- BUFFER *b;
+ BUFFER *b = NULL;
static const char * const want_headers = "DATE FROM SENDER SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE CONTENT-DESCRIPTION IN-REPLY-TO REPLY-TO LINES LIST-POST X-LABEL";
ctx = idata->ctx;
mutt_progress_init (&progress, _("Fetching message headers..."),
MUTT_PROGRESS_MSG, ReadInc, msn_end);
- while (msn_begin <= msn_end && fetch_msn_end < msn_end)
+ b = mutt_buffer_pool_get ();
+
+ /* NOTE:
+ * The (fetch_msn_end < msn_end) used to be important to prevent
+ * an infinite loop, in the event the server did not return all
+ * the headers (due to a pending expunge, for example).
+ *
+ * I believe the new chunking imap_fetch_msn_seqset()
+ * implementation and "msn_begin = fetch_msn_end + 1" assignment
+ * at the end of the loop makes the comparison unneeded, but to be
+ * cautious I'm keeping it.
+ */
+ while ((fetch_msn_end < msn_end) &&
+ imap_fetch_msn_seqset (b, idata, evalhc, msn_begin, msn_end,
+ &fetch_msn_end))
{
- b = mutt_buffer_new ();
- if (evalhc)
- {
- /* In case there are holes in the header cache. */
- evalhc = 0;
- imap_fetch_msn_seqset (b, idata, msn_begin, msn_end);
- }
- else
- mutt_buffer_add_printf (b, "%u:%u", msn_begin, msn_end);
-
- fetch_msn_end = msn_end;
safe_asprintf (&cmd, "FETCH %s (UID FLAGS INTERNALDATE RFC822.SIZE %s)",
- b->data, hdrreq);
+ mutt_b2s (b), hdrreq);
imap_cmd_start (idata, cmd);
FREE (&cmd);
- mutt_buffer_free (&b);
rc = IMAP_CMD_CONTINUE;
for (msgno = msn_begin; rc == IMAP_CMD_CONTINUE; msgno++)
goto bail;
}
- /* In case we get new mail while fetching the headers.
- *
- * Note: The RFC says we shouldn't get any EXPUNGE responses in the
- * middle of a FETCH. But just to be cautious, use the current state
- * of max_msn, not fetch_msn_end to set the next start range.
- */
+ /* In case we get new mail while fetching the headers. */
if (idata->reopen & IMAP_NEWMAIL_PENDING)
{
- /* update to the last value we actually pulled down */
- fetch_msn_end = idata->max_msn;
- msn_begin = idata->max_msn + 1;
msn_end = idata->newMailCount;
while (msn_end > ctx->hdrmax)
mx_alloc_memory (ctx);
idata->reopen &= ~IMAP_NEWMAIL_PENDING;
idata->newMailCount = 0;
}
+
+ /* Note: RFC3501 section 7.4.1 and RFC7162 section 3.2.10.2 say we
+ * must not get any EXPUNGE/VANISHED responses in the middle of a
+ * FETCH, nor when no command is in progress (e.g. between the
+ * chunked FETCH commands). We previously tried to be robust by
+ * setting:
+ * msn_begin = idata->max_msn + 1;
+ * but with chunking (and the mythical header cache holes) this
+ * may not be correct. So here we must assume the msn values have
+ * not been altered during or after the fetch.
+ */
+ msn_begin = fetch_msn_end + 1;
}
retval = 0;
bail:
+ mutt_buffer_pool_release (&b);
safe_fclose (&fp);
FREE (&hdrreq);