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)
signature.asc
Description: PGP signature