Arnt Gulbrandsen wrote:
> At any rate, I added RFC6531/6855 support to mutt. Please pull:
> 
> https://bitbucket.org/arnt/mutt/commits/a83a4387c35384eccd3a11d74b8db4ebbb185d30
> 
> Some earlier changesets need to be reverted. mutt_idna.c may have been a
> fine idea, but using idna in mail didn't get traction, and is incompatible
> with this. This patch is compatible with the latest experimental release of
> Postfix, and with what Google and Microsoft are testing.

Hi Arnt,

I've been looking at your patch.  I have different comments for the
IMAP and SMTP parts, so I'm going to separate those into two different
emails.  This email is for IMAP mailboxes part.

I've attached a revised patch that is working for me for IMAP mailboxes.
I've tested it against a non-UTF8 server (fastmail) and a UTF8 server
(gmail) both with a mailbox "你好".

Comments/changes I made:

- Added the unicode logic to the imap_unmunge_mbox_name() call.  (I'm
  assuming we shouldn't be trying to utf7 decode strings from the server
  after we've enabled utf8.  If this is incorrect, please let me know.)

- I changed the parameter to pass in idata instead of the unicode flag.
  (This was just personal preference: it seemed better to encapsulate the
  decision of what to look at inside the function, in case we need to look
  at other flags or capabilities in the future).

- The imap_utf7_encode/decode were also performing conversion from
  the local Charset to UTF-8 before doing the UTF-7 conversion.  This
  conversion needs to be performed in either case.

  Therefore I renamed them to imap_utf_encode/decode, threaded through
  idata, and put the "if" for utf-7 inside of them.  That way the
  Charset conversion will always occur.

- Changed cmd_parse_enabled() to use ascii_strncasecmp() instead of
  strcasecmp().  (See "BEWARE" in the source tree.)

I noticed the decode is taking place twice after a LIST command while
browsing.  I haven't tracked down why yet.

-- 
Kevin J. McCarthy
GPG Fingerprint: 8975 A9B3 3AA3 7910 385C  5308 ADEF 7684 8031 6BDA
http://www.8t8.us/configs/gpg-key-transition-statement.txt
# HG changeset patch
# User Kevin McCarthy <ke...@8t8.us>
# Date 1405605947 -7200
#      Thu Jul 17 16:05:47 2014 +0200
# Node ID 184712201b3474fd1e516100aa4a1262311ebba9
# Parent  b3c095648df63e74690e1c8e01c691b09a776283
Add support for utf-8 mailboxes in imap.

This adds support for RFC6855 to imap/*.c.

Thanks to Arnt Gulbrandsen for the original patch.

diff --git a/imap/browse.c b/imap/browse.c
--- a/imap/browse.c
+++ b/imap/browse.c
@@ -70,17 +70,17 @@
 
   /* skip check for parents when at the root */
   if (mx.mbox && mx.mbox[0] != '\0')
   {
     int rc;
     char *ptr;
     imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox));
     ptr = safe_strdup (mbox);
