]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
dsync: Escape and unescape mailbox names as needed
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 1 Mar 2022 13:37:57 +0000 (08:37 -0500)
committerMarkus Valentin <markus.valentin@open-xchange.com>
Mon, 21 Mar 2022 09:06:53 +0000 (10:06 +0100)
This should fix various issues with syncing local and remote mailbox names.
It especially avoids changing the escape character to alt_char.

src/doveadm/dsync/dsync-brain-mailbox-tree.c
src/doveadm/dsync/dsync-mailbox-tree.c
src/doveadm/dsync/test-dsync-mailbox-tree-sync.c

index 007082cef8123f7f52593400fb5585ce8cbc6d21..0e520ae2f0acfd09a3f4522d9dab2121251947dc 100644 (file)
@@ -84,15 +84,24 @@ void dsync_brain_mailbox_trees_init(struct dsync_brain *brain)
                dsync_mailbox_tree_iter_init(brain->local_mailbox_tree);
 }
 
+static const char *const *
+dsync_brain_mailbox_to_parts(struct dsync_brain *brain, const char *name)
+{
+       char sep[] = { brain->hierarchy_sep, '\0' };
+       char **parts = p_strsplit(unsafe_data_stack_pool, name, sep);
+       for (unsigned int i = 0; parts[i] != NULL; i++) {
+               mailbox_list_name_unescape((const char **)&parts[i],
+                                          brain->escape_char);
+       }
+       return (const char *const *)parts;
+}
 
 void dsync_brain_send_mailbox_tree(struct dsync_brain *brain)
 {
        struct dsync_mailbox_node *node;
        enum dsync_ibc_send_ret ret;
        const char *full_name;
-       char sep[2];
 
-       sep[0] = brain->hierarchy_sep; sep[1] = '\0';
        while (dsync_mailbox_tree_iter_next(brain->local_tree_iter,
                                            &full_name, &node)) {
                if (node->ns == NULL) {
@@ -115,19 +124,7 @@ void dsync_brain_send_mailbox_tree(struct dsync_brain *brain)
                                        dsync_mailbox_node_to_string(node));
                        }
 
-                       /* Avoid sending out mailbox names with escape
-                          characters. Especially when dsync is used for
-                          migration, we don't want to end up having invalid
-                          mUTF7 mailbox names locally. Also, remote might not
-                          even be configured to use the same escape
-                          character. */
-                       if (node->ns != NULL) {
-                               i_assert(brain->alt_char != '\0');
-                               full_name = t_str_replace(full_name,
-                                       node->ns->list->set.vname_escape_char,
-                                       brain->alt_char);
-                       }
-                       parts = t_strsplit(full_name, sep);
+                       parts = dsync_brain_mailbox_to_parts(brain, full_name);
                        ret = dsync_ibc_send_mailbox_tree_node(brain->ibc,
                                                               parts, node);
                } T_END;
@@ -302,13 +299,29 @@ dsync_get_mailbox_name(struct dsync_brain *brain, const char *const *name_parts,
        ns_sep = mail_namespace_get_sep(ns);
 
        /* build the mailbox name */
+       char escape_chars[] = {
+               brain->escape_char,
+               ns_sep,
+       };
+       struct dsync_mailbox_list *dlist = DSYNC_LIST_CONTEXT(ns->list);
+       if (dlist != NULL && !dlist->have_orig_escape_char) {
+               /* The escape character was added only for dsync internally.
+                  Normally there is no escape character configured. Change
+                  the mailbox names so that it doesn't rely on it. */
+               escape_chars[0] = '\0';
+       }
        vname = t_str_new(128);
        for (; *name_parts != NULL; name_parts++) {
-               for (p = *name_parts; *p != '\0'; p++) {
-                       if (*p != ns_sep)
-                               str_append_c(vname, *p);
-                       else
-                               str_append_c(vname, brain->alt_char);
+               if (escape_chars[0] != '\0') {
+                       mailbox_list_name_escape(*name_parts, escape_chars,
+                                                vname);
+               } else {
+                       for (p = *name_parts; *p != '\0'; p++) {
+                               if (*p != ns_sep)
+                                       str_append_c(vname, *p);
+                               else
+                                       str_append_c(vname, brain->alt_char);
+                       }
                }
                str_append_c(vname, ns_sep);
        }
index e932378c96c5b33f8489179a5aa7914fdaf408fa..e34f335e472af57f70423c41bf1c2683df2a8657 100644 (file)
@@ -258,13 +258,37 @@ convert_name_to_remote_sep(struct dsync_mailbox_tree *tree, const char *name)
 {
        string_t *str = t_str_new(128);
 
-       for (; *name != '\0'; name++) {
-               if (*name == tree->sep)
-                       str_append_c(str, tree->remote_sep);
-               else if (*name == tree->remote_sep)
-                       str_append_c(str, tree->alt_char);
-               else
-                       str_append_c(str, *name);
+       char remote_escape_chars[3] = {
+               tree->remote_escape_char,
+               tree->remote_sep,
+               '\0'
+       };
+
+       for (;;) {
+               const char *end = strchr(name, tree->sep);
+               const char *name_part = end == NULL ? name :
+                       t_strdup_until(name, end++);
+
+               if (tree->escape_char != '\0')
+                       mailbox_list_name_unescape(&name_part, tree->escape_char);
+               if (remote_escape_chars[0] != '\0') {
+                       /* The local name can be fully escaped to remote
+                          name and back. */
+                       mailbox_list_name_escape(name_part, remote_escape_chars,
+                                                str);
+               } else {
+                       /* There is no remote escape char, so for conflicting
+                          separator use the alt_char. */
+                       for (; *name_part != '\0'; name_part++) {
+                               if (*name_part == tree->remote_sep)
+                                       str_append_c(str, tree->alt_char);
+                               else
+                                       str_append_c(str, *name_part);
+                       }
+               }
+               if (end == NULL)
+                       break;
+               str_append_c(str, tree->remote_sep);
        }
        return str_c(str);
 }
index b7e88da61f4ebdcae87071dff235e1afc82ca316..b068137dbb7828c2c9253c1581a1624ac1843a82 100644 (file)
@@ -30,6 +30,18 @@ void mailbox_name_get_sha128(const char *name, guid_128_t guid_128_r)
        memcpy(guid_128_r, sha, I_MIN(GUID_128_SIZE, sizeof(sha)));
 }
 
+void mailbox_list_name_unescape(const char **name ATTR_UNUSED,
+                               char escape_char ATTR_UNUSED)
+{
+}
+
+void mailbox_list_name_escape(const char *name,
+                             const char *escape_chars ATTR_UNUSED,
+                             string_t *dest)
+{
+       str_append(dest, name);
+}
+
 static struct dsync_mailbox_node *
 node_create(struct dsync_mailbox_tree *tree, unsigned int counter,
            const char *name, unsigned int last_renamed_or_created)