]> granicus.if.org Git - mutt/commitdiff
Add utility functions for QRESYNC support.
authorKevin McCarthy <kevin@8t8.us>
Thu, 24 May 2018 20:26:35 +0000 (13:26 -0700)
committerKevin McCarthy <kevin@8t8.us>
Sun, 12 Aug 2018 01:35:21 +0000 (18:35 -0700)
* 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 3a2a4933ff39f70f54956f7d94580e8c97e1fc67..171f953d77319bbd463a12127e8773fc4add9e19 100644 (file)
@@ -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;
   }
 }
index 47a836ac61b3ca5d8dfe1b3eb1685428fca01052..1f942f5c15a59d3018bcc1fb16b07fafe4d21a1b 100644 (file)
@@ -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);
   }
index fed8bfe6e3da963a9a68ea6964e70b0273c896f7..cc3caa08f3c5f23e59dfeb3e80c86c750048ca32 100644 (file)
@@ -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);
index 85399929f4533953a0af4f6bbc45040a99801e49..6e037d67d097e40b6f12681a5760dfbec2d3b1e4 100644 (file)
@@ -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);
index c68f636397e99a9ffd41aa8813b9d8d0eae43ba4..bbf7d8d9c3ea3dc90490c8ade47f11011a6c7fd7 100644 (file)
@@ -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__ */
+}
+