-    imap_utf7_encode (&ptr);
+    imap_utf_encode (idata, &ptr);
     mbox[sizeof (mbox) - 1] = '\0';
     strncpy (mbox, ptr, sizeof (mbox) - 1);
     FREE (&ptr);
     n = mutt_strlen (mbox);
 
     dprint (3, (debugfile, "imap_browse: mbox: %s\n", mbox));
 
     /* if our target exists and has inferiors, enter it if we
@@ -395,21 +395,24 @@
 /* imap_add_folder: add a folder name to the browser list, formatting it as
  *   necessary. */
 static void imap_add_folder (char delim, char *folder, int noselect,
   int noinferiors, struct browser_state *state, short isparent)
 {
   char tmp[LONG_STRING];
   char relpath[LONG_STRING];
   IMAP_MBOX mx;
+  IMAP_DATA* idata;
 
   if (imap_parse_path (state->folder, &mx))
     return;
+  if (!(idata = imap_conn_find (&(mx.account), 0)))
+    return;
 
-  imap_unmunge_mbox_name (folder);
+  imap_unmunge_mbox_name (idata, folder);
 
   if (state->entrylen + 1 == state->entrymax)
   {
     safe_realloc (&state->entry,
       sizeof (struct folder_file) * (state->entrymax += 256));
     memset (state->entry + state->entrylen, 0,
       (sizeof (struct folder_file) * (state->entrymax - state->entrylen)));
   }
diff --git a/imap/command.c b/imap/command.c
--- a/imap/command.c
+++ b/imap/command.c
@@ -46,30 +46,32 @@
 static void cmd_parse_capability (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);
 static void cmd_parse_fetch (IMAP_DATA* idata, char* s);
 static void cmd_parse_myrights (IMAP_DATA* idata, const char* s);
 static void cmd_parse_search (IMAP_DATA* idata, const char* s);
 static void cmd_parse_status (IMAP_DATA* idata, char* s);
+static void cmd_parse_enabled (IMAP_DATA* idata, const char* s);
 
 static const char * const Capabilities[] = {
   "IMAP4",
   "IMAP4rev1",
   "STATUS",
   "ACL", 
   "NAMESPACE",
   "AUTH=CRAM-MD5",
   "AUTH=GSSAPI",
   "AUTH=ANONYMOUS",
   "STARTTLS",
   "LOGINDISABLED",
   "IDLE",
   "SASL-IR",
+  "ENABLE",
 
   NULL
 };
 
 /* imap_cmd_start: Given an IMAP command, send it to the server.
  *   If cmdstr is NULL, sends queued commands. */
 int imap_cmd_start (IMAP_DATA* idata, const char* cmdstr)
 {
@@ -517,16 +519,18 @@
   else if (ascii_strncasecmp ("LSUB", s, 4) == 0)
     cmd_parse_lsub (idata, s);
   else if (ascii_strncasecmp ("MYRIGHTS", s, 8) == 0)
     cmd_parse_myrights (idata, s);
   else if (ascii_strncasecmp ("SEARCH", s, 6) == 0)
     cmd_parse_search (idata, s);
   else if (ascii_strncasecmp ("STATUS", s, 6) == 0)
     cmd_parse_status (idata, s);
+  else if (ascii_strncasecmp ("ENABLED", s, 7) == 0)
+    cmd_parse_enabled (idata, s);
   else if (ascii_strncasecmp ("BYE", s, 3) == 0)
   {
     dprint (2, (debugfile, "Handling BYE\n"));
 
     /* check if we're logging out */
     if (idata->status == IMAP_BYE)
       return 0;
 
@@ -723,17 +727,17 @@
     {
       idata->status = IMAP_FATAL;
       return;
     }
     list->name = idata->buf;
   }
   else
   {
-    imap_unmunge_mbox_name (s);
+    imap_unmunge_mbox_name (idata, s);
     list->name = s;
   }
 
   if (list->name[0] == '\0')
   {
     idata->delim = list->delim;
     dprint (3, (debugfile, "Root delimiter: %c\n", idata->delim));
   }
@@ -912,17 +916,17 @@
     *s = '\0';
     s++;
     SKIPWS(s);
   }
   else
   {
     s = imap_next_word (mailbox);
     *(s - 1) = '\0';
-    imap_unmunge_mbox_name (mailbox);
+    imap_unmunge_mbox_name (idata, mailbox);
   }
 
   status = imap_mboxcache_get (idata, mailbox, 1);
   olduv = status->uidvalidity;
   oldun = status->uidnext;
 
   if (*s++ != '(')
   {
@@ -1017,8 +1021,21 @@
       }
 
       FREE (&value);
     }
 
     FREE (&mx.mbox);
   }
 }
+
+/* cmd_parse_enabled: record what the server has enabled */
+static void cmd_parse_enabled (IMAP_DATA* idata, const char* s)
+{
+  dprint (2, (debugfile, "Handling ENABLED\n"));
+
+  while ((s = imap_next_word ((char*)s)) && *s != '\0')
+  {
+    if (ascii_strncasecmp(s, "UTF8=ACCEPT", 11) == 0 ||
+        ascii_strncasecmp(s, "UTF8=ONLY", 9) == 0)
+      idata->unicode = 1;
+  }
+}
diff --git a/imap/imap.c b/imap/imap.c
--- a/imap/imap.c
+++ b/imap/imap.c
@@ -87,17 +87,17 @@
   FREE (&mx.mbox);
 
   if (imap_mboxcache_get (idata, mailbox, 0))
   {
     dprint (3, (debugfile, "imap_access: found %s in cache\n", mailbox));
     return 0;
   }
 
