]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-storage: mail-search - Add UTF8 support for mail search args IMAP encoding
authorStephan Bosch <stephan.bosch@open-xchange.com>
Tue, 26 Aug 2025 02:15:17 +0000 (04:15 +0200)
committerStephan Bosch <stephan.bosch@open-xchange.com>
Mon, 26 Jan 2026 01:58:58 +0000 (02:58 +0100)
src/lib-storage/index/imapc/imapc-search.c
src/lib-storage/mail-search-args-cmdline.c
src/lib-storage/mail-search-args-imap.c
src/lib-storage/mail-search-mime.c
src/lib-storage/mail-search-mime.h
src/lib-storage/mail-search.h
src/lib-storage/test-mail-search-args-imap.c
src/lib-storage/test-mail-search-args-simplify.c

index 460872c6da304830c3a5728b66543c048a9e7697..c51b870e034cf8465bcabbe8abc911a2dc43c15d 100644 (file)
@@ -181,7 +181,7 @@ imapc_build_search_query_arg(struct imapc_mailbox *mbox,
                    (mbox->capabilities & IMAPC_CAPABILITY_WITHIN) == 0) {
                        /* a bit kludgy way to check this.. */
                        size_t pos = str_len(str);
-                       if (!mail_search_arg_to_imap(str, arg, &error))
+                       if (!mail_search_arg_to_imap(str, arg, FALSE, &error))
                                return FALSE;
                        if (str_begins_icase_with(str_c(str) + pos, "OLDER") ||
                            str_begins_icase_with(str_c(str) + pos, "YOUNGER"))
@@ -206,16 +206,16 @@ imapc_build_search_query_arg(struct imapc_mailbox *mbox,
        case SEARCH_HEADER_COMPRESS_LWSP:
        case SEARCH_BODY:
        case SEARCH_TEXT:
-               return mail_search_arg_to_imap(str, arg, &error);
+               return mail_search_arg_to_imap(str, arg, FALSE, &error);
        /* extensions */
        case SEARCH_MODSEQ:
                if ((mbox->capabilities & IMAPC_CAPABILITY_CONDSTORE) == 0)
                        return FALSE;
-               return mail_search_arg_to_imap(str, arg, &error);
+               return mail_search_arg_to_imap(str, arg, FALSE, &error);
        case SEARCH_SAVEDATESUPPORTED:
                if ((mbox->capabilities & IMAPC_CAPABILITY_SAVEDATE) == 0)
                        return FALSE;
-               return mail_search_arg_to_imap(str, arg, &error);
+               return mail_search_arg_to_imap(str, arg, FALSE, &error);
        case SEARCH_INTHREAD:
        case SEARCH_GUID:
        case SEARCH_MAILBOX:
@@ -227,7 +227,7 @@ imapc_build_search_query_arg(struct imapc_mailbox *mbox,
        case SEARCH_MIMEPART:
                if ((mbox->capabilities & IMAPC_CAPABILITY_SEARCH_MIMEPART) == 0)
                        return FALSE;
-               return mail_search_arg_to_imap(str, arg, &error);
+               return mail_search_arg_to_imap(str, arg, FALSE, &error);
        }
        return FALSE;
 }
index 18bbc171299ceb1deb6b62fc67d20a6db1d421fe..0e6ce71e772114e8ff4ecfafce9e4248423bc663 100644 (file)
@@ -44,7 +44,7 @@ mail_search_arg_to_cmdline(string_t *dest, const struct mail_search_arg *arg)
 
                new_arg = *arg;
                new_arg.match_not = FALSE;
-               if (!mail_search_arg_to_imap(dest, &new_arg, &error))
+               if (!mail_search_arg_to_imap(dest, &new_arg, FALSE, &error))
                        i_unreached();
                if (str_c(dest)[pos] == '(') {
                        str_insert(dest, pos+1, " ");
@@ -91,7 +91,7 @@ mail_search_arg_to_cmdline(string_t *dest, const struct mail_search_arg *arg)
        }
        new_arg = *arg;
        new_arg.match_not = FALSE;
-       if (!mail_search_arg_to_imap(dest, &new_arg, &error))
+       if (!mail_search_arg_to_imap(dest, &new_arg, FALSE, &error))
                i_panic("mail_search_args_to_cmdline(): Missing handler: %s", error);
 }
 
