From: Stephan Bosch Date: Wed, 27 Aug 2025 00:20:50 +0000 (+0200) Subject: imap: Expect and produce UTF8 mailbox names when UTF8=ACCEPT is enabled X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9df07bd214083f8df01ca5f13ddd89bbf7fa5088;p=thirdparty%2Fdovecot%2Fcore.git imap: Expect and produce UTF8 mailbox names when UTF8=ACCEPT is enabled --- diff --git a/src/imap/cmd-fetch.c b/src/imap/cmd-fetch.c index 35e85d15d3..4fe682698b 100644 --- a/src/imap/cmd-fetch.c +++ b/src/imap/cmd-fetch.c @@ -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) && diff --git a/src/imap/cmd-getmetadata.c b/src/imap/cmd-getmetadata.c index 0b34be3a65..1f913a4271 100644 --- a/src/imap/cmd-getmetadata.c +++ b/src/imap/cmd-getmetadata.c @@ -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(); diff --git a/src/imap/cmd-list.c b/src/imap/cmd-list.c index 993740b31d..dc56c5ee39 100644 --- a/src/imap/cmd-list.c +++ b/src/imap/cmd-list.c @@ -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 = diff --git a/src/imap/cmd-namespace.c b/src/imap/cmd-namespace.c index 0ae67fff1e..5822dd8a42 100644 --- a/src/imap/cmd-namespace.c +++ b/src/imap/cmd-namespace.c @@ -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."); diff --git a/src/imap/cmd-notify.c b/src/imap/cmd-notify.c index caabf62d2b..47cc979890 100644 --- a/src/imap/cmd-notify.c +++ b/src/imap/cmd-notify.c @@ -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)) diff --git a/src/imap/cmd-search.c b/src/imap/cmd-search.c index 6161931592..04998f751b 100644 --- a/src/imap/cmd-search.c +++ b/src/imap/cmd-search.c @@ -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"); diff --git a/src/imap/cmd-select.c b/src/imap/cmd-select.c index 3e9baae399..9e3d5e0cb0 100644 --- a/src/imap/cmd-select.c +++ b/src/imap/cmd-select.c @@ -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); diff --git a/src/imap/imap-client.c b/src/imap/imap-client.c index f09807fe84..d68a8cab21 100644 --- a/src/imap/imap-client.c +++ b/src/imap/imap-client.c @@ -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); diff --git a/src/imap/imap-client.h b/src/imap/imap-client.h index c14b95deb5..fb48c567c9 100644 --- a/src/imap/imap-client.h +++ b/src/imap/imap-client.h @@ -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 { diff --git a/src/imap/imap-commands-util.c b/src/imap/imap-commands-util.c index 4647809cc4..54cbd98012 100644 --- a/src/imap/imap-commands-util.c +++ b/src/imap/imap-commands-util.c @@ -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; } diff --git a/src/imap/imap-fetch.c b/src/imap/imap-fetch.c index fb8a0bb60a..0dca607905 100644 --- a/src/imap/imap-fetch.c +++ b/src/imap/imap-fetch.c @@ -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; } diff --git a/src/imap/imap-fetch.h b/src/imap/imap-fetch.h index ba8cfc80a9..67ac7b5b5a 100644 --- a/src/imap/imap-fetch.h +++ b/src/imap/imap-fetch.h @@ -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, diff --git a/src/imap/imap-notify.c b/src/imap/imap-notify.c index a8928667ca..e0f4cf8a0b 100644 --- a/src/imap/imap-notify.c +++ b/src/imap/imap-notify.c @@ -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); diff --git a/src/imap/imap-notify.h b/src/imap/imap-notify.h index c5fa4ae1ac..3e149caa51 100644 --- a/src/imap/imap-notify.h +++ b/src/imap/imap-notify.h @@ -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, diff --git a/src/imap/imap-search.c b/src/imap/imap-search.c index efcf4a53a4..d8ccd6f3a6 100644 --- a/src/imap/imap-search.c +++ b/src/imap/imap-search.c @@ -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); diff --git a/src/imap/imap-state.c b/src/imap/imap-state.c index ccbe50ceca..3a2ef457f8 100644 --- a/src/imap/imap-state.c +++ b/src/imap/imap-state.c @@ -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);