-  imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
+  imap_munge_mbox_name (idata, mbox, sizeof (mbox), mailbox);
 
   if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
     snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
   else if (mutt_bit_isset (idata->capabilities, STATUS))
     snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
   else
   {
     dprint (2, (debugfile, "imap_access: STATUS not supported?\n"));
@@ -112,17 +112,17 @@
 
   return 0;
 }
 
 int imap_create_mailbox (IMAP_DATA* idata, char* mailbox)
 {
   char buf[LONG_STRING], mbox[LONG_STRING];
 
-  imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
+  imap_munge_mbox_name (idata, mbox, sizeof (mbox), mailbox);
   snprintf (buf, sizeof (buf), "CREATE %s", mbox);
 
   if (imap_exec (idata, buf, 0) != 0)
   {
     mutt_error (_("CREATE failed: %s"), imap_cmd_trailer (idata));
     return -1;
   }
 
@@ -130,18 +130,18 @@
 }
 
 int imap_rename_mailbox (IMAP_DATA* idata, IMAP_MBOX* mx, const char* newname)
 {
   char oldmbox[LONG_STRING];
   char newmbox[LONG_STRING];
   char buf[LONG_STRING];
 
-  imap_munge_mbox_name (oldmbox, sizeof (oldmbox), mx->mbox);
-  imap_munge_mbox_name (newmbox, sizeof (newmbox), newname);
+  imap_munge_mbox_name (idata, oldmbox, sizeof (oldmbox), mx->mbox);
+  imap_munge_mbox_name (idata, newmbox, sizeof (newmbox), newname);
 
   snprintf (buf, sizeof (buf), "RENAME %s %s", oldmbox, newmbox);
 
   if (imap_exec (idata, buf, 0) != 0)
     return -1;
 
   return 0;
 }
@@ -157,17 +157,17 @@
     {
       FREE (&mx.mbox);
       return -1;
     }
   } else {
     idata = ctx->data;
   }
 
-  imap_munge_mbox_name (mbox, sizeof (mbox), mx.mbox);
+  imap_munge_mbox_name (idata, mbox, sizeof (mbox), mx.mbox);
   snprintf (buf, sizeof (buf), "DELETE %s", mbox);
 
   if (imap_exec ((IMAP_DATA*) idata, buf, 0) != 0)
     return -1;
 
   return 0;
 }
 
@@ -381,16 +381,19 @@
     }
     else
       mutt_account_unsetpass (&idata->conn->account);
   }
   if (new && idata->state == IMAP_AUTHENTICATED)
   {
     /* 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);
     /* 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);
   }
@@ -591,17 +594,17 @@
   idata->ctx = ctx;
 
   /* clear mailbox status */
   idata->status = 0;
   memset (idata->ctx->rights, 0, sizeof (idata->ctx->rights));
   idata->newMailCount = 0;
 
   mutt_message (_("Selecting %s..."), idata->mailbox);
-  imap_munge_mbox_name (buf, sizeof(buf), idata->mailbox);
+  imap_munge_mbox_name (idata, buf, sizeof(buf), idata->mailbox);
 
   /* pipeline ACL test */
   if (mutt_bit_isset (idata->capabilities, ACL))
   {
     snprintf (bufout, sizeof (bufout), "MYRIGHTS %s", buf);
     imap_exec (idata, bufout, IMAP_CMD_QUEUE);
   }
   /* assume we have all rights if ACL is unavailable */
@@ -1516,17 +1519,17 @@
         dprint (1, (debugfile, "Error polling mailboxes\n"));
 
       lastdata = NULL;
     }
 
     if (!lastdata)
       lastdata = idata;
 
-    imap_munge_mbox_name (munged, sizeof (munged), name);
+    imap_munge_mbox_name (idata, munged, sizeof (munged), name);
     snprintf (command, sizeof (command),
              "STATUS %s (UIDNEXT UIDVALIDITY UNSEEN RECENT)", munged);
 
     if (imap_exec (idata, command, IMAP_CMD_QUEUE) < 0)
     {
       dprint (1, (debugfile, "Error queueing command\n"));
       return 0;
     }
@@ -1564,19 +1567,19 @@
     return -1;
 
   if (!imap_mxcmp (buf, idata->mailbox))
     /* We are in the folder we're polling - just return the mailbox count */
     return idata->ctx->msgcount;
   else if (mutt_bit_isset(idata->capabilities,IMAP4REV1) ||
           mutt_bit_isset(idata->capabilities,STATUS))
   {
-    imap_munge_mbox_name (mbox, sizeof(mbox), buf);
+    imap_munge_mbox_name (idata, mbox, sizeof(mbox), buf);
     snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox, "MESSAGES");