index 60f0064284b94e2ab3aa3002a42b8de067c49ad0..42bbcfbf3970bb103e8c42695e1db1983ea2b1f9 100644 (file)
 
 static bool
 mail_search_subargs_to_imap(string_t *dest, const struct mail_search_arg *args,
-                           const char *prefix, const char **error_r)
+                           const char *prefix, enum imap_quote_flags qflags,
+                           const char **error_r)
 {
+       bool utf8 = HAS_ALL_BITS(qflags, IMAP_QUOTE_FLAG_UTF8);
        const struct mail_search_arg *arg;
 
        if (prefix[0] == '\0')
@@ -25,7 +27,7 @@ mail_search_subargs_to_imap(string_t *dest, const struct mail_search_arg *args,
        for (arg = args; arg != NULL; arg = arg->next) {
                if (arg->next != NULL)
                        str_append(dest, prefix);
-               if (!mail_search_arg_to_imap(dest, arg, error_r))
+               if (!mail_search_arg_to_imap(dest, arg, utf8, error_r))
                        return FALSE;
                if (arg->next != NULL)
                        str_append_c(dest, ' ');
@@ -77,8 +79,9 @@ mail_search_arg_to_imap_flags(string_t *dest, enum mail_flags flags)
 }
 
 bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
-                            const char **error_r)
+                            bool utf8, const char **error_r)
 {
+       enum imap_quote_flags qflags = (utf8 ? IMAP_QUOTE_FLAG_UTF8 : 0);
        unsigned int start_pos;
 
        if (arg->match_not)
@@ -87,12 +90,12 @@ bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
        switch (arg->type) {
        case SEARCH_OR:
                if (!mail_search_subargs_to_imap(dest, arg->value.subargs,
-                                                "OR ", error_r))
+                                                "OR ", qflags, error_r))
                        return FALSE;
                break;
        case SEARCH_SUB:
                if (!mail_search_subargs_to_imap(dest, arg->value.subargs,
-                                                "", error_r))
+                                                "", qflags, error_r))
                        return FALSE;
                break;
        case SEARCH_ALL:
@@ -226,19 +229,19 @@ bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
                        str_append(dest, t_str_ucase(arg->hdr_field_name));
                else {
                        str_append(dest, "HEADER ");
-                       imap_append_astring(dest, arg->hdr_field_name, 0);
+                       imap_append_astring(dest, arg->hdr_field_name, qflags);
                }
                str_append_c(dest, ' ');
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
 
        case SEARCH_BODY:
                str_append(dest, "BODY ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_TEXT:
                str_append(dest, "TEXT ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
 
        /* extensions */
@@ -282,7 +285,7 @@ bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
                        0);
                str_append_c(dest, ' ');
                if (!mail_search_subargs_to_imap(dest, arg->value.subargs,
-                                                "", error_r))
+                                                "", qflags, error_r))
                        return FALSE;
                break;
        case SEARCH_GUID:
@@ -297,7 +300,7 @@ bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
                return FALSE;
        case SEARCH_MAILBOX_GLOB:
                str_append(dest, "X-MAILBOX ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_REAL_UID:
                str_append(dest, "X-REAL-UID ");
@@ -305,8 +308,8 @@ bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
                break;
        case SEARCH_MIMEPART:
                str_append(dest, "MIMEPART ");
-               if (!mail_search_mime_part_to_imap(dest,
-                       arg->value.mime_part, error_r))
+               if (!mail_search_mime_part_to_imap(dest, arg->value.mime_part,
+                                                  utf8, error_r))
                        return FALSE;
                break;
        }
