]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
imap: Expect and produce UTF8 mailbox names when UTF8=ACCEPT is enabled
authorStephan Bosch <stephan.bosch@open-xchange.com>
Wed, 27 Aug 2025 00:20:50 +0000 (02:20 +0200)
committerStephan Bosch <stephan.bosch@open-xchange.com>
Mon, 26 Jan 2026 01:58:58 +0000 (02:58 +0100)
16 files changed:
src/imap/cmd-fetch.c
src/imap/cmd-getmetadata.c
src/imap/cmd-list.c
src/imap/cmd-namespace.c
src/imap/cmd-notify.c
src/imap/cmd-search.c
src/imap/cmd-select.c
src/imap/imap-client.c
src/imap/imap-client.h
src/imap/imap-commands-util.c
src/imap/imap-fetch.c
src/imap/imap-fetch.h
src/imap/imap-notify.c
src/imap/imap-notify.h
src/imap/imap-search.c
src/imap/imap-state.c

index 35e85d15d366e37facb72684b52d8a1f81c2f105..4fe682698b4be0ae25c2b2b8345f57b1aace357e 100644 (file)
@@ -355,7 +355,7 @@ bool cmd_fetch(struct client_command_context *cmd)
                return ret < 0;
 
        ctx = imap_fetch_alloc(client, cmd->pool,
-                              imap_client_command_get_reason(cmd));
+                              imap_client_command_get_reason(cmd), cmd->utf8);
 
        if (!fetch_parse_args(ctx, cmd, &args[1], &next_arg) ||
            (imap_arg_get_list(next_arg, &list_arg) &&
index 0b34be3a6554509c09eb7891b5bf482b7943f038..1f913a4271079d28bcc887e6a2545f31e370bba2 100644 (file)
@@ -120,6 +120,8 @@ metadata_add_entry(struct imap_getmetadata_context *ctx, const char *entry)
                if (ctx->box == NULL) {
                        /* server metadata reply */
                        str_append(str, "\"\"");
+               } else if (ctx->cmd->utf8) {
+                       imap_append_astring(str, mailbox_get_vname(ctx->box), FALSE);
                } else {
                        if (imap_utf8_to_utf7(mailbox_get_vname(ctx->box), mailbox_mutf7) < 0)
                                i_unreached();
index 993740b31ddf151606d27ce874498b9cabeae78a..dc56c5ee39efcdef9638bdc71f33654d9b2f0e3e 100644 (file)
@@ -298,9 +298,12 @@ static bool cmd_list_continue(struct client_command_context *cmd)
                        continue;
                }
 
-               str_truncate(mutf7_name, 0);
-               if (imap_utf8_to_utf7(name, mutf7_name) < 0)
-                       i_panic("LIST: Mailbox name not UTF-8: %s", name);
+               if (!cmd->utf8) {
+                       str_truncate(mutf7_name, 0);
+                       if (imap_utf8_to_utf7(name, mutf7_name) < 0)
+                               i_panic("LIST: Mailbox name not UTF-8: %s", name);
+                       name = str_c(mutf7_name);
+               }
 
                str_truncate(str, 0);
                str_printfa(str, "* %s (", ctx->lsub ? "LSUB" : "LIST");
@@ -309,7 +312,7 @@ static bool cmd_list_continue(struct client_command_context *cmd)
                list_reply_append_ns_sep_param(str,
                        mail_namespace_get_sep(info->ns));
                str_append_c(str, ' ');
-               imap_append_astring(str, str_c(mutf7_name), 0);
+               imap_append_astring(str, name, 0);
                mailbox_childinfo2str(ctx, str, flags);
 
                /* send LIST/LSUB response */
@@ -320,8 +323,8 @@ static bool cmd_list_continue(struct client_command_context *cmd)
                /* send optional responses (if any) */
                if (list_has_options(ctx)) {
                        struct imap_list_return_flag_params params = {
-                               .name = name,
-                               .mutf7_name = str_c(mutf7_name),
+                               .name = info->vname,
+                               .mutf7_name = name,
                                .mbox_flags = flags,
                                .list_flags = ctx->list_flags,
                        };
@@ -385,7 +388,7 @@ static void cmd_list_init(struct cmd_list_context *ctx,
                                                  ctx->list_flags);
 }
 
-static void cmd_list_ref_root(struct client *client, const char *ref)
+static void cmd_list_ref_root(struct client *client, const char *ref, bool utf8)
 {
        struct mail_namespace *ns;
        const char *ns_prefix;
@@ -397,7 +400,10 @@ static void cmd_list_ref_root(struct client *client, const char *ref)
           Otherwise we'll emulate UW-IMAP behavior. */
        ns = mail_namespace_find_visible(client->user->namespaces, ref);
        if (ns != NULL) {
-               ns_prefix = ns_prefix_mutf7(ns);
+               if (utf8)
+                       ns_prefix = ns->prefix;
+               else
+                       ns_prefix = ns_prefix_mutf7(ns);
                ns_sep = mail_namespace_get_sep(ns);
        } else {
                ns_prefix = "";
@@ -463,10 +469,14 @@ bool cmd_list_full(struct client_command_context *cmd, bool lsub)
                return TRUE;
        }
        str = t_str_new(64);
-       if (imap_utf7_to_utf8(ref, str) == 0)
+       if (cmd->utf8) {
+               if (!uni_utf8_str_is_valid(ref))
+                       invalid_ref = TRUE;
+       } else if (imap_utf7_to_utf8(ref, str) == 0) {
                ref = p_strdup(cmd->pool, str_c(str));
-       else
+       } else {
                invalid_ref = TRUE;
+       }
        str_truncate(str, 0);
 
        if (imap_arg_get_list_full(&args[1], &list_args, &arg_count)) {
@@ -479,7 +489,10 @@ bool cmd_list_full(struct client_command_context *cmd, bool lsub)
                                        "Invalid pattern list.");
                                return TRUE;
                        }
-                       if (imap_utf7_to_utf8(pattern, str) == 0) {
+                       if (cmd->utf8) {
+                               if (uni_utf8_str_is_valid(pattern))
+                                       array_push_back(&patterns, &pattern);
+                       } else if (imap_utf7_to_utf8(pattern, str) == 0) {
                                pattern = p_strdup(cmd->pool, str_c(str));
                                array_push_back(&patterns, &pattern);
                        }
@@ -492,7 +505,10 @@ bool cmd_list_full(struct client_command_context *cmd, bool lsub)
                        return TRUE;
                }
                p_array_init(&patterns, cmd->pool, 1);
-               if (imap_utf7_to_utf8(pattern, str) == 0) {
+               if (cmd->utf8) {
+                       if (uni_utf8_str_is_valid(pattern))
+                               array_push_back(&patterns, &pattern);
+               } else if (imap_utf7_to_utf8(pattern, str) == 0) {
                        pattern = p_strdup(cmd->pool, str_c(str));
                        array_push_back(&patterns, &pattern);
                }
@@ -540,10 +556,13 @@ bool cmd_list_full(struct client_command_context *cmd, bool lsub)
 
        array_append_zero(&patterns); /* NULL-terminate */
        patterns_strarr = array_front(&patterns);
-       if (invalid_ref || patterns_strarr[0] == NULL ||
-           (!ctx->used_listext && !lsub && *patterns_strarr[0] == '\0')) {
-               /* Only LIST ref "" gets us here, or invalid ref/pattern */
-               cmd_list_ref_root(client, ref);
+       if (invalid_ref || patterns_strarr[0] == NULL) {
+               /* invalid ref/pattern */
+               client_send_tagline(cmd, "OK List completed.");
+       } else if (!ctx->used_listext && !lsub &&
+                  *patterns_strarr[0] == '\0') {
+               /* Only LIST ref "" gets us here */
+               cmd_list_ref_root(client, ref, cmd->utf8);
                client_send_tagline(cmd, "OK List completed.");
        } else {
                patterns_strarr =
index 0ae67fff1e56f3c45b24fcbf023c8eeb3e530d34..5822dd8a4284b0bf26ee8800c05f8b4c378cfa12 100644 (file)
@@ -27,8 +27,9 @@ static int namespace_order_cmp(const struct namespace_order *no1,
        return 0;
 }
 
-static void list_namespaces(struct mail_namespace *ns,
-                           enum mail_namespace_type type, string_t *str)
+static void
+list_namespaces(struct mail_namespace *ns, enum mail_namespace_type type,
+               string_t *str, bool utf8)
 {
        ARRAY(struct namespace_order) ns_order;
        struct namespace_order *no;
@@ -54,19 +55,24 @@ static void list_namespaces(struct mail_namespace *ns,
        }
        array_sort(&ns_order, namespace_order_cmp);
 
-       mutf7_prefix = t_str_new(64);
+       mutf7_prefix = (utf8 ? NULL : t_str_new(64));
        str_append_c(str, '(');
        array_foreach_modifiable(&ns_order, no) {
+               const char *prefix = no->ns->prefix;
+
                ns_sep = mail_namespace_get_sep(no->ns);
                str_append_c(str, '(');
 
-               str_truncate(mutf7_prefix, 0);
-               if (imap_utf8_to_utf7(no->ns->prefix, mutf7_prefix) < 0) {
-                       i_panic("LIST: Namespace prefix not UTF-8: %s",
-                               no->ns->prefix);
+               if (!utf8) {
+                       str_truncate(mutf7_prefix, 0);
+                       if (imap_utf8_to_utf7(prefix, mutf7_prefix) < 0) {
+                               i_panic("LIST: Namespace prefix not UTF-8: %s",
+                                       prefix);
+                       }
+                       prefix = str_c(mutf7_prefix);
                }
 
-               imap_append_string(str, str_c(mutf7_prefix), 0);
+               imap_append_string(str, prefix, 0);
                str_append(str, " \"");
                if (ns_sep == '\\')
                        str_append_c(str, '\\');
@@ -85,13 +91,13 @@ bool cmd_namespace(struct client_command_context *cmd)
        str_append(str, "* NAMESPACE ");
 
        list_namespaces(client->user->namespaces,
-                       MAIL_NAMESPACE_TYPE_PRIVATE, str);
+                       MAIL_NAMESPACE_TYPE_PRIVATE, str, cmd->utf8);
        str_append_c(str, ' ');
        list_namespaces(client->user->namespaces,
-                       MAIL_NAMESPACE_TYPE_SHARED, str);
+                       MAIL_NAMESPACE_TYPE_SHARED, str, cmd->utf8);
        str_append_c(str, ' ');
        list_namespaces(client->user->namespaces,
-                       MAIL_NAMESPACE_TYPE_PUBLIC, str);
+                       MAIL_NAMESPACE_TYPE_PUBLIC, str, cmd->utf8);
 
        client_send_line(client, str_c(str));
        client_send_tagline(cmd, "OK Namespace completed.");
index caabf62d2ba6578ce0b5e0d2d566d50c7579564f..47cc979890773b45dd5e010d391aad2b8132a005 100644 (file)
@@ -46,7 +46,8 @@ cmd_notify_parse_fetch(struct imap_notify_context *ctx,
        if (list->type == IMAP_ARG_EOL)
                return -1; /* at least one attribute must be set */
        return imap_fetch_att_list_parse(ctx->client, ctx->pool, list,
-                                        &ctx->fetch_ctx, &ctx->error);
+                                        ctx->utf8, &ctx->fetch_ctx,
+                                        &ctx->error);
 }
 
 static bool
@@ -470,10 +471,12 @@ imap_notify_box_send_status(struct client_command_context *cmd,
        } else {
                const char *vname = info->vname;
 
-               string_t *mutf7_vname = t_str_new(128);
-               if (imap_utf8_to_utf7(vname, mutf7_vname) < 0)
-                       i_panic("Mailbox name not UTF-8: %s", vname);
-               vname = str_c(mutf7_vname);
+               if (!cmd->utf8) {
+                       string_t *mutf7_vname = t_str_new(128);
+                       if (imap_utf8_to_utf7(vname, mutf7_vname) < 0)
+                               i_panic("Mailbox name not UTF-8: %s", vname);
+                       vname = str_c(mutf7_vname);
+               }
                imap_status_send(client, vname, &items, &result);
        }
        mailbox_free(&box);
@@ -550,6 +553,7 @@ bool cmd_notify(struct client_command_context *cmd)
        ctx = p_new(pool, struct imap_notify_context, 1);
        ctx->pool = pool;
        ctx->client = cmd->client;
+       ctx->utf8 = cmd->utf8;
        p_array_init(&ctx->namespaces, pool, 4);
 
        if (!imap_arg_get_atom(&args[0], &str))
index 616193159267c6d9889068b8072d2d9a84cb2313..04998f751ba433ca40c08500378937ec423b43e1 100644 (file)
@@ -28,7 +28,7 @@ bool cmd_search(struct client_command_context *cmd)
 
        if (imap_arg_atom_equals(args, "CHARSET")) {
                /* CHARSET specified */
-               if ((client_enabled_mailbox_features(cmd->client) & MAILBOX_FEATURE_UTF8ACCEPT) != 0) {
+               if (cmd->utf8) {
                        /* RFC 6855 Section 3 bans CHARSET after UTF8=ACCEPT */
                        client_send_command_error(cmd,
                                "Cannot set search charset when using UTF8=ACCEPT");
index 3e9baae399152c7482b421443133446027d9fe0b..9e3d5e0cb04fcb1272be7812b49481e4281e56ab 100644 (file)
@@ -247,7 +247,8 @@ static int select_qresync(struct imap_select_context *ctx)
        }
 
        fetch_ctx = imap_fetch_alloc(ctx->cmd->client, ctx->cmd->pool,
-               t_strdup_printf("%s %s", ctx->cmd->name, ctx->cmd->args));
+               t_strdup_printf("%s %s", ctx->cmd->name, ctx->cmd->args),
+               ctx->cmd->utf8);
 
        imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_uid_init);
        imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init);
index f09807fe847d8b8eb5da6aa39a255aac1339c36e..d68a8cab2139ee9fcf6cb84ee0836ea32fdd85fc 100644 (file)
@@ -954,6 +954,7 @@ struct client_command_context *client_command_alloc(struct client *client)
        cmd->stats.last_run_timeval = ioloop_timeval;
        cmd->stats.start_ioloop_wait_usecs =
                io_loop_get_wait_usecs(current_ioloop);
+       cmd->utf8 = client_has_enabled(client, imap_feature_utf8accept);
        p_array_init(&cmd->module_contexts, cmd->pool, 5);
 
        DLLIST_PREPEND(&client->command_queue, cmd);
index c14b95deb5b13786781b66313f49ad237335e1d1..fb48c567c9e961e9673cc2d66705283c31f960cb 100644 (file)
@@ -127,6 +127,7 @@ struct client_command_context {
        bool tagline_sent:1;
        bool executing:1;
        bool internal:1;
+       bool utf8:1; /* status of UTF8=ACCEPT feature at command alloc */
 };
 
 struct imap_client_vfuncs {
index 4647809cc4b37ca6391041e90d83632d19c57938..54cbd9801234e24e2d67e7b37f697772302b2b01 100644 (file)
@@ -24,7 +24,13 @@ client_find_namespace_full(struct client_command_context *cmd,
        string_t *utf8_name;
 
        utf8_name = t_str_new(64);
-       if (imap_utf7_to_utf8(*mailbox, utf8_name) < 0) {
+       if (cmd->utf8) {
+               if (!uni_utf8_str_is_valid(*mailbox)) {
+                       *client_error_r = "NO Mailbox name is not valid UTF-8";
+                       return NULL;
+               }
+               str_append(utf8_name, *mailbox);
+       } else if (imap_utf7_to_utf8(*mailbox, utf8_name) < 0) {
                *client_error_r = "NO Mailbox name is not valid mUTF-7";
                return NULL;
        }
index fb8a0bb60ae0fae7ab2b27e29f393827dcb5ace0..0dca6079052f169e09e06ae624e5caebee2b7bc2 100644 (file)
@@ -94,7 +94,7 @@ void imap_fetch_init_nofail_handler(struct imap_fetch_context *ctx,
 }
 
 int imap_fetch_att_list_parse(struct client *client, pool_t pool,
-                             const struct imap_arg *list,
+                             const struct imap_arg *list, bool utf8,
                              struct imap_fetch_context **fetch_ctx_r,
                              const char **client_error_r)
 {
@@ -102,7 +102,7 @@ int imap_fetch_att_list_parse(struct client *client, pool_t pool,
        const char *str;
 
        i_zero(&init_ctx);
-       init_ctx.fetch_ctx = imap_fetch_alloc(client, pool, "NOTIFY");
+       init_ctx.fetch_ctx = imap_fetch_alloc(client, pool, "NOTIFY", utf8);
        init_ctx.pool = pool;
        init_ctx.args = list;
 
@@ -126,7 +126,8 @@ int imap_fetch_att_list_parse(struct client *client, pool_t pool,
 }
 
 struct imap_fetch_context *
-imap_fetch_alloc(struct client *client, pool_t pool, const char *reason)
+imap_fetch_alloc(struct client *client, pool_t pool, const char *reason,
+                bool utf8)
 {
        struct imap_fetch_context *ctx;
 
@@ -134,6 +135,7 @@ imap_fetch_alloc(struct client *client, pool_t pool, const char *reason)
        ctx->client = client;
        ctx->ctx_pool = pool;
        ctx->reason = p_strdup(pool, reason);
+       ctx->utf8 = utf8;
        pool_ref(pool);
 
        p_array_init(&ctx->all_headers, pool, 64);
@@ -942,7 +944,6 @@ static int fetch_x_mailbox(struct imap_fetch_context *ctx, struct mail *mail,
                           void *context ATTR_UNUSED)
 {
        const char *name;
-       string_t *mutf7_name;
 
        if (mail_get_special(mail, MAIL_FETCH_MAILBOX_NAME, &name) < 0) {
                /* This can happen with virtual mailbox if the backend mail
@@ -950,12 +951,15 @@ static int fetch_x_mailbox(struct imap_fetch_context *ctx, struct mail *mail,
                return -1;
        }
 
-       mutf7_name = t_str_new(strlen(name)*2);
-       if (imap_utf8_to_utf7(name, mutf7_name) < 0)
-               i_panic("FETCH: Mailbox name not UTF-8: %s", name);
+       if (!ctx->utf8) {
+               string_t *mutf7_name = t_str_new(strlen(name)*2);
+               if (imap_utf8_to_utf7(name, mutf7_name) < 0)
+                       i_panic("FETCH: Mailbox name not UTF-8: %s", name);
+               name = str_c(mutf7_name);
+       }
 
        str_append(ctx->state.cur_str, "X-MAILBOX ");
-       imap_append_astring(ctx->state.cur_str, str_c(mutf7_name), 0);
+       imap_append_astring(ctx->state.cur_str, name, 0);
        str_append_c(ctx->state.cur_str, ' ');
        return 1;
 }
index ba8cfc80a9851468ea321375c3c884703b181a54..67ac7b5b5ab554b2918ae4770b2473c379c58c26 100644 (file)
@@ -102,6 +102,7 @@ struct imap_fetch_context {
        bool flags_show_only_seen_changes:1;
        /* HEADER.FIELDS or HEADER.FIELDS.NOT is fetched */
        bool fetch_header_fields:1;
+       bool utf8:1;
 };
 
 void imap_fetch_handlers_register(const struct imap_fetch_handler *handlers,
@@ -121,12 +122,13 @@ void imap_fetch_add_handler(struct imap_fetch_init_context *ctx,
                (imap_fetch_handler_t *)handler, context)
 
 int imap_fetch_att_list_parse(struct client *client, pool_t pool,
-                             const struct imap_arg *list,
+                             const struct imap_arg *list, bool utf8,
                              struct imap_fetch_context **fetch_ctx_r,
                              const char **client_error_r);
 
 struct imap_fetch_context *
-imap_fetch_alloc(struct client *client, pool_t pool, const char *reason);
+imap_fetch_alloc(struct client *client, pool_t pool, const char *reason,
+                bool utf8);
 void imap_fetch_free(struct imap_fetch_context **ctx);
 bool imap_fetch_init_handler(struct imap_fetch_init_context *init_ctx);
 void imap_fetch_init_nofail_handler(struct imap_fetch_context *ctx,
index a8928667ca51a1f65283054a73048686e616aef0..e0f4cf8a0b3aca2fc6d74b418d8af4a79bfa2462 100644 (file)
@@ -94,12 +94,15 @@ static int imap_notify_status(struct imap_notify_namespace *notify_ns,
                if (error != MAIL_ERROR_PERM)
                        ret = -1;
        } else {
+               bool utf8 = client_has_enabled(client, imap_feature_utf8accept);
                const char *vname = rec->vname;
 
-               string_t *mutf7_vname = t_str_new(128);
-               if (imap_utf8_to_utf7(vname, mutf7_vname) < 0)
-                       i_panic("Mailbox name not UTF-8: %s", vname);
-               vname = str_c(mutf7_vname);
+               if (!utf8) {
+                       string_t *mutf7_vname = t_str_new(128);
+                       if (imap_utf8_to_utf7(vname, mutf7_vname) < 0)
+                               i_panic("Mailbox name not UTF-8: %s", vname);
+                       vname = str_c(mutf7_vname);
+               }
                ret = imap_status_send(client, vname, &items, &result);
        }
        mailbox_free(&box);
index c5fa4ae1ac3848cac268a6ae0d55dfc31660de6e..3e149caa51a17d2f84954b965207ac53262c4fbd 100644 (file)
@@ -57,6 +57,7 @@ struct imap_notify_context {
        bool send_immediate_status:1;
        bool watching_mailbox:1;
        bool notifying:1;
+       bool utf8:1;
 };
 
 bool imap_notify_match_mailbox(struct imap_notify_namespace *notify_ns,
index efcf4a53a4071c04896b0f57109e4f73d069176e..d8ccd6f3a6b14f9dae81445e08ba38e30a9bc2b4 100644 (file)
@@ -49,8 +49,8 @@ search_parse_fetch_att(struct imap_search_context *ctx,
 
        ctx->fetch_pool = pool_alloconly_create("search update fetch", 512);
        if (imap_fetch_att_list_parse(ctx->cmd->client, ctx->fetch_pool,
-                                     update_args, &ctx->fetch_ctx,
-                                     &client_error) < 0) {
+                                     update_args, ctx->cmd->utf8,
+                                     &ctx->fetch_ctx, &client_error) < 0) {
                client_send_command_error(ctx->cmd, t_strconcat(
                        "SEARCH UPDATE fetch-att: ", client_error, NULL));
                pool_unref(&ctx->fetch_pool);
index ccbe50ceca0ae1dac1ba9006ff27651775166cc8..3a2ef457f8e9d4b00bfb1d8a7b82a5cbd5c25212 100644 (file)
@@ -446,6 +446,7 @@ import_send_flag_changes(struct client *client,
                         const struct mailbox_import_state *state,
                         unsigned int *flag_change_count_r)
 {
+       bool utf8 = client_has_enabled(client, imap_feature_utf8accept);
        struct imap_fetch_context *fetch_ctx;
        struct mail_search_args *search_args;
        ARRAY_TYPE(seq_range) old_uids;
@@ -466,7 +467,7 @@ import_send_flag_changes(struct client *client,
        imap_search_add_changed_since(search_args, state->highest_modseq);
 
        pool = pool_alloconly_create("imap state flag changes", 1024);
-       fetch_ctx = imap_fetch_alloc(client, pool, "unhibernate");
+       fetch_ctx = imap_fetch_alloc(client, pool, "unhibernate", utf8);
        pool_unref(&pool);
 
        imap_fetch_init_nofail_handler(fetch_ctx, imap_fetch_flags_init);