* Add function to generate uid_seqset for header cache.
This will be used by QREFRESH to reconstruct the msn_index state.
* Add seqset data structure and iterator functions.
* Add cmd_parse_vanished, for QRESYNC support.
* Add and enable QRESYNC capability.
* Create helper functions to store and retrieve the uid_seqset.
static void cmd_handle_fatal (IMAP_DATA* idata);
static int cmd_handle_untagged (IMAP_DATA* idata);
static void cmd_parse_capability (IMAP_DATA* idata, char* s);
+static void cmd_parse_vanished (IMAP_DATA* idata, char* s);
static void cmd_parse_expunge (IMAP_DATA* idata, const char* s);
static void cmd_parse_list (IMAP_DATA* idata, char* s);
static void cmd_parse_lsub (IMAP_DATA* idata, char* s);
"SASL-IR",
"ENABLE",
"CONDSTORE",
+ "QRESYNC",
NULL
};
else if (ascii_strncasecmp ("FETCH", s, 5) == 0)
cmd_parse_fetch (idata, pn);
}
+ else if ((idata->state >= IMAP_SELECTED) &&
+ ascii_strncasecmp ("VANISHED", s, 8) == 0)
+ cmd_parse_vanished (idata, pn);
else if (ascii_strncasecmp ("CAPABILITY", s, 10) == 0)
cmd_parse_capability (idata, s);
else if (!ascii_strncasecmp ("OK [CAPABILITY", s, 14))
idata->reopen |= IMAP_EXPUNGE_PENDING;
}
+/* cmd_parse_vanished: handles VANISHED (RFC 7162), which is like
+ * expunge, but passes a seqset of UIDs. An optional (EARLIER) argument
+ * specifies not to decrement subsequent MSNs. */
+static void cmd_parse_vanished (IMAP_DATA* idata, char* s)
+{
+ int earlier = 0, rc;
+ char *end_of_seqset;
+ SEQSET_ITERATOR *iter;
+ unsigned int uid, exp_msn, cur;
+ HEADER* h;
+
+ dprint (2, (debugfile, "Handling VANISHED\n"));
+
+ if (ascii_strncasecmp ("(EARLIER)", s, 9) == 0)
+ {
+ earlier = 1;
+ s = imap_next_word (s);
+ }
+
+ end_of_seqset = s;
+ while (*end_of_seqset)
+ {
+ if (!strchr ("0123456789:,", *end_of_seqset))
+ *end_of_seqset = '\0';
+ else
+ end_of_seqset++;
+ }
+
+ iter = mutt_seqset_iterator_new (s);
+ if (!iter)
+ {
+ dprint (2, (debugfile, "VANISHED: empty seqset [%s]?\n", s));
+ return;
+ }
+
+ while ((rc = mutt_seqset_iterator_next (iter, &uid)) == 0)
+ {
+ h = (HEADER *)int_hash_find (idata->uid_hash, uid);
+ if (!h)
+ continue;
+
+ exp_msn = HEADER_DATA(h)->msn;
+
+ /* imap_expunge_mailbox() will rewrite h->index.
+ * It needs to resort using SORT_ORDER anyway, so setting to INT_MAX
+ * makes the code simpler and possibly more efficient. */
+ h->index = INT_MAX;
+ HEADER_DATA(h)->msn = 0;
+
+ if (exp_msn < 1 || exp_msn > idata->max_msn)
+ {
+ dprint (1, (debugfile,
+ "VANISHED: msn for UID %u is incorrect.\n", uid));
+ continue;
+ }
+ if (idata->msn_index[exp_msn - 1] != h)
+ {
+ dprint (1, (debugfile,
+ "VANISHED: msn_index for UID %u is incorrect.\n", uid));
+ continue;
+ }
+
+ idata->msn_index[exp_msn - 1] = NULL;
+
+ if (!earlier)
+ {
+ /* decrement seqno of those above. */
+ for (cur = exp_msn; cur < idata->max_msn; cur++)
+ {
+ h = idata->msn_index[cur];
+ if (h)
+ HEADER_DATA(h)->msn--;
+ idata->msn_index[cur - 1] = h;
+ }
+
+ idata->msn_index[idata->max_msn - 1] = NULL;
+ idata->max_msn--;
+ }
+ }
+
+ if (rc < 0)
+ dprint (1, (debugfile, "VANISHED: illegal seqset %s\n", s));
+
+ idata->reopen |= IMAP_EXPUNGE_PENDING;
+
+ mutt_seqset_iterator_free (&iter);
+}
+
/* cmd_parse_fetch: Load fetch response into IMAP_DATA. Currently only
* handles unanticipated FETCH responses, and only FLAGS data. We get
* these if another client has changed flags for a mailbox we've selected.
if (ascii_strncasecmp(s, "UTF8=ACCEPT", 11) == 0 ||
ascii_strncasecmp(s, "UTF8=ONLY", 9) == 0)
idata->unicode = 1;
+ if (ascii_strncasecmp(s, "QRESYNC", 7) == 0)
+ idata->qresync = 1;
}
}
{
/* capabilities may have changed */
imap_exec (idata, "CAPABILITY", IMAP_CMD_QUEUE);
+
/* enable RFC6855, if the server supports that */
if (mutt_bit_isset (idata->capabilities, ENABLE))
imap_exec (idata, "ENABLE UTF8=ACCEPT", IMAP_CMD_QUEUE);
+
+ /* enable QRESYNC. Advertising QRESYNC also means CONDSTORE
+ * is supported (even if not advertised), so flip that bit. */
+ if (mutt_bit_isset (idata->capabilities, QRESYNC))
+ {
+ mutt_bit_set (idata->capabilities, CONDSTORE);
+ imap_exec (idata, "ENABLE QRESYNC", IMAP_CMD_QUEUE);
+ }
+
/* get root delimiter, '/' as default */
idata->delim = '/';
imap_exec (idata, "LIST \"\" \"\"", IMAP_CMD_QUEUE);
if (option (OPTIMAPCHECKSUBSCRIBED))
imap_exec (idata, "LSUB \"\" \"*\"", IMAP_CMD_QUEUE);
+
/* we may need the root delimiter before we open a mailbox */
imap_exec (idata, NULL, IMAP_CMD_FAIL_OK);
}
SASL_IR, /* SASL initial response draft */
ENABLE, /* RFC 5161 */
CONDSTORE, /* RFC 7162 */
+ QRESYNC, /* RFC 7162 */
CAPMAX
};
* than mUTF7 */
int unicode;
+ int qresync; /* Set to 1 if QRESYNC is successfully ENABLE'd */
+
/* if set, the response parser will store results for complicated commands
* here. */
IMAP_COMMAND_TYPE cmdtype;
} IMAP_DATA;
/* I wish that were called IMAP_CONTEXT :( */
+typedef struct
+{
+ char *full_seqset;
+ char *eostr;
+ int in_range;
+ int down;
+ unsigned int range_cur, range_end;
+ char *substr_cur, *substr_end;
+} SEQSET_ITERATOR;
+
/* -- macros -- */
#define CTX_DATA ((IMAP_DATA *) ctx->data)
HEADER* imap_hcache_get (IMAP_DATA* idata, unsigned int uid);
int imap_hcache_put (IMAP_DATA* idata, HEADER* h);
int imap_hcache_del (IMAP_DATA* idata, unsigned int uid);
+int imap_hcache_store_uid_seqset (IMAP_DATA *idata);
+char *imap_hcache_get_uid_seqset (IMAP_DATA *idata);
#endif
int imap_continue (const char* msg, const char* resp);
void imap_munge_mbox_name (IMAP_DATA *idata, char *dest, size_t dlen, const char *src);
void imap_unmunge_mbox_name (IMAP_DATA *idata, char *s);
int imap_wordcasecmp(const char *a, const char *b);
+SEQSET_ITERATOR *mutt_seqset_iterator_new (const char *seqset);
+int mutt_seqset_iterator_next (SEQSET_ITERATOR *iter, unsigned int *next);
+void mutt_seqset_iterator_free (SEQSET_ITERATOR **p_iter);
/* utf7.c */
void imap_utf_encode (IMAP_DATA *idata, char **s);
* 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_generate_seqset (BUFFER *b, IMAP_DATA *idata, unsigned int msn_begin,
- unsigned int msn_end)
+static void imap_fetch_msn_seqset (BUFFER *b, IMAP_DATA *idata, unsigned int msn_begin,
+ unsigned int msn_end)
{
int chunks = 0;
int state = 0; /* 1: single msn, 2: range of msn */
{
/* In case there are holes in the header cache. */
evalhc = 0;
- imap_generate_seqset (b, idata, msn_begin, msn_end);
+ imap_fetch_msn_seqset (b, idata, msn_begin, msn_end);
}
else
mutt_buffer_printf (b, "%u:%u", msn_begin, msn_end);
}
#ifdef USE_HCACHE
+
+/* Generates a seqseq of the UIDs in msn_index to persist in the header cache.
+ *
+ * Empty spots are stored as 0.
+ */
+static void imap_msn_index_to_uid_seqset (BUFFER *b, IMAP_DATA *idata)
+{
+ int first = 1, state = 0, match = 0;
+ HEADER *cur_header;
+ unsigned int msn, cur_uid = 0, last_uid = 0;
+ unsigned int range_begin = 0, range_end = 0;
+
+ for (msn = 1; msn <= idata->max_msn + 1; msn++)
+ {
+ match = 0;
+ if (msn <= idata->max_msn)
+ {
+ cur_header = idata->msn_index[msn - 1];
+ cur_uid = cur_header ? HEADER_DATA(cur_header)->uid : 0;
+ if (!state || (cur_uid && (cur_uid - 1 == last_uid)))
+ match = 1;
+ last_uid = cur_uid;
+ }
+
+ if (match)
+ {
+ switch (state)
+ {
+ case 1: /* single: convert to a range */
+ state = 2;
+ /* fall through */
+ case 2: /* extend range ending */
+ range_end = cur_uid;
+ break;
+ default:
+ state = 1;
+ range_begin = cur_uid;
+ break;
+ }
+ }
+ else if (state)
+ {
+ if (first)
+ first = 0;
+ else
+ mutt_buffer_addch (b, ',');
+
+ if (state == 1)
+ mutt_buffer_printf (b, "%u", range_begin);
+ else if (state == 2)
+ mutt_buffer_printf (b, "%u:%u", range_begin, range_end);
+
+ state = 1;
+ range_begin = cur_uid;
+ }
+ }
+}
+
static int imap_hcache_namer (const char* path, char* dest, size_t dlen)
{
return snprintf (dest, dlen, "%s.hcache", path);
sprintf (key, "/%u", uid);
return mutt_hcache_delete (idata->hcache, key, imap_hcache_keylen);
}
+
+int imap_hcache_store_uid_seqset (IMAP_DATA *idata)
+{
+ BUFFER *b;
+ size_t seqset_size;
+ int rc;
+
+ if (!idata->hcache)
+ return -1;
+
+ b = mutt_buffer_new ();
+ /* The seqset is likely large. Preallocate to reduce reallocs */
+ mutt_buffer_increase_size (b, HUGE_STRING);
+ imap_msn_index_to_uid_seqset (b, idata);
+
+ seqset_size = b->dptr - b->data;
+ if (seqset_size == 0)
+ b->data[0] = '\0';
+
+ rc = mutt_hcache_store_raw (idata->hcache, "/UIDSEQSET",
+ b->data, seqset_size + 1,
+ imap_hcache_keylen);
+ dprint (5, (debugfile, "Stored /UIDSEQSET %s\n", b->data));
+ mutt_buffer_free (&b);
+ return rc;
+}
+
+char *imap_hcache_get_uid_seqset (IMAP_DATA *idata)
+{
+ char *hc_seqset, *seqset;
+
+ if (!idata->hcache)
+ return NULL;
+
+ hc_seqset = mutt_hcache_fetch_raw (idata->hcache, "/UIDSEQSET",
+ imap_hcache_keylen);
+ seqset = safe_strdup (hc_seqset);
+ mutt_hcache_free ((void **)&hc_seqset);
+ dprint (5, (debugfile, "Retrieved /UIDSEQSET %s\n", NONULL (seqset)));
+
+ return seqset;
+}
#endif
/* imap_parse_path: given an IMAP mailbox name, return host, port
return mutt_account_match (a1_canon, a2_canon);
}
+
+/* Sequence set iteration */
+
+SEQSET_ITERATOR *mutt_seqset_iterator_new (const char *seqset)
+{
+ SEQSET_ITERATOR *iter;
+
+ if (!seqset || !*seqset)
+ return NULL;
+
+ iter = safe_calloc (1, sizeof(SEQSET_ITERATOR));
+ iter->full_seqset = safe_strdup (seqset);
+ iter->eostr = strchr (iter->full_seqset, '\0');
+ iter->substr_cur = iter->substr_end = iter->full_seqset;
+
+ return iter;
+}
+
+/* Returns: 0 when the next sequence is generated
+ * 1 when the iterator is finished
+ * -1 on error
+ */
+int mutt_seqset_iterator_next (SEQSET_ITERATOR *iter, unsigned int *next)
+{
+ char *range_sep;
+
+ if (!iter || !next)
+ return -1;
+
+ if (iter->in_range)
+ {
+ if ((iter->down && iter->range_cur == (iter->range_end - 1)) ||
+ (!iter->down && iter->range_cur == (iter->range_end + 1)))
+ iter->in_range = 0;
+ }
+
+ if (!iter->in_range)
+ {
+ iter->substr_cur = iter->substr_end;
+ if (iter->substr_cur == iter->eostr)
+ return 1;
+
+ while (!*(iter->substr_cur))
+ iter->substr_cur++;
+ iter->substr_end = strchr (iter->substr_cur, ',');
+ if (!iter->substr_end)
+ iter->substr_end = iter->eostr;
+ else
+ *(iter->substr_end) = '\0';
+
+ range_sep = strchr (iter->substr_cur, ':');
+ if (range_sep)
+ *range_sep++ = '\0';
+
+ if (mutt_atoui (iter->substr_cur, &iter->range_cur))
+ return -1;
+ if (range_sep)
+ {
+ if (mutt_atoui (range_sep, &iter->range_end))
+ return -1;
+ }
+ else
+ iter->range_end = iter->range_cur;
+
+ iter->down = (iter->range_end < iter->range_cur);
+ iter->in_range = 1;
+ }
+
+ *next = iter->range_cur;
+ if (iter->down)
+ iter->range_cur--;
+ else
+ iter->range_cur++;
+
+ return 0;
+}
+
+void mutt_seqset_iterator_free (SEQSET_ITERATOR **p_iter)
+{
+ SEQSET_ITERATOR *iter;
+
+ if (!p_iter || !*p_iter)
+ return;
+
+ iter = *p_iter;
+ FREE (&iter->full_seqset);
+ FREE (p_iter); /* __FREE_CHECKED__ */
+}
+