@@ -314,12 +317,12 @@ bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
 }
 
 bool mail_search_args_to_imap(string_t *dest, const struct mail_search_arg *args,
-                             const char **error_r)
+                             bool utf8, const char **error_r)
 {
        const struct mail_search_arg *arg;
 
        for (arg = args; arg != NULL; arg = arg->next) {
-               if (!mail_search_arg_to_imap(dest, arg, error_r))
+               if (!mail_search_arg_to_imap(dest, arg, utf8, error_r))
                        return FALSE;
                if (arg->next != NULL)
                        str_append_c(dest, ' ');
index 08ddcb9a916dbf280f75ca8be9b8a37b15ee4e40..1bf3c011fad58a34b7f8d64eafbcf5e1b7c54922 100644 (file)
@@ -348,8 +348,11 @@ void mail_search_mime_simplify(struct mail_search_mime_part *mpart)
 static bool
 mail_search_mime_subargs_to_imap(string_t *dest,
                                 const struct mail_search_mime_arg *args,
-                                const char *prefix, const char **error_r)
+                                const char *prefix,
+                                enum imap_quote_flags qflags,
+                                const char **error_r)
 {
+       bool utf8 = HAS_ALL_BITS(qflags, IMAP_QUOTE_FLAG_UTF8);
        const struct mail_search_mime_arg *arg;
 
        if (prefix[0] == '\0')
@@ -357,7 +360,7 @@ mail_search_mime_subargs_to_imap(string_t *dest,
        for (arg = args; arg != NULL; arg = arg->next) {
                if (arg->next != NULL)
                        str_append(dest, prefix);
-               if (!mail_search_mime_arg_to_imap(dest, arg, error_r))
+               if (!mail_search_mime_arg_to_imap(dest, arg, utf8, error_r))
                        return FALSE;
                if (arg->next != NULL)
                        str_append_c(dest, ' ');
@@ -388,19 +391,21 @@ mail_search_mime_arg_to_imap_date(string_t *dest,
 
 bool mail_search_mime_arg_to_imap(string_t *dest,
                                  const struct mail_search_mime_arg *arg,
-                                 const char **error_r)
+                                 bool utf8, const char **error_r)
 {
+       enum imap_quote_flags qflags = (utf8 ? IMAP_QUOTE_FLAG_UTF8 : 0);
+
        if (arg->match_not)
                str_append(dest, "NOT ");
        switch (arg->type) {
        case SEARCH_MIME_OR:
                if (!mail_search_mime_subargs_to_imap(
-                       dest, arg->value.subargs, "OR ", error_r))
+                       dest, arg->value.subargs, "OR ", qflags, error_r))
                        return FALSE;
                break;
        case SEARCH_MIME_SUB:
                if (!mail_search_mime_subargs_to_imap(
-                       dest, arg->value.subargs, "", error_r))
+                       dest, arg->value.subargs, "", qflags, error_r))
                        return FALSE;
                break;
        case SEARCH_MIME_SIZE_EQUAL:
@@ -414,7 +419,7 @@ bool mail_search_mime_arg_to_imap(string_t *dest,
                break;
        case SEARCH_MIME_DESCRIPTION:
                str_append(dest, "DESCRIPTION ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_DISPOSITION_TYPE:
                str_append(dest, "DISPOSITION TYPE ");
@@ -424,7 +429,7 @@ bool mail_search_mime_arg_to_imap(string_t *dest,
                str_append(dest, "DISPOSITION PARAM ");
                imap_append_astring(dest, arg->field_name, 0);
                str_append_c(dest, ' ');
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_ENCODING:
                str_append(dest, "ENCODING ");
@@ -458,49 +463,49 @@ bool mail_search_mime_arg_to_imap(string_t *dest,
                str_append(dest, "PARAM ");
                imap_append_astring(dest, arg->field_name, 0);
                str_append_c(dest, ' ');
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_HEADER:
                str_append(dest, "HEADER ");
                imap_append_astring(dest, arg->field_name, 0);
                str_append_c(dest, ' ');
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_BODY:
                str_append(dest, "BODY ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_TEXT:
                str_append(dest, "TEXT ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_CC:
                str_append(dest, "CC ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_BCC:
                str_append(dest, "BCC ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_FROM:
                str_append(dest, "FROM ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_IN_REPLY_TO:
                str_append(dest, "IN-REPLY-TO ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_MESSAGE_ID:
                str_append(dest, "MESSAGE-ID ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_REPLY_TO:
                str_append(dest, "REPLY-TO ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_SENDER:
                str_append(dest, "SENDER ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_SENTBEFORE:
                str_append(dest, "SENTBEFORE");
@@ -531,11 +536,11 @@ bool mail_search_mime_arg_to_imap(string_t *dest,
                break;
        case SEARCH_MIME_SUBJECT:
                str_append(dest, "SUBJECT ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_TO:
                str_append(dest, "TO ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_DEPTH_EQUAL:
                str_printfa(dest, "DEPTH %u", arg->value.number);
@@ -554,7 +559,7 @@ bool mail_search_mime_arg_to_imap(string_t *dest,
                if (arg->value.subargs == NULL)
                        str_append(dest, "EXISTS");
                else if (!mail_search_mime_subargs_to_imap(
-                               dest, arg->value.subargs, "", error_r))
+                               dest, arg->value.subargs, "", qflags, error_r))
                        return FALSE;
                break;
        case SEARCH_MIME_CHILD:
@@ -562,24 +567,24 @@ bool mail_search_mime_arg_to_imap(string_t *dest,
                if (arg->value.subargs == NULL)
                        str_append(dest, "EXISTS");
                else if (!mail_search_mime_subargs_to_imap(
-                               dest, arg->value.subargs, "", error_r))
+                               dest, arg->value.subargs, "", qflags, error_r))
                        return FALSE;
                break;
        case SEARCH_MIME_FILENAME_IS:
                str_append(dest, "FILENAME IS ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_FILENAME_CONTAINS:
                str_append(dest, "FILENAME CONTAINS ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_FILENAME_BEGINS:
                str_append(dest, "FILENAME BEGINS ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        case SEARCH_MIME_FILENAME_ENDS:
                str_append(dest, "FILENAME ENDS ");
-               imap_append_astring(dest, arg->value.str, 0);
+               imap_append_astring(dest, arg->value.str, qflags);
                break;
        }
        return TRUE;
@@ -587,18 +592,20 @@ bool mail_search_mime_arg_to_imap(string_t *dest,
 
 bool mail_search_mime_part_to_imap(string_t *dest,
                                   const struct mail_search_mime_part *mpart,
-                                  const char **error_r)
+                                  bool utf8, const char **error_r)
 {
        const struct mail_search_mime_arg *arg;
 
        i_assert(mpart->args != NULL);
        if (mpart->args->next == NULL) {
-               if (!mail_search_mime_arg_to_imap(dest, mpart->args, error_r))
+               if (!mail_search_mime_arg_to_imap(dest, mpart->args, utf8,
+                                                 error_r))
                        return FALSE;
        } else {
                str_append_c(dest, '(');
                for (arg = mpart->args; arg != NULL; arg = arg->next) {
-                       if (!mail_search_mime_arg_to_imap(dest, arg, error_r))
+                       if (!mail_search_mime_arg_to_imap(dest, arg, utf8,
+                                                         error_r))
                                return FALSE;
                        if (arg->next != NULL)
                                str_append_c(dest, ' ');
index dfd60dd24868947969797fca2fa9630f438da695..7d48e2f90ab39a198bbeb939b8b15e3fb30aea41 100644 (file)
@@ -138,11 +138,11 @@ void mail_search_mime_simplify(struct mail_search_mime_part *args);
 /* Appends MIMEPART search key to the dest string and returns TRUE. */
 bool mail_search_mime_part_to_imap(string_t *dest,
                                   const struct mail_search_mime_part *mpart,
-                                  const char **error_r);
+                                  bool utf8, const char **error_r);
 /* Like mail_search_mime_part_to_imap(), but append only a single MIMEPART
    key. */
 bool mail_search_mime_arg_to_imap(string_t *dest,
                                  const struct mail_search_mime_arg *arg,
-                                 const char **error_r);
+                                 bool utf8, const char **error_r);
 
 #endif
index 903216568c3242467faa5dfed390a678cb333997..c67f1d881eeee9653bacfa82981e5054890a0889 100644 (file)
@@ -255,11 +255,12 @@ void mail_search_args_simplify(struct mail_search_args *args);
 /* Append all args as IMAP SEARCH AND-query to the dest string and returns TRUE.
    If some search arg can't be written as IMAP SEARCH parameter, error_r is set
    and FALSE is returned. */
-bool mail_search_args_to_imap(string_t *dest, const struct mail_search_arg *args,
-                             const char **error_r);
+bool mail_search_args_to_imap(string_t *dest,
+                             const struct mail_search_arg *args,
+                             bool utf8, const char **error_r);
 /* Like mail_search_args_to_imap(), but append only a single arg. */
 bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
-                            const char **error_r);
+                            bool utf8, const char **error_r);
 /* Write all args to dest string as cmdline/human compatible input. */
 void mail_search_args_to_cmdline(string_t *dest,
                                 const struct mail_search_arg *args);
index 13c34a52d49d12939865325d230cc2e5a2864755..ba4a8361ab3ce99b3fb646b48431a717ecb72f76 100644 (file)
@@ -159,12 +159,16 @@ static void test_mail_search_args_imap(void)
                output = tests[i].output != NULL ?
                        tests[i].output : tests[i].input;
                str_truncate(str, 0);
-               test_assert_idx(mail_search_args_to_imap(str, args->args, &error), i);
+               test_assert_idx(mail_search_args_to_imap(str, args->args, FALSE,
+                                                        &error), i);
                test_assert_idx(strcmp(str_c(str), output) == 0, i);
                mail_search_args_unref(&args);
        }
-       for (i = 0; i < N_ELEMENTS(test_failures); i++)
-               test_assert_idx(!mail_search_args_to_imap(str, &test_failures[i], &error), i);
+       for (i = 0; i < N_ELEMENTS(test_failures); i++) {
+               test_assert_idx(
+                       !mail_search_args_to_imap(str, &test_failures[i], FALSE,
+                                                 &error), i);
+       }
        test_end();
 }
 
index 545ddb48df7d65a0025a37c9957c434abdb3e5fe..719dba3b31668a98116f24b802f4221906680db1 100644 (file)
@@ -298,7 +298,8 @@ static void test_mail_search_args_simplify(void)
                mail_search_args_simplify(args);
 
                str_truncate(str, 0);
-               test_assert(mail_search_args_to_imap(str, args->args, &error));
+               test_assert(mail_search_args_to_imap(str, args->args, FALSE,
+                                                    &error));
                test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i);
 
                test_assert_idx(test_search_args_are_initialized(args->args), i);