From: Kevin McCarthy Date: Thu, 24 May 2018 20:26:35 +0000 (-0700) Subject: Add utility functions for QRESYNC support. X-Git-Tag: mutt-1-11-rel~98 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=868aaa48aa43f5090fa57d0b2584bd95a5e94631;p=mutt Add utility functions for QRESYNC support. * 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. --- diff --git a/imap/command.c b/imap/command.c index 3a2a4933..171f953d 100644 --- a/imap/command.c +++ b/imap/command.c @@ -46,6 +46,7 @@ static int cmd_status (const char *s); 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); @@ -71,6 +72,7 @@ static const char * const Capabilities[] = { "SASL-IR", "ENABLE", "CONDSTORE", + "QRESYNC", NULL }; @@ -548,6 +550,9 @@ static int cmd_handle_untagged (IMAP_DATA* idata) 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)) @@ -662,6 +667,94 @@ static void cmd_parse_expunge (IMAP_DATA* idata, const char* s) 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. @@ -1149,5 +1242,7 @@ static void cmd_parse_enabled (IMAP_DATA* idata, const char* s) 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; } } diff --git a/imap/imap.c b/imap/imap.c index 47a836ac..1f942f5c 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -415,14 +415,25 @@ IMAP_DATA* imap_conn_find (const ACCOUNT* account, int flags) { /* 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); } diff --git a/imap/imap_private.h b/imap/imap_private.h index fed8bfe6..cc3caa08 100644 --- a/imap/imap_private.h +++ b/imap/imap_private.h @@ -119,6 +119,7 @@ enum SASL_IR, /* SASL initial response draft */ ENABLE, /* RFC 5161 */ CONDSTORE, /* RFC 7162 */ + QRESYNC, /* RFC 7162 */ CAPMAX }; @@ -195,6 +196,8 @@ typedef struct * 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; @@ -235,6 +238,16 @@ typedef struct } 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) @@ -288,6 +301,8 @@ void imap_hcache_close (IMAP_DATA* idata); 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); @@ -311,6 +326,9 @@ void imap_unquote_string (char* s); 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); diff --git a/imap/message.c b/imap/message.c index 85399929..6e037d67 100644 --- a/imap/message.c +++ b/imap/message.c @@ -108,8 +108,8 @@ static void imap_alloc_msn_index (IMAP_DATA *idata, unsigned int msn_count) * 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 */ @@ -451,7 +451,7 @@ int imap_read_headers (IMAP_DATA* idata, unsigned int msn_begin, unsigned int ms { /* 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); diff --git a/imap/util.c b/imap/util.c index c68f6363..bbf7d8d9 100644 --- a/imap/util.c +++ b/imap/util.c @@ -73,6 +73,64 @@ int imap_expand_path (char* path, size_t len) } #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); @@ -165,6 +223,48 @@ int imap_hcache_del (IMAP_DATA* idata, unsigned int uid) 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 @@ -871,3 +971,92 @@ int imap_account_match (const ACCOUNT* a1, const ACCOUNT* a2) 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__ */ +} +