-    imap_unmunge_mbox_name (mbox);
+    imap_unmunge_mbox_name (idata, mbox);
   }
   else
     /* Server does not support STATUS, and this is not the current mailbox.
      * There is no lightweight way to check recent arrivals */
     return -1;
 
   if (queue)
   {
@@ -1846,24 +1849,24 @@
       dprint (1, (debugfile, "Error adding subscribed mailbox: %s\n", errstr));
     FREE (&token.data);
   }
 
   if (subscribe)
     mutt_message (_("Subscribing to %s..."), buf);
   else
     mutt_message (_("Unsubscribing from %s..."), buf);
-  imap_munge_mbox_name (mbox, sizeof(mbox), buf);
+  imap_munge_mbox_name (idata, mbox, sizeof(mbox), buf);
 
   snprintf (buf, sizeof (buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mbox);
 
   if (imap_exec (idata, buf, 0) < 0)
     goto fail;
 
-  imap_unmunge_mbox_name(mx.mbox);
+  imap_unmunge_mbox_name(idata, mx.mbox);
   if (subscribe)
     mutt_message (_("Subscribed to %s"), mx.mbox);
   else
     mutt_message (_("Unsubscribed from %s"), mx.mbox);
   FREE (&mx.mbox);
   return 0;
 
  fail:
diff --git a/imap/imap_private.h b/imap/imap_private.h
--- a/imap/imap_private.h
+++ b/imap/imap_private.h
@@ -110,16 +110,17 @@
   NAMESPACE,                           /* RFC 2342: IMAP4 Namespace */
   ACRAM_MD5,                   /* RFC 2195: CRAM-MD5 authentication */
   AGSSAPI,                     /* RFC 1731: GSSAPI authentication */
   AUTH_ANON,                   /* AUTH=ANONYMOUS */
   STARTTLS,                    /* RFC 2595: STARTTLS */
   LOGINDISABLED,               /*           LOGINDISABLED */
   IDLE,                         /* RFC 2177: IDLE */
   SASL_IR,                      /* SASL initial response draft */
+  ENABLE,                       /* RFC 5161 */
 
   CAPMAX
 };
 
 /* imap_conn_find flags */
 #define M_IMAP_CONN_NONEW    (1<<0)
 #define M_IMAP_CONN_NOSELECT (1<<1)
 
@@ -139,17 +140,17 @@
   unsigned int uidnext;
   unsigned int uidvalidity;
   unsigned int unseen;
 } IMAP_STATUS;
 
 typedef struct
 {
   char* name;
-  
+
   char delim;
   /* if we end up storing a lot of these we could turn this into a bitfield */
   unsigned char noselect;
   unsigned char noinferiors;
 } IMAP_LIST;
 
 /* IMAP command structure */
 typedef struct
@@ -181,16 +182,20 @@
    * it's just no fun to get the same information twice */
   char* capstr;
   unsigned char capabilities[(CAPMAX + 7)/8];
   unsigned int seqno;
   time_t lastread; /* last time we read a command for the server */
   char* buf;
   unsigned int blen;
   
+  /* If nonzero, we can send UTF-8, and the server will use UTF8 rather
+   * than mUTF7 */
+  int unicode;
+
   /* if set, the response parser will store results for complicated commands
    * here. */
   IMAP_COMMAND_TYPE cmdtype;
   void* cmddata;
 
   /* command queue */
   IMAP_COMMAND* cmds;
   int cmdslots;
@@ -284,22 +289,22 @@
 char* imap_get_qualifier (char* buf);
 int imap_mxcmp (const char* mx1, const char* mx2);
 char* imap_next_word (char* s);
 time_t imap_parse_date (char* s);
 void imap_make_date (char* buf, time_t timestamp);
 void imap_qualify_path (char *dest, size_t len, IMAP_MBOX *mx, char* path);
 void imap_quote_string (char* dest, size_t slen, const char* src);
 void imap_unquote_string (char* s);
-void imap_munge_mbox_name (char *dest, size_t dlen, const char *src);
-void imap_unmunge_mbox_name (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);
 
 /* utf7.c */
-void imap_utf7_encode (char **s);
-void imap_utf7_decode (char **s);
+void imap_utf_encode (IMAP_DATA *idata, char **s);
+void imap_utf_decode (IMAP_DATA *idata, char **s);
 
 #if USE_HCACHE
 /* typedef size_t (*hcache_keylen_t)(const char* fn); */
 #define imap_hcache_keylen mutt_strlen
 #endif /* USE_HCACHE */
 
 #endif
