]> granicus.if.org Git - neomutt/commitdiff
Add utility functions for QRESYNC support.
authorKevin McCarthy <kevin@8t8.us>
Thu, 24 May 2018 20:26:35 +0000 (13:26 -0700)
committerRichard Russon <rich@flatcap.org>
Mon, 3 Sep 2018 15:38:30 +0000 (16:38 +0100)
* 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.

imap/command.c
imap/imap.c
imap/imap_private.h
imap/message.c
imap/util.c

index b7d4a0af130954da35e265d78df8ed91814a721e..22bcc2a2ec30c93691c73177d79c70c94f746b56 100644 (file)
@@ -72,7 +72,8 @@ static const char *const Capabilities[] = {
   "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,
 };
 
 /**
@@ -274,6 +275,92 @@ static void cmd_parse_expunge(struct ImapData *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(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
@@ -860,6 +947,8 @@ static void cmd_parse_enabled(struct ImapData *idata, const char *s)
     {
       idata->unicode = 1;
     }
+    if (mutt_str_strncasecmp(s, "QRESYNC", 7) == 0)
+      idata->qresync = 1;
   }
 }
 
@@ -920,6 +1009,8 @@ static int cmd_handle_untagged(struct ImapData *idata)
     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)
index c3869ea867f515cf602442f6b90e37ee9cb027a7..27f0373b779777ca6e2623c31185f4f934dc840b 100644 (file)
@@ -985,12 +985,23 @@ struct ImapData *imap_conn_find(const struct 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);
+
     /* we may need the root delimiter before we open a mailbox */
     imap_exec(idata, NULL, IMAP_CMD_FAIL_OK);
   }
index d9880f39f8e2e0f44d245d9de2f485408b067d89..02c8399320534eb61a9ad55b4607ffa7291cc631 100644 (file)
@@ -136,6 +136,7 @@ enum ImapCaps
   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 */
 
@@ -230,6 +231,8 @@ struct ImapData
    * 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;
@@ -269,6 +272,16 @@ struct ImapData
 #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);
@@ -318,6 +331,8 @@ void imap_hcache_close(struct ImapData *idata);
 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);
@@ -335,6 +350,9 @@ void imap_quote_string(char *dest, size_t dlen, const char *src, bool quote_back
 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);
 
index be3ebf1049ad610afce0d6d705dfd167fcff57fe..9234e912f435e43a66540cfd4a88dbf2d5ae14f8 100644 (file)
@@ -530,8 +530,8 @@ static void alloc_msn_index(struct ImapData *idata, size_t 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 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 */
@@ -880,7 +880,7 @@ int imap_read_headers(struct ImapData *idata, unsigned int msn_begin, unsigned i
         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;
         }
 
@@ -913,7 +913,7 @@ int imap_read_headers(struct ImapData *idata, unsigned int msn_begin, unsigned i
     {
       /* 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);
index f90a9287303bb28a4961c0de5f70965ec70d8263..4daa38a96a68520032a389312228254dc5bf6443 100644 (file)
@@ -210,6 +210,63 @@ void imap_clean_path(char *path, size_t plen)
 }
 
 #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
  */
@@ -334,6 +391,46 @@ int imap_hcache_del(struct ImapData *idata, unsigned int uid)
   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
 
 /**
@@ -1025,3 +1122,91 @@ int imap_account_match(const struct Account *a1, const struct Account *a2)
 
   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__ */
+}