summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/mail-search-args-simplify.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib-storage/mail-search-args-simplify.c
parentInitial commit. (diff)
downloaddovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz
dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-storage/mail-search-args-simplify.c')
-rw-r--r--src/lib-storage/mail-search-args-simplify.c921
1 files changed, 921 insertions, 0 deletions
diff --git a/src/lib-storage/mail-search-args-simplify.c b/src/lib-storage/mail-search-args-simplify.c
new file mode 100644
index 0000000..e6490b5
--- /dev/null
+++ b/src/lib-storage/mail-search-args-simplify.c
@@ -0,0 +1,921 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "mail-search.h"
+
+struct mail_search_simplify_prev_arg {
+ struct {
+ enum mail_search_arg_type type;
+ enum mail_search_arg_flag search_flags;
+ enum mail_search_date_type date_type;
+ enum mail_flags mail_flags;
+ bool match_not;
+ bool fuzzy;
+ } bin_mask;
+ const char *hdr_field_name_mask;
+ const char *str_mask;
+
+ struct mail_search_arg *prev_arg;
+};
+
+struct mail_search_simplify_ctx {
+ pool_t pool;
+ /* arg mask => prev_arg */
+ HASH_TABLE(struct mail_search_simplify_prev_arg *,
+ struct mail_search_simplify_prev_arg *) prev_args;
+ bool parent_and;
+ bool removals;
+ bool initialized;
+};
+
+static int
+mail_search_simplify_prev_arg_cmp(const struct mail_search_simplify_prev_arg *arg1,
+ const struct mail_search_simplify_prev_arg *arg2)
+{
+ int ret;
+
+ ret = memcmp(&arg1->bin_mask, &arg2->bin_mask, sizeof(arg1->bin_mask));
+ if (ret == 0)
+ ret = null_strcmp(arg1->hdr_field_name_mask, arg2->hdr_field_name_mask);
+ if (ret == 0)
+ ret = null_strcmp(arg1->str_mask, arg2->str_mask);
+ return ret;
+}
+
+static unsigned int
+mail_search_simplify_prev_arg_hash(const struct mail_search_simplify_prev_arg *arg)
+{
+ unsigned int hash;
+
+ hash = mem_hash(&arg->bin_mask, sizeof(arg->bin_mask));
+ if (arg->hdr_field_name_mask != NULL)
+ hash ^= str_hash(arg->hdr_field_name_mask);
+ if (arg->str_mask != NULL)
+ hash ^= str_hash(arg->str_mask);
+ return hash;
+}
+
+static void mail_search_arg_get_base_mask(const struct mail_search_arg *arg,
+ struct mail_search_simplify_prev_arg *mask_r)
+{
+ i_zero(mask_r);
+ mask_r->bin_mask.type = arg->type;
+ mask_r->bin_mask.fuzzy = arg->fuzzy;
+ mask_r->bin_mask.search_flags = arg->value.search_flags;
+}
+
+static struct mail_search_arg **
+mail_search_args_simplify_get_prev_argp(struct mail_search_simplify_ctx *ctx,
+ const struct mail_search_simplify_prev_arg *mask)
+{
+ struct mail_search_simplify_prev_arg *prev_arg;
+
+ prev_arg = hash_table_lookup(ctx->prev_args, mask);
+ if (prev_arg == NULL) {
+ prev_arg = p_new(ctx->pool, struct mail_search_simplify_prev_arg, 1);
+ prev_arg->bin_mask = mask->bin_mask;
+ prev_arg->hdr_field_name_mask =
+ p_strdup(ctx->pool, mask->hdr_field_name_mask);
+ prev_arg->str_mask =
+ p_strdup(ctx->pool, mask->str_mask);
+ hash_table_insert(ctx->prev_args, prev_arg, prev_arg);
+ }
+ return &prev_arg->prev_arg;
+}
+
+static bool
+mail_search_args_merge_mask(struct mail_search_simplify_ctx *ctx,
+ struct mail_search_arg *args,
+ const struct mail_search_simplify_prev_arg *mask)
+{
+ struct mail_search_arg **prev_argp;
+
+ prev_argp = mail_search_args_simplify_get_prev_argp(ctx, mask);
+ if (*prev_argp == NULL) {
+ *prev_argp = args;
+ return FALSE;
+ }
+
+ if (ctx->initialized)
+ mail_search_arg_one_deinit(args);
+
+ if ((*prev_argp)->match_not != args->match_not) {
+ /* a && !a = 0 */
+ if (ctx->initialized)
+ mail_search_arg_one_deinit(*prev_argp);
+ (*prev_argp)->type = SEARCH_ALL;
+ (*prev_argp)->match_not = ctx->parent_and;
+ }
+ /* duplicate keyword. */
+ return TRUE;
+}
+
+static bool mail_search_args_merge_flags(struct mail_search_simplify_ctx *ctx,
+ struct mail_search_arg *args)
+{
+ struct mail_search_simplify_prev_arg mask;
+
+ mail_search_arg_get_base_mask(args, &mask);
+ mask.bin_mask.mail_flags = args->value.flags;
+ return mail_search_args_merge_mask(ctx, args, &mask);
+}
+
+static bool
+mail_search_args_merge_keywords(struct mail_search_simplify_ctx *ctx,
+ struct mail_search_arg *args)
+{
+ struct mail_search_simplify_prev_arg mask;
+
+ mail_search_arg_get_base_mask(args, &mask);
+ mask.str_mask = args->value.str;
+ return mail_search_args_merge_mask(ctx, args, &mask);
+}
+
+static void mail_search_args_simplify_set(struct mail_search_arg *args)
+{
+ const struct seq_range *seqset;
+ unsigned int count;
+
+ if (args->match_not) {
+ /* invert the set to drop the NOT. Note that (uint32_t)-1
+ matches the last existing mail, which we don't know at this
+ point. lib-imap/imap-seqset.c has similar code that
+ disallows using (uint32_t)-1 as a real UID. */
+ if (seq_range_exists(&args->value.seqset, (uint32_t)-1))
+ return;
+ args->match_not = FALSE;
+ seq_range_array_invert(&args->value.seqset, 1, (uint32_t)-2);
+ }
+ seqset = array_get(&args->value.seqset, &count);
+ if (count == 1 && seqset->seq1 == 1 && seqset->seq2 >= (uint32_t)-2) {
+ /* 1:* is the same as ALL. */
+ args->type = SEARCH_ALL;
+ } else if (count == 0) {
+ /* empty set is the same as NOT ALL. this is mainly coming
+ from mail_search_args_merge_set() intersection. */
+ args->type = SEARCH_ALL;
+ args->match_not = TRUE;
+ }
+}
+
+static bool mail_search_args_merge_set(struct mail_search_simplify_ctx *ctx,
+ struct mail_search_arg *args)
+{
+ struct mail_search_simplify_prev_arg mask;
+ struct mail_search_arg **prev_argp;
+
+ if (args->match_not) {
+ /* "*" used - can't simplify it */
+ return FALSE;
+ }
+
+ mail_search_arg_get_base_mask(args, &mask);
+ mask.bin_mask.match_not = args->match_not;
+ prev_argp = mail_search_args_simplify_get_prev_argp(ctx, &mask);
+
+ if (*prev_argp == NULL) {
+ *prev_argp = args;
+ return FALSE;
+ } else if (ctx->parent_and) {
+ seq_range_array_intersect(&(*prev_argp)->value.seqset,
+ &args->value.seqset);
+ return TRUE;
+ } else {
+ seq_range_array_merge(&(*prev_argp)->value.seqset,
+ &args->value.seqset);
+ return TRUE;
+ }
+}
+
+static bool mail_search_args_merge_time(struct mail_search_simplify_ctx *ctx,
+ struct mail_search_arg *args)
+{
+ struct mail_search_simplify_prev_arg mask;
+ struct mail_search_arg **prev_argp, *prev_arg;
+
+ mail_search_arg_get_base_mask(args, &mask);
+ mask.bin_mask.match_not = args->match_not;
+ mask.bin_mask.date_type = args->value.date_type;
+ prev_argp = mail_search_args_simplify_get_prev_argp(ctx, &mask);
+
+ if (*prev_argp == NULL) {
+ *prev_argp = args;
+ return FALSE;
+ }
+
+ prev_arg = *prev_argp;
+ switch (args->type) {
+ case SEARCH_BEFORE:
+ if (ctx->parent_and) {
+ if (prev_arg->value.time < args->value.time) {
+ /* prev_arg < 5 AND arg < 10 */
+ } else {
+ /* prev_arg < 10 AND arg < 5 */
+ prev_arg->value.time = args->value.time;
+ }
+ } else {
+ if (prev_arg->value.time < args->value.time) {
+ /* prev_arg < 5 OR arg < 10 */
+ prev_arg->value.time = args->value.time;
+ } else {
+ /* prev_arg < 10 OR arg < 5 */
+ }
+ }
+ return TRUE;
+ case SEARCH_ON:
+ if (prev_arg->value.time == args->value.time)
+ return TRUE;
+ return FALSE;
+ case SEARCH_SINCE:
+ if (ctx->parent_and) {
+ if (prev_arg->value.time < args->value.time) {
+ /* prev_arg >= 5 AND arg >= 10 */
+ prev_arg->value.time = args->value.time;
+ } else {
+ /* prev_arg >= 10 AND arg >= 5 */
+ }
+ } else {
+ if (prev_arg->value.time < args->value.time) {
+ /* prev_arg >= 5 OR arg >= 10 */
+ } else {
+ /* prev_arg >= 10 OR arg >= 5 */
+ prev_arg->value.time = args->value.time;
+ }
+ }
+ return TRUE;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+static bool mail_search_args_merge_size(struct mail_search_simplify_ctx *ctx,
+ struct mail_search_arg *args)
+{
+ struct mail_search_simplify_prev_arg mask;
+ struct mail_search_arg **prev_argp, *prev_arg;
+
+ mail_search_arg_get_base_mask(args, &mask);
+ mask.bin_mask.match_not = args->match_not;
+ prev_argp = mail_search_args_simplify_get_prev_argp(ctx, &mask);
+
+ if (*prev_argp == NULL) {
+ *prev_argp = args;
+ return FALSE;
+ }
+
+ prev_arg = *prev_argp;
+ switch (args->type) {
+ case SEARCH_SMALLER:
+ if (ctx->parent_and) {
+ if (prev_arg->value.size < args->value.size) {
+ /* prev_arg < 5 AND arg < 10 */
+ } else {
+ /* prev_arg < 10 AND arg < 5 */
+ prev_arg->value.size = args->value.size;
+ }
+ } else {
+ if (prev_arg->value.size < args->value.size) {
+ /* prev_arg < 5 OR arg < 10 */
+ prev_arg->value.size = args->value.size;
+ } else {
+ /* prev_arg < 10 OR arg < 5 */
+ }
+ }
+ return TRUE;
+ case SEARCH_LARGER:
+ if (ctx->parent_and) {
+ if (prev_arg->value.size < args->value.size) {
+ /* prev_arg >= 5 AND arg >= 10 */
+ prev_arg->value.size = args->value.size;
+ } else {
+ /* prev_arg >= 10 AND arg >= 5 */
+ }
+ } else {
+ if (prev_arg->value.size < args->value.size) {
+ /* prev_arg >= 5 OR arg >= 10 */
+ } else {
+ /* prev_arg >= 10 OR arg >= 5 */
+ prev_arg->value.size = args->value.size;
+ }
+ }
+ return TRUE;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+static bool mail_search_args_merge_text(struct mail_search_simplify_ctx *ctx,
+ struct mail_search_arg *args)
+{
+ struct mail_search_simplify_prev_arg mask;
+
+ mail_search_arg_get_base_mask(args, &mask);
+ mask.hdr_field_name_mask = args->hdr_field_name;
+ mask.str_mask = args->value.str;
+ return mail_search_args_merge_mask(ctx, args, &mask);
+}
+
+static bool
+mail_search_args_have_equal(const struct mail_search_arg *args,
+ const struct mail_search_arg *wanted_arg)
+{
+ const struct mail_search_arg *arg;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ if (mail_search_arg_one_equals(arg, wanted_arg))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool
+mail_search_args_remove_equal(struct mail_search_args *all_args,
+ struct mail_search_arg **argsp,
+ const struct mail_search_arg *wanted_arg,
+ bool check_subs)
+{
+ struct mail_search_arg **argp;
+ bool found = FALSE;
+
+ for (argp = argsp; (*argp) != NULL; ) {
+ if (mail_search_arg_one_equals(*argp, wanted_arg)) {
+ if (all_args->init_refcount > 0)
+ mail_search_arg_one_deinit(*argp);
+ *argp = (*argp)->next;
+ found = TRUE;
+ } else if (check_subs) {
+ i_assert((*argp)->type == SEARCH_SUB ||
+ (*argp)->type == SEARCH_OR);
+ if (!mail_search_args_remove_equal(all_args, &(*argp)->value.subargs, wanted_arg, FALSE)) {
+ /* we already verified that this should have
+ existed. */
+ i_unreached();
+ }
+ if ((*argp)->value.subargs == NULL)
+ *argp = (*argp)->next;
+ else
+ argp = &(*argp)->next;
+ found = TRUE;
+ } else {
+ argp = &(*argp)->next;
+ }
+ }
+ return found;
+}
+
+static bool
+mail_search_args_have_all_equal(struct mail_search_arg *parent_arg,
+ const struct mail_search_arg *wanted_args)
+{
+ const struct mail_search_arg *arg;
+
+ i_assert(parent_arg->type == SEARCH_SUB ||
+ parent_arg->type == SEARCH_OR);
+
+ for (arg = wanted_args; arg != NULL; arg = arg->next) {
+ if (!mail_search_args_have_equal(parent_arg->value.subargs, arg))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* Absorptive Law - This law enables a reduction in a complicated expression to
+ a simpler one by absorbing like terms.
+
+ A + (A.B) = (A.1) + (A.B) = A(1 + B) = A (OR Absorption Law)
+ A(A + B) = (A + 0).(A + B) = A + (0.B) = A (AND Absorption Law)
+
+ Cases with multiple shared terms (duals appy as well)
+
+ A + B + (A.C) + (B.C) = (A + (A.C)) + (B + B.C)) (apply law to sides of external sum))
+ = A + B
+ A + B + (A.B.C) = (A + (A.(B.C))) + B (X = B.C)
+ = (A + (A.(X)) + B (apply law to X)
+ = A + B
+*/
+static bool
+mail_search_args_simplify_drop_redundant_args(struct mail_search_args *all_args,
+ struct mail_search_arg **argsp,
+ bool and_arg)
+{
+ if (*argsp == NULL || (*argsp)->next == NULL)
+ return FALSE;
+
+ struct mail_search_arg *arg, **argp;
+ enum mail_search_arg_type child_subargs_type;
+ bool changed = FALSE;
+
+ ARRAY(const struct mail_search_arg *) candidates;
+ t_array_init(&candidates, 1);
+
+ child_subargs_type = and_arg ? SEARCH_OR : SEARCH_SUB;
+ for (arg = *argsp; arg != NULL; arg = arg->next) {
+ if (arg->type == child_subargs_type) {
+ const struct mail_search_arg *entry = arg->value.subargs;
+ if (entry == NULL ||
+ array_lsearch(&candidates, &entry,
+ mail_search_arg_equals_p) != NULL)
+ continue;
+ array_push_back(&candidates, &entry);
+ } else {
+ struct mail_search_arg *copy = t_new(struct mail_search_arg, 1);
+ *copy = *arg;
+ copy->next = NULL;
+ const struct mail_search_arg *entry = copy;
+ array_push_back(&candidates, &entry);
+ }
+ }
+
+ const struct mail_search_arg *candidate;
+ array_foreach_elem(&candidates, candidate) {
+ /* if there are any args that include the candidate - EXCEPT the
+ one that originally contained it - drop the arg, since it is
+ redundant. (non-SUB duplicates are dropped elsewhere.) */
+ for (argp = argsp; *argp != NULL; ) {
+ if (*argp != candidate &&
+ (*argp)->type == child_subargs_type &&
+ (*argp)->value.subargs != candidate &&
+ mail_search_args_have_all_equal(*argp, candidate)) {
+ if (all_args->init_refcount > 0)
+ mail_search_arg_one_deinit(*argp);
+ *argp = (*argp)->next;
+ changed = TRUE;
+ } else {
+ argp = &(*argp)->next;
+ }
+ }
+ }
+ return changed;
+}
+
+static bool
+mail_search_args_simplify_extract_common(struct mail_search_args *all_args,
+ struct mail_search_arg **argsp,
+ pool_t pool, bool and_arg)
+{
+ /* Simple SUB example:
+ (a AND b) OR (a AND c) -> a AND (b OR c)
+
+ More complicated example:
+ (c1 AND c2 AND u1 AND u2) OR (c1 AND c2 AND u3 AND u4) ->
+ c1 AND c2 AND ((u1 AND u2) OR (u3 AND u4))
+
+ Similarly for ORs:
+ (a OR b) AND (a OR c) -> a OR (b AND c)
+
+ (c1 OR c2 OR u1 OR u2) AND (c1 OR c2 OR u3 OR u4) ->
+ c1 OR c2 OR ((u1 OR u2) AND (u3 OR u4))
+
+ */
+ struct mail_search_arg *arg, *sub_arg, *sub_next;
+ struct mail_search_arg *new_arg, *child_arg, *common_args = NULL;
+ enum mail_search_arg_type child_subargs_type;
+
+ if (*argsp == NULL || (*argsp)->next == NULL) {
+ /* single arg, nothing to extract */
+ return FALSE;
+ }
+
+ child_subargs_type = and_arg ? SEARCH_OR : SEARCH_SUB;
+
+ /* find the first arg with child_subargs_type */
+ for (arg = *argsp; arg != NULL; arg = arg->next) {
+ if (arg->type == child_subargs_type)
+ break;
+ }
+ if (arg == NULL)
+ return FALSE;
+
+ for (sub_arg = arg->value.subargs; sub_arg != NULL; sub_arg = sub_next) {
+ sub_next = sub_arg->next;
+
+ /* check if sub_arg is found from all the args */
+ for (arg = *argsp; arg != NULL; arg = arg->next) {
+ if (mail_search_arg_one_equals(arg, sub_arg)) {
+ /* the whole arg matches */
+ } else if (arg->type == child_subargs_type &&
+ mail_search_args_have_equal(arg->value.subargs, sub_arg)) {
+ /* exists as subarg */
+ } else {
+ break;
+ }
+ }
+ if (arg != NULL)
+ continue;
+
+ /* extract the arg and put it to common_args */
+ mail_search_args_remove_equal(all_args, argsp, sub_arg, TRUE);
+ sub_arg->next = common_args;
+ common_args = sub_arg;
+ }
+ if (common_args == NULL)
+ return FALSE;
+
+ /* replace all the original args with a single new SUB/OR arg */
+ new_arg = p_new(pool, struct mail_search_arg, 1);
+ new_arg->type = child_subargs_type;
+ if (*argsp == NULL) {
+ /* there are only common args */
+ new_arg->value.subargs = common_args;
+ } else {
+ /* replace OR arg with AND(OR(non_common_args), common_args)
+ or
+ replace AND arg with OR(AND(non_common_args), common_args) */
+ child_arg = p_new(pool, struct mail_search_arg, 1);
+ child_arg->type = and_arg ? SEARCH_SUB : SEARCH_OR;
+ child_arg->value.subargs = *argsp;
+ child_arg->next = common_args;
+ new_arg->value.subargs = child_arg;
+ }
+ *argsp = new_arg;
+ return TRUE;
+}
+
+static bool mail_search_args_nils_removable(enum mail_search_arg_type type) {
+ switch(type) {
+ case SEARCH_FLAGS:
+ case SEARCH_KEYWORDS:
+ case SEARCH_BEFORE:
+ case SEARCH_ON:
+ case SEARCH_SINCE:
+ case SEARCH_SMALLER:
+ case SEARCH_LARGER:
+ case SEARCH_GUID:
+ case SEARCH_MAILBOX:
+ case SEARCH_MAILBOX_GUID:
+ case SEARCH_MAILBOX_GLOB:
+ case SEARCH_MODSEQ:
+ case SEARCH_REAL_UID:
+ case SEARCH_SEQSET:
+ case SEARCH_UIDSET:
+ case SEARCH_MIMEPART:
+ case SEARCH_SAVEDATESUPPORTED:
+ /* these want NILs to become NOT ALL */
+ return FALSE;
+
+ case SEARCH_ALL:
+ case SEARCH_NIL:
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ case SEARCH_BODY:
+ case SEARCH_TEXT:
+ /* these allow to remove NILs */
+ return TRUE;
+
+ case SEARCH_INTHREAD:
+ case SEARCH_SUB:
+ case SEARCH_OR:
+ /* these are handled in the caller as they need
+ insight on the tree under that expression */
+ i_unreached();
+
+ default:
+ i_unreached();
+ }
+}
+
+static bool
+mail_search_args_handle_nils(struct mail_search_arg **argsp, bool *remove_nils_r) {
+ /* allow_remove + deny_remove + NILs count = args count */
+ int allow_remove = 0;
+ int deny_remove = 0;
+ bool changed = FALSE;
+
+ for (struct mail_search_arg *arg = *argsp; arg != NULL; arg = arg->next) {
+
+ switch(arg->type) {
+ case SEARCH_INTHREAD:
+ case SEARCH_SUB:
+ case SEARCH_OR: {
+ bool term_remove_nils;
+ if (mail_search_args_handle_nils(
+ &arg->value.subargs, &term_remove_nils))
+ changed = TRUE;
+
+ if (arg->value.subargs == NULL)
+ arg->type = SEARCH_NIL;
+ else if (term_remove_nils)
+ allow_remove++;
+ else
+ deny_remove++;
+ break;
+ }
+
+ case SEARCH_NIL:
+ break;
+
+ default:
+ if (mail_search_args_nils_removable(arg->type))
+ allow_remove++;
+ else
+ deny_remove++;
+ }
+ }
+
+ /* The NILs can be removed if either:
+ (a) no other terms deny the removal
+ (b) or at least one other term allows the removal
+ otherwise, they are replaced with NOT ALL
+
+ [DENY, NIL] -> [DENY, NOT ALL] -- NIL replaced with NOT ALL
+ [DENY, ALLOW, NIL] -> [DENY ALLOW] -- NIL removed
+ [ALLOW, NIL] -> [ALLOW] -- NIL removed
+ [NIL] -> [] -- NIL removed */
+ bool remove_nils = deny_remove == 0 || allow_remove > 0;
+ if (remove_nils_r != NULL) *remove_nils_r = remove_nils;
+
+ while (*argsp != NULL) {
+ bool is_nil = (*argsp)->type == SEARCH_NIL;
+ if (is_nil && remove_nils) {
+ changed = TRUE;
+ *argsp = (*argsp)->next;
+ } else if (is_nil) {
+ changed = TRUE;
+ (*argsp)->type = SEARCH_ALL;
+ (*argsp)->match_not = TRUE;
+ argsp = &(*argsp)->next;
+ } else {
+ argsp = &(*argsp)->next;
+ }
+ }
+
+ return changed;
+}
+
+static bool
+mail_search_args_simplify_sub(struct mail_search_args *all_args, pool_t pool,
+ struct mail_search_arg **argsp, bool parent_and)
+{
+ struct mail_search_simplify_ctx ctx;
+ struct mail_search_arg *sub, **all_argsp = argsp;
+ bool merged;
+
+ i_zero(&ctx);
+ ctx.initialized = all_args->init_refcount > 0;
+ ctx.parent_and = parent_and;
+ ctx.pool = pool_alloconly_create("mail search args simplify", 1024);
+ hash_table_create(&ctx.prev_args, ctx.pool, 0,
+ mail_search_simplify_prev_arg_hash,
+ mail_search_simplify_prev_arg_cmp);
+
+ while (*argsp != NULL) {
+ struct mail_search_arg *args = *argsp;
+
+ if (args->match_not && (args->type == SEARCH_SUB ||
+ args->type == SEARCH_OR)) {
+ /* neg(p and q and ..) == neg(p) or neg(q) or ..
+ neg(p or q or ..) == neg(p) and neg(q) and .. */
+ args->type = args->type == SEARCH_SUB ?
+ SEARCH_OR : SEARCH_SUB;
+ args->match_not = FALSE;
+ sub = args->value.subargs;
+ do {
+ sub->match_not = !sub->match_not;
+ sub = sub->next;
+ } while (sub != NULL);
+ }
+
+ if ((args->type == SEARCH_SUB && parent_and) ||
+ (args->type == SEARCH_OR && !parent_and) ||
+ ((args->type == SEARCH_SUB || args->type == SEARCH_OR) &&
+ args->value.subargs->next == NULL)) {
+ /* p and (q and ..) == p and q and ..
+ p or (q or ..) == p or q or ..
+ (p) = p */
+ sub = args->value.subargs;
+ for (; sub->next != NULL; sub = sub->next) ;
+ sub->next = args->next;
+ *args = *args->value.subargs;
+ ctx.removals = TRUE;
+ continue;
+ }
+
+ if (args->type == SEARCH_SUB ||
+ args->type == SEARCH_OR ||
+ args->type == SEARCH_INTHREAD) {
+ i_assert(!args->match_not);
+
+ if (args->type != SEARCH_INTHREAD) {
+ bool and_arg = args->type == SEARCH_SUB;
+
+ if (mail_search_args_simplify_drop_redundant_args(all_args, &args->value.subargs, and_arg))
+ ctx.removals = TRUE;
+ if (mail_search_args_simplify_extract_common(all_args, &args->value.subargs, pool, and_arg))
+ ctx.removals = TRUE;
+ }
+ if (mail_search_args_simplify_sub(all_args, pool, &args->value.subargs,
+ args->type != SEARCH_OR))
+ ctx.removals = TRUE;
+ }
+ if (args->type == SEARCH_SEQSET ||
+ args->type == SEARCH_UIDSET)
+ mail_search_args_simplify_set(args);
+
+ /* try to merge arguments */
+ merged = FALSE;
+ switch (args->type) {
+ case SEARCH_ALL: {
+ if (*all_argsp == args && args->next == NULL) {
+ /* this arg has no siblings - no merging */
+ break;
+ }
+ if ((parent_and && !args->match_not) ||
+ (!parent_and && args->match_not)) {
+ /* .. AND ALL ..
+ .. OR NOT ALL ..
+ This arg is irrelevant -> drop */
+ merged = TRUE;
+ break;
+ }
+ /* .. AND NOT ALL ..
+ .. OR ALL ..
+ The other args are irrelevant -> drop them */
+ *all_argsp = args;
+ args->next = NULL;
+ ctx.removals = TRUE;
+ break;
+ }
+ case SEARCH_FLAGS:
+ merged = mail_search_args_merge_flags(&ctx, args);
+ break;
+ case SEARCH_KEYWORDS:
+ merged = mail_search_args_merge_keywords(&ctx, args);
+ break;
+ case SEARCH_SEQSET:
+ case SEARCH_UIDSET:
+ merged = mail_search_args_merge_set(&ctx, args);
+ break;
+ case SEARCH_BEFORE:
+ case SEARCH_ON:
+ case SEARCH_SINCE:
+ merged = mail_search_args_merge_time(&ctx, args);
+ break;
+ case SEARCH_SMALLER:
+ case SEARCH_LARGER:
+ merged = mail_search_args_merge_size(&ctx, args);
+ break;
+ case SEARCH_BODY:
+ case SEARCH_TEXT:
+ if (args->value.str[0] == '\0') {
+ /* BODY "" and TEXT "" matches everything */
+ args->type = SEARCH_ALL;
+ ctx.removals = TRUE;
+ break;
+ }
+ /* fall through */
+ case SEARCH_HEADER:
+ case SEARCH_HEADER_ADDRESS:
+ case SEARCH_HEADER_COMPRESS_LWSP:
+ merged = mail_search_args_merge_text(&ctx, args);
+ break;
+ default:
+ break;
+ }
+ if (merged) {
+ *argsp = args->next;
+ ctx.removals = TRUE;
+ continue;
+ }
+
+ argsp = &args->next;
+ }
+ hash_table_destroy(&ctx.prev_args);
+ pool_unref(&ctx.pool);
+ return ctx.removals;
+}
+
+static bool
+mail_search_args_simplify_merge_flags(struct mail_search_arg **argsp,
+ bool parent_and)
+{
+ struct mail_search_arg *prev_flags = NULL;
+ bool removals = FALSE;
+
+ while (*argsp != NULL) {
+ struct mail_search_arg *args = *argsp;
+
+ if (args->type == SEARCH_SUB ||
+ args->type == SEARCH_OR ||
+ args->type == SEARCH_INTHREAD) {
+ if (mail_search_args_simplify_merge_flags(&args->value.subargs,
+ args->type != SEARCH_OR))
+ removals = TRUE;
+ } else if (args->type != SEARCH_FLAGS) {
+ /* ignore non-flags */
+ } else if (!((!args->match_not && parent_and) ||
+ (args->match_not && !parent_and))) {
+ /* can't merge these flags args */
+ } else if (prev_flags == NULL) {
+ /* first flags arg */
+ prev_flags = args;
+ } else {
+ /* merge to previous arg */
+ prev_flags->value.flags |= args->value.flags;
+ *argsp = args->next;
+ removals = TRUE;
+ continue;
+ }
+ argsp = &args->next;
+ }
+ return removals;
+}
+
+static bool
+mail_search_args_unnest_inthreads(struct mail_search_args *args,
+ struct mail_search_arg **argp,
+ bool parent_inthreads, bool parent_and)
+{
+ struct mail_search_arg *arg, *thread_arg, *or_arg;
+ bool child_inthreads = FALSE, non_inthreads = FALSE;
+
+ for (arg = *argp; arg != NULL; arg = arg->next) {
+ switch (arg->type) {
+ case SEARCH_SUB:
+ case SEARCH_OR:
+ if (!mail_search_args_unnest_inthreads(args,
+ &arg->value.subargs, parent_inthreads,
+ arg->type != SEARCH_OR)) {
+ arg->result = 1;
+ child_inthreads = TRUE;
+ } else {
+ arg->result = 0;
+ non_inthreads = TRUE;
+ }
+ break;
+ case SEARCH_INTHREAD:
+ if (mail_search_args_unnest_inthreads(args,
+ &arg->value.subargs, TRUE, TRUE)) {
+ /* children converted to SEARCH_INTHREADs */
+ arg->type = SEARCH_SUB;
+ }
+ args->have_inthreads = TRUE;
+ arg->result = 1;
+ child_inthreads = TRUE;
+ break;
+ default:
+ arg->result = 0;
+ non_inthreads = TRUE;
+ break;
+ }
+ }
+
+ if (!parent_inthreads || !child_inthreads || !non_inthreads)
+ return FALSE;
+
+ /* put all non-INTHREADs under a single INTHREAD */
+ thread_arg = p_new(args->pool, struct mail_search_arg, 1);
+ thread_arg->type = SEARCH_INTHREAD;
+
+ while (*argp != NULL) {
+ arg = *argp;
+ argp = &(*argp)->next;
+
+ if (arg->result == 0) {
+ /* not an INTHREAD or a SUB/OR with only INTHREADs */
+ arg->next = thread_arg->value.subargs;
+ thread_arg->value.subargs = arg;
+ }
+ }
+ if (!parent_and) {
+ /* We want to OR the args */
+ or_arg = p_new(args->pool, struct mail_search_arg, 1);
+ or_arg->type = SEARCH_OR;
+ or_arg->value.subargs = thread_arg->value.subargs;
+ thread_arg->value.subargs = or_arg;
+ }
+ return TRUE;
+}
+
+void mail_search_args_simplify(struct mail_search_args *args)
+{
+ args->simplified = TRUE;
+
+ bool removals = mail_search_args_handle_nils(&args->args, NULL);
+ if (mail_search_args_simplify_sub(args, args->pool, &args->args, TRUE))
+ removals = TRUE;
+ if (mail_search_args_unnest_inthreads(args, &args->args,
+ FALSE, TRUE)) {
+ /* we may have added some extra SUBs that could be dropped */
+ if (mail_search_args_simplify_sub(args, args->pool, &args->args, TRUE))
+ removals = TRUE;
+ }
+ do {
+ if (mail_search_args_simplify_drop_redundant_args(args, &args->args, TRUE))
+ removals = TRUE;
+ if (mail_search_args_simplify_extract_common(args, &args->args, args->pool, TRUE))
+ removals = TRUE;
+ if (removals)
+ removals = mail_search_args_simplify_sub(args, args->pool, &args->args, TRUE);
+ /* do the flag merging into a single arg only at the end.
+ up until then they're treated as any other search args,
+ which simplifies their handling. after the flags merging is
+ done, further simplifications are still possible. */
+ if (mail_search_args_simplify_merge_flags(&args->args, TRUE))
+ removals = TRUE;
+ } while (removals);
+}