diff --git a/imap/message.c b/imap/message.c
--- a/imap/message.c
+++ b/imap/message.c
@@ -637,17 +637,17 @@
 
     len++;
   }
   rewind (fp);
 
   mutt_progress_init (&progressbar, _("Uploading message..."),
                      M_PROGRESS_SIZE, NetInc, len);
 
-  imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
+  imap_munge_mbox_name (idata, mbox, sizeof (mbox), mailbox);
   imap_make_date (internaldate, msg->received);
 
   imap_flags[0] = imap_flags[1] = 0;
   if (msg->flags.read)
     safe_strcat (imap_flags, sizeof (imap_flags), " \\Seen");
   if (msg->flags.replied)
     safe_strcat (imap_flags, sizeof (imap_flags), " \\Answered");
   if (msg->flags.flagged)
@@ -768,17 +768,17 @@
   {
     dprint (3, (debugfile, "imap_copy_messages: Message contains attachments 
to be deleted\n"));
     return 1;
   }
 
   imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox));
   if (!*mbox)
     strfcpy (mbox, "INBOX", sizeof (mbox));
-  imap_munge_mbox_name (mmbox, sizeof (mmbox), mbox);
+  imap_munge_mbox_name (idata, mmbox, sizeof (mmbox), mbox);
 
   /* loop in case of TRYCREATE */
   do
   {
     mutt_buffer_init (&sync_cmd);
     mutt_buffer_init (&cmd);
 
     /* Null HEADER* means copy tagged messages */
diff --git a/imap/utf7.c b/imap/utf7.c
--- a/imap/utf7.c
+++ b/imap/utf7.c
@@ -247,35 +247,45 @@
   if (u7)  *u7 = buf;
   return buf;
 
  bail:
   FREE (&buf);
   return 0;
 }
 
-void imap_utf7_encode (char **s)
+void imap_utf_encode (IMAP_DATA *idata, char **s)
 {
   if (Charset)
   {
     char *t = safe_strdup (*s);
-    if (!mutt_convert_string (&t, Charset, "utf-8", 0))
+    if (t && !mutt_convert_string (&t, Charset, "utf-8", 0))
     {
-      char *u7 = utf8_to_utf7 (t, strlen (t), NULL, 0);
       FREE (s);                /* __FREE_CHECKED__ */
-      *s = u7;
+      if (idata->unicode)
+        *s = safe_strdup (t);
+      else
+        *s = utf8_to_utf7 (t, strlen (t), NULL, 0);
     }
     FREE (&t);
   }
 }
 
-void imap_utf7_decode (char **s)
+void imap_utf_decode (IMAP_DATA *idata, char **s)
 {
+  char *t;
+
   if (Charset)
   {
-    char *t = utf7_to_utf8 (*s, strlen (*s), 0, 0);
+    if (idata->unicode)
+      t = safe_strdup (*s);
+    else
+      t = utf7_to_utf8 (*s, strlen (*s), 0, 0);
+
     if (t && !mutt_convert_string (&t, "utf-8", Charset, 0))
     {
       FREE (s);                /* __FREE_CHECKED__ */
       *s = t;
     }
+    else
+      FREE (&t);
   }
 }
diff --git a/imap/util.c b/imap/util.c
--- a/imap/util.c
+++ b/imap/util.c
@@ -668,42 +668,43 @@
       *d = *s;
       d++;
       s++;
     }
   }
   *d = '\0';
 }
 
+
 /*
  * Quoting and UTF-7 conversion
  */
 
-void imap_munge_mbox_name (char *dest, size_t dlen, const char *src)
+void imap_munge_mbox_name (IMAP_DATA *idata, char *dest, size_t dlen, const 
char *src)
 {
   char *buf;
 
   buf = safe_strdup (src);
-  imap_utf7_encode (&buf);
+  imap_utf_encode (idata, &buf);
 
   imap_quote_string (dest, dlen, buf);
 
   FREE (&buf);
 }
 
-void imap_unmunge_mbox_name (char *s)
+void imap_unmunge_mbox_name (IMAP_DATA *idata, char *s)
 {
   char *buf;
 
   imap_unquote_string(s);
 
   buf = safe_strdup (s);
   if (buf)
   {
-    imap_utf7_decode (&buf);
+    imap_utf_decode (idata, &buf);
     strncpy (s, buf, strlen (s));
   }
 
   FREE (&buf);
 }
 
 /* imap_wordcasecmp: find word a in word list b */
 int imap_wordcasecmp(const char *a, const char *b)

Attachment: signature.asc
Description: PGP signature

Reply via email to