* 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.
"AUTH=GSSAPI", "AUTH=ANONYMOUS", "AUTH=OAUTHBEARER",
"STARTTLS", "LOGINDISABLED", "IDLE",
"SASL-IR", "ENABLE", "CONDSTORE",
- "X-GM-EXT-1", "X-GM-EXT1", NULL,
+ "QRESYNC", "X-GM-EXT-1", "X-GM-EXT1",
+ NULL,
};
/**
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(struct ImapData *idata, char *s)
+{
+ int earlier = 0, rc;
+ char *end_of_seqset;
+ struct SeqsetIterator *iter;
+ unsigned int uid, exp_msn, cur;
+ struct Header *h;
+
+ mutt_debug(2, "Handling VANISHED\n");
+
+ if (mutt_str_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)
+ {
+ mutt_debug(2, "VANISHED: empty seqset [%s]?\n", s);
+ return;
+ }
+
+ while ((rc = mutt_seqset_iterator_next(iter, &uid)) == 0)
+ {
+ h = (struct Header *) mutt_hash_int_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)
+ {
+ mutt_debug(1, "VANISHED: msn for UID %u is incorrect.\n", uid);
+ continue;
+ }
+ if (idata->msn_index[exp_msn - 1] != h)
+ {
+ mutt_debug(1, "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)
+ mutt_debug(1, "VANISHED: illegal seqset %s\n", s);
+
+ idata->reopen |= IMAP_EXPUNGE_PENDING;
+
+ mutt_seqset_iterator_free(&iter);
+}
+
/**
* cmd_parse_fetch - Load fetch response into ImapData
* @param idata Server data
{
idata->unicode = 1;
}
+ if (mutt_str_strncasecmp(s, "QRESYNC", 7) == 0)
+ idata->qresync = 1;
}
}
else if (mutt_str_strncasecmp("FETCH", s, 5) == 0)
cmd_parse_fetch(idata, pn);
}
+ else if ((idata->state >= IMAP_SELECTED) && mutt_str_strncasecmp("VANISHED", s, 8) == 0)
+ cmd_parse_vanished(idata, pn);
else if (mutt_str_strncasecmp("CAPABILITY", s, 10) == 0)
cmd_parse_capability(idata, s);
else if (mutt_str_strncasecmp("OK [CAPABILITY", s, 14) == 0)
{
/* 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);
+
/* 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, /**< RFC5161 */
CONDSTORE, /**< RFC7162 */
+ QRESYNC, /**< RFC7162 */
X_GM_EXT1, /**< https://developers.google.com/gmail/imap/imap-extensions */
X_GM_ALT1 = X_GM_EXT1, /**< Alternative capability string */
* 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. */
enum ImapCommandType cmdtype;
#endif
};
+struct SeqsetIterator
+{
+ char *full_seqset;
+ char *eostr;
+ int in_range;
+ int down;
+ unsigned int range_cur, range_end;
+ char *substr_cur, *substr_end;
+};
+
/* -- private IMAP functions -- */
/* imap.c */
int imap_check(struct ImapData *idata, bool force);
struct Header *imap_hcache_get(struct ImapData *idata, unsigned int uid);
int imap_hcache_put(struct ImapData *idata, struct Header *h);
int imap_hcache_del(struct ImapData *idata, unsigned int uid);
+int imap_hcache_store_uid_seqset (struct ImapData *idata);
+char *imap_hcache_get_uid_seqset (struct ImapData *idata);
#endif
int imap_continue(const char *msg, const char *resp);
void imap_unquote_string(char *s);
void imap_munge_mbox_name(struct ImapData *idata, char *dest, size_t dlen, const char *src);
void imap_unmunge_mbox_name(struct ImapData *idata, char *s);
+struct SeqsetIterator *mutt_seqset_iterator_new (const char *seqset);
+int mutt_seqset_iterator_next (struct SeqsetIterator *iter, unsigned int *next);
+void mutt_seqset_iterator_free (struct SeqsetIterator **p_iter);
int imap_account_match(const struct Account *a1, const struct Account *a2);
void imap_get_parent(const char *mbox, char delim, char *buf, size_t buflen);
* 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 generate_seqset(struct Buffer *b, struct ImapData *idata,
- unsigned int msn_begin, unsigned int msn_end)
+static void imap_fetch_msn_seqset(struct Buffer *b, struct ImapData *idata,
+ unsigned int msn_begin, unsigned int msn_end)
{
int chunks = 0;
int state = 0; /* 1: single msn, 2: range of msn */
if (header_msn < 1 || header_msn > msn_end || !idata->msn_index[header_msn - 1])
{
mutt_debug(1, "imap_read_headers: skipping CONDSTORE flag update for unknown message number %u\n",
- header_msn);
+ header_msn);
continue;
}
{
/* In case there are holes in the header cache. */
evalhc = false;
- 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(struct Buffer *b, struct ImapData *idata)
+{
+ int first = 1, state = 0, match = 0;
+ struct 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;
+ }
+ }
+}
+
/**
* imap_hcache_namer - Generate a filename for the header cache - Implements ::hcache_namer_t
*/
sprintf(key, "/%u", uid);
return mutt_hcache_delete(idata->hcache, key, imap_hcache_keylen(key));
}
+
+int imap_hcache_store_uid_seqset(struct ImapData *idata)
+{
+ struct 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", 10, b->data,
+ seqset_size + 1);
+ mutt_debug(5, "Stored /UIDSEQSET %s\n", b->data);
+ mutt_buffer_free(&b);
+ return rc;
+}
+
+char *imap_hcache_get_uid_seqset(struct ImapData *idata)
+{
+ char *hc_seqset, *seqset;
+
+ if (!idata->hcache)
+ return NULL;
+
+ hc_seqset = mutt_hcache_fetch_raw(idata->hcache, "/UIDSEQSET", 10);
+ seqset = mutt_str_strdup(hc_seqset);
+ mutt_hcache_free(idata->hcache, (void **) &hc_seqset);
+ mutt_debug(5, "Retrieved /UIDSEQSET %s\n", NONULL(seqset));
+
+ return seqset;
+}
#endif
/**
return mutt_account_match(a1_canon, a2_canon);
}
+
+/* Sequence set iteration */
+
+struct SeqsetIterator *mutt_seqset_iterator_new(const char *seqset)
+{
+ struct SeqsetIterator *iter;
+
+ if (!seqset || !*seqset)
+ return NULL;
+
+ iter = mutt_mem_calloc(1, sizeof(struct SeqsetIterator));
+ iter->full_seqset = mutt_str_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(struct SeqsetIterator *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_str_atoui(iter->substr_cur, &iter->range_cur))
+ return -1;
+ if (range_sep)
+ {
+ if (mutt_str_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(struct SeqsetIterator **p_iter)
+{
+ struct SeqsetIterator *iter;
+
+ if (!p_iter || !*p_iter)
+ return;
+
+ iter = *p_iter;
+ FREE(&iter->full_seqset);
+ FREE(p_iter); /* __FREE_CHECKED__ */
+}