]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-storage: mailbox_list_escape_name_params() - Add first_part parameter
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 30 Apr 2026 09:04:45 +0000 (09:04 +0000)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 7 May 2026 20:22:22 +0000 (20:22 +0000)
The leading '~' check used to fire whenever the input started with '~'.
That assumed callers always passed the full vname. Since
mailbox_list_default_get_storage_name() and
mailbox_name_hdr_decode_storage_name() now split into hierarchy parts
before escaping, the '~' check would wrongly match the start of any
part - e.g. "foo/~bar" would have its second component escaped as
"+7ebar" even though '~' in the middle of a path has no special
meaning.

Add a first_part parameter. Only when first_part=TRUE do we apply the
leading-'~' escape. Callers operating on parts pass first_part=FALSE
for non-leading parts.

For mailbox_list_default_get_storage_name() also require prefix[0] ==
'\\0' so that when the INBOX/INBOX prefix has been prepended to
storage_name, the first split-part is no longer the very start of the
escaped output.

src/lib-storage/index/imapc/imapc-list.c
src/lib-storage/list/mailbox-list-index.c
src/lib-storage/mailbox-list-private.h
src/lib-storage/mailbox-list.c

index 67fa709c142955666393277060c37863cd43f45c..d6678b0041950031a25c9b18151f34980b5638f3 100644 (file)
@@ -202,7 +202,8 @@ imapc_list_remote_to_storage_name(struct imapc_mailbox_list *list,
        return mailbox_list_escape_name_params(remote_name,
                list->root_sep,
                mailbox_list_get_hierarchy_sep(&list->list),
-               list->list.mail_set->mailbox_list_storage_escape_char[0], "");
+               list->list.mail_set->mailbox_list_storage_escape_char[0], "",
+               TRUE);
 }
 
 static const char *
@@ -505,7 +506,8 @@ imapc_list_storage_to_fs_name(struct imapc_mailbox_list *list,
        remote_name = imapc_list_storage_to_remote_name(list, storage_name);
        return mailbox_list_escape_name_params(remote_name,
                list->root_sep, mailbox_list_get_hierarchy_sep(fs_list),
-               fs_list->mail_set->mailbox_list_storage_escape_char[0], "");
+               fs_list->mail_set->mailbox_list_storage_escape_char[0], "",
+               TRUE);
 }
 
 static const char *
index d064e0f7109805351a952142518e3ff29cc88fd3..1bfc1a7b8dd00a6a9c7d00072d63a25a1da0c41b 100644 (file)
@@ -641,6 +641,7 @@ mailbox_name_hdr_decode_storage_name(struct mailbox_list *list,
 
        const char list_sep = mailbox_list_get_hierarchy_sep(list);
        const char escape_char = list->mail_set->mailbox_list_storage_escape_char[0];
+       bool first_part = TRUE;
        array_foreach_elem(&raw_parts, raw_part) {
                if (str_len(storage_name) > 0)
                        str_append_c(storage_name, list_sep);
@@ -650,8 +651,10 @@ mailbox_name_hdr_decode_storage_name(struct mailbox_list *list,
                        str_append(storage_name,
                                   mailbox_list_escape_name_params(raw_part,
                                        '\0', list_sep, escape_char,
-                                       list->mail_set->mailbox_directory_name));
+                                       list->mail_set->mailbox_directory_name,
+                                       first_part));
                }
+               first_part = FALSE;
        }
        return str_c(storage_name);
 }
index 93826ee8c553bc59c51419b8279b58e49fd46628..a37af4a8a03390ce6973bdb76eb0e9049436ee19 100644 (file)
@@ -198,7 +198,8 @@ void mailbox_lists_deinit(void);
 
 const char *
 mailbox_list_escape_name_params(const char *vname, char ns_sep, char list_sep,
-                               char escape_char, const char *maildir_name);
+                               char escape_char, const char *maildir_name,
+                               bool first_part);
 const char *
 mailbox_list_unescape_name_params(const char *src, char ns_sep, char list_sep,
                                  char escape_char);
index 352e116391113dcf3f5da25c4b52853195ef1491..bee0b9c30c96e8353ddc03fda5256979542dcac7 100644 (file)
@@ -210,15 +210,20 @@ static bool need_escape_dirstart(const char *vname, const char *maildir_name)
 
 const char *
 mailbox_list_escape_name_params(const char *vname, char ns_sep, char list_sep,
-                               char escape_char, const char *maildir_name)
+                               char escape_char, const char *maildir_name,
+                               bool first_part)
 {
        string_t *escaped_name = t_str_new(64);
        bool dirstart = TRUE;
 
        i_assert(escape_char != '\0');
 
-       /* escape the mailbox name */
-       if (*vname == '~') {
+       /* escape the mailbox name. The leading '~' is escaped only at the very
+          beginning of the full name, since '~' is otherwise a perfectly valid
+          character in a hierarchy component. Callers operating on a single
+          hierarchy part (already split from the full vname) pass first_part=FALSE
+          for non-leading parts to skip this check. */
+       if (first_part && *vname == '~') {
                str_printfa(escaped_name, "%c%02x", escape_char, *vname);
                vname++;
                dirstart = FALSE;
@@ -390,7 +395,8 @@ const char *mailbox_list_default_get_storage_name(struct mailbox_list *list,
                                   '\0', /* no separator conversion */
                                   mailbox_list_get_hierarchy_sep(list),
                                   list->mail_set->mailbox_list_storage_escape_char[0],
-                                  list->mail_set->mailbox_directory_name));
+                                  list->mail_set->mailbox_directory_name,
+                                  i == 0 && prefix[0] == '\0'));
                }
        }
        return str_c(storage_name);