diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-storage/mail-search.c | 806 |
1 files changed, 806 insertions, 0 deletions
diff --git a/src/lib-storage/mail-search.c b/src/lib-storage/mail-search.c new file mode 100644 index 0000000..1b14b67 --- /dev/null +++ b/src/lib-storage/mail-search.c @@ -0,0 +1,806 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "imap-match.h" +#include "mail-index.h" +#include "mail-storage.h" +#include "mail-namespace.h" +#include "mail-search-build.h" +#include "mail-search.h" +#include "mail-search-mime.h" + +static void +mailbox_uidset_change(struct mail_search_arg *arg, struct mailbox *box, + const ARRAY_TYPE(seq_range) *search_saved_uidset) +{ + struct seq_range *uids; + unsigned int i, count; + uint32_t seq1, seq2; + + if (arg->value.str != NULL && strcmp(arg->value.str, "$") == 0) { + /* SEARCHRES: Replace with saved uidset */ + array_clear(&arg->value.seqset); + if (search_saved_uidset == NULL || + !array_is_created(search_saved_uidset)) + return; + + array_append_array(&arg->value.seqset, search_saved_uidset); + return; + } + + arg->type = SEARCH_SEQSET; + + /* make a copy of the UIDs */ + count = array_count(&arg->value.seqset); + if (count == 0) { + /* empty set, keep it */ + return; + } + uids = t_new(struct seq_range, count); + memcpy(uids, array_front(&arg->value.seqset), sizeof(*uids) * count); + + /* put them back to the range as sequences */ + array_clear(&arg->value.seqset); + for (i = 0; i < count; i++) { + mailbox_get_seq_range(box, uids[i].seq1, uids[i].seq2, + &seq1, &seq2); + if (seq1 != 0) { + seq_range_array_add_range(&arg->value.seqset, + seq1, seq2); + } + if (uids[i].seq2 == (uint32_t)-1) { + /* make sure the last message is in the range */ + mailbox_get_seq_range(box, 1, (uint32_t)-1, + &seq1, &seq2); + if (seq2 != 0) + seq_range_array_add(&arg->value.seqset, seq2); + } + } +} + +static void +mailbox_seqset_change(struct mail_search_arg *arg, struct mailbox *box) +{ + const struct seq_range *seqset; + unsigned int count; + uint32_t seq1, seq2; + + seqset = array_get(&arg->value.seqset, &count); + if (count > 0 && seqset[count-1].seq2 == (uint32_t)-1) { + /* n:* -> n:maxseq. */ + mailbox_get_seq_range(box, 1, (uint32_t)-1, + &seq1, &seq2); + if (seq2 == 0) { + /* no mails in mailbox - nothing can match */ + array_clear(&arg->value.seqset); + } else if (seqset[count-1].seq1 == (uint32_t)-1) { + /* "*" alone needs a bit special handling + NOTE: This could be e.g. 5,* so use + seqset[last] */ + seq_range_array_remove(&arg->value.seqset, (uint32_t)-1); + seq_range_array_add(&arg->value.seqset, seq2); + } else { + seq_range_array_remove_range(&arg->value.seqset, + seq2+1, (uint32_t)-1); + } + } +} + +static void +mail_search_arg_change_sets(struct mail_search_args *args, + struct mail_search_arg *arg, + const ARRAY_TYPE(seq_range) *search_saved_uidset) +{ + for (; arg != NULL; arg = arg->next) { + switch (arg->type) { + case SEARCH_SEQSET: + mailbox_seqset_change(arg, args->box); + break; + case SEARCH_UIDSET: + T_BEGIN { + mailbox_uidset_change(arg, args->box, + search_saved_uidset); + } T_END; + break; + case SEARCH_INTHREAD: + case SEARCH_SUB: + case SEARCH_OR: + mail_search_arg_change_sets(args, arg->value.subargs, + search_saved_uidset); + break; + default: + break; + } + } +} + +void mail_search_arg_init(struct mail_search_args *args, + struct mail_search_arg *arg) +{ + struct mail_search_args *thread_args; + const char *keywords[2]; + + for (; arg != NULL; arg = arg->next) { + switch (arg->type) { + case SEARCH_MODSEQ: + if (arg->value.str == NULL) + break; + /* fall through - modseq with keyword */ + case SEARCH_KEYWORDS: + keywords[0] = arg->value.str; + keywords[1] = NULL; + + i_assert(arg->initialized.keywords == NULL); + arg->initialized.keywords = + mailbox_keywords_create_valid(args->box, + keywords); + break; + + case SEARCH_MAILBOX_GLOB: { + struct mail_namespace *ns = + mailbox_get_namespace(args->box); + + arg->initialized.mailbox_glob = + imap_match_init(default_pool, arg->value.str, + TRUE, mail_namespace_get_sep(ns)); + break; + } + case SEARCH_INTHREAD: + thread_args = arg->initialized.search_args; + if (thread_args == NULL) { + arg->initialized.search_args = thread_args = + p_new(args->pool, + struct mail_search_args, 1); + thread_args->pool = args->pool; + thread_args->args = arg->value.subargs; + thread_args->simplified = TRUE; + thread_args->init_refcount = 1; + /* simplification should have unnested all + inthreads, so we'll assume that + have_inthreads=FALSE */ + } + thread_args->refcount++; + thread_args->box = args->box; + /* fall through */ + case SEARCH_SUB: + case SEARCH_OR: + mail_search_arg_init(args, arg->value.subargs); + break; + default: + break; + } + } +} + +void mail_search_args_init(struct mail_search_args *args, + struct mailbox *box, bool change_sets, + const ARRAY_TYPE(seq_range) *search_saved_uidset) +{ + i_assert(args->init_refcount <= args->refcount); + + if (args->init_refcount++ > 0) { + i_assert(args->box == box); + return; + } + + args->box = box; + if (change_sets) { + /* Change seqsets/uidsets before simplifying the args, since it + can't handle search_saved_uidset. */ + mail_search_arg_change_sets(args, args->args, + search_saved_uidset); + } + if (!args->simplified) + mail_search_args_simplify(args); + mail_search_arg_init(args, args->args); +} + +void mail_search_arg_deinit(struct mail_search_arg *arg) +{ + for (; arg != NULL; arg = arg->next) + mail_search_arg_one_deinit(arg); +} + +void mail_search_arg_one_deinit(struct mail_search_arg *arg) +{ + switch (arg->type) { + case SEARCH_MODSEQ: + case SEARCH_KEYWORDS: + if (arg->initialized.keywords == NULL) + break; + mailbox_keywords_unref(&arg->initialized.keywords); + break; + case SEARCH_MAILBOX_GLOB: + if (arg->initialized.mailbox_glob == NULL) + break; + + imap_match_deinit(&arg->initialized.mailbox_glob); + break; + case SEARCH_INTHREAD: + i_assert(arg->initialized.search_args->refcount > 0); + if (arg->value.search_result != NULL) + mailbox_search_result_free(&arg->value.search_result); + arg->initialized.search_args->refcount--; + arg->initialized.search_args->box = NULL; + /* fall through */ + case SEARCH_SUB: + case SEARCH_OR: + mail_search_arg_deinit(arg->value.subargs); + break; + default: + break; + } +} + +void mail_search_args_deinit(struct mail_search_args *args) +{ + if (--args->init_refcount > 0) + return; + + mail_search_arg_deinit(args->args); + args->box = NULL; +} + +static void mail_search_args_seq2uid_sub(struct mail_search_args *args, + struct mail_search_arg *arg, + ARRAY_TYPE(seq_range) *uids) +{ + for (; arg != NULL; arg = arg->next) { + switch (arg->type) { + case SEARCH_SEQSET: + array_clear(uids); + mailbox_get_uid_range(args->box, + &arg->value.seqset, uids); + + /* replace sequences with UIDs in the existing array. + this way it's possible to switch between uidsets and + seqsets constantly without leaking memory */ + arg->type = SEARCH_UIDSET; + array_clear(&arg->value.seqset); + array_append_array(&arg->value.seqset, uids); + break; + case SEARCH_SUB: + case SEARCH_OR: + case SEARCH_INTHREAD: + mail_search_args_seq2uid_sub(args, arg->value.subargs, + uids); + break; + default: + break; + } + } +} + +void mail_search_args_seq2uid(struct mail_search_args *args) +{ + T_BEGIN { + ARRAY_TYPE(seq_range) uids; + + t_array_init(&uids, 128); + mail_search_args_seq2uid_sub(args, args->args, &uids); + } T_END; +} + +void mail_search_args_ref(struct mail_search_args *args) +{ + i_assert(args->refcount > 0); + + args->refcount++; +} + +void mail_search_args_unref(struct mail_search_args **_args) +{ + struct mail_search_args *args = *_args; + + i_assert(args->refcount > 0); + + *_args = NULL; + if (--args->refcount > 0) { + i_assert(args->init_refcount <= args->refcount); + return; + } + i_assert(args->init_refcount <= 1); + if (args->init_refcount == 1) + mail_search_args_deinit(args); + pool_unref(&args->pool); +} + +static struct mail_search_arg * +mail_search_arg_dup_one(pool_t pool, const struct mail_search_arg *arg) +{ + struct mail_search_arg *new_arg; + + new_arg = p_new(pool, struct mail_search_arg, 1); + new_arg->type = arg->type; + new_arg->match_not = arg->match_not; + new_arg->match_always = arg->match_always; + new_arg->nonmatch_always = arg->nonmatch_always; + new_arg->fuzzy = arg->fuzzy; + new_arg->value.search_flags = arg->value.search_flags; + + switch (arg->type) { + case SEARCH_INTHREAD: + new_arg->value.thread_type = arg->value.thread_type; + /* fall through */ + case SEARCH_OR: + case SEARCH_SUB: + new_arg->value.subargs = + mail_search_arg_dup(pool, arg->value.subargs); + break; + case SEARCH_ALL: + case SEARCH_SAVEDATESUPPORTED: + break; + case SEARCH_SEQSET: + case SEARCH_UIDSET: + case SEARCH_REAL_UID: + p_array_init(&new_arg->value.seqset, pool, + array_count(&arg->value.seqset)); + array_append_array(&new_arg->value.seqset, &arg->value.seqset); + break; + case SEARCH_FLAGS: + new_arg->value.flags = arg->value.flags; + break; + case SEARCH_BEFORE: + case SEARCH_ON: + case SEARCH_SINCE: + new_arg->value.time = arg->value.time; + new_arg->value.date_type = arg->value.date_type; + break; + case SEARCH_SMALLER: + case SEARCH_LARGER: + new_arg->value.size = arg->value.size; + break; + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + new_arg->hdr_field_name = p_strdup(pool, arg->hdr_field_name); + /* fall through */ + case SEARCH_KEYWORDS: + case SEARCH_BODY: + case SEARCH_TEXT: + case SEARCH_GUID: + case SEARCH_MAILBOX: + case SEARCH_MAILBOX_GUID: + case SEARCH_MAILBOX_GLOB: + new_arg->value.str = p_strdup(pool, arg->value.str); + break; + case SEARCH_MODSEQ: + new_arg->value.modseq = + p_new(pool, struct mail_search_modseq, 1); + *new_arg->value.modseq = *arg->value.modseq; + break; + case SEARCH_MIMEPART: + new_arg->value.mime_part = + mail_search_mime_part_dup(pool, arg->value.mime_part); + break; + } + return new_arg; +} + +struct mail_search_arg * +mail_search_arg_dup(pool_t pool, const struct mail_search_arg *arg) +{ + struct mail_search_arg *new_arg = NULL, **dest = &new_arg; + + for (; arg != NULL; arg = arg->next) { + *dest = mail_search_arg_dup_one(pool, arg); + dest = &(*dest)->next; + } + return new_arg; +} + +struct mail_search_args * +mail_search_args_dup(const struct mail_search_args *args) +{ + struct mail_search_args *new_args; + + new_args = mail_search_build_init(); + new_args->simplified = args->simplified; + new_args->have_inthreads = args->have_inthreads; + new_args->args = mail_search_arg_dup(new_args->pool, args->args); + return new_args; +} + +void mail_search_args_reset(struct mail_search_arg *args, bool full_reset) +{ + while (args != NULL) { + if (args->type == SEARCH_OR || args->type == SEARCH_SUB) + mail_search_args_reset(args->value.subargs, full_reset); + + if (args->match_always) { + if (!full_reset) + args->result = 1; + else { + args->match_always = FALSE; + args->result = -1; + } + } else if (args->nonmatch_always) { + if (!full_reset) + args->result = 0; + else { + args->nonmatch_always = FALSE; + args->result = -1; + } + } else { + args->result = -1; + } + + args = args->next; + } +} + +static void search_arg_foreach(struct mail_search_arg *arg, + mail_search_foreach_callback_t *callback, + void *context) +{ + struct mail_search_arg *subarg; + + if (arg->result != -1) + return; + + if (arg->type == SEARCH_SUB) { + /* sublist of conditions */ + i_assert(arg->value.subargs != NULL); + + arg->result = 1; + subarg = arg->value.subargs; + while (subarg != NULL) { + if (subarg->result == -1) + search_arg_foreach(subarg, callback, context); + + if (subarg->result == -1) + arg->result = -1; + else if (subarg->result == 0) { + /* didn't match */ + arg->result = 0; + break; + } + + subarg = subarg->next; + } + if (arg->match_not && arg->result != -1) + arg->result = arg->result > 0 ? 0 : 1; + } else if (arg->type == SEARCH_OR) { + /* OR-list of conditions */ + i_assert(arg->value.subargs != NULL); + + subarg = arg->value.subargs; + arg->result = 0; + while (subarg != NULL) { + if (subarg->result == -1) + search_arg_foreach(subarg, callback, context); + + if (subarg->result == -1) + arg->result = -1; + else if (subarg->result > 0) { + /* matched */ + arg->result = 1; + break; + } + + subarg = subarg->next; + } + if (arg->match_not && arg->result != -1) + arg->result = arg->result > 0 ? 0 : 1; + } else { + /* just a single condition */ + callback(arg, context); + } +} + +#undef mail_search_args_foreach +int mail_search_args_foreach(struct mail_search_arg *args, + mail_search_foreach_callback_t *callback, + void *context) +{ + int result; + + result = 1; + for (; args != NULL; args = args->next) { + search_arg_foreach(args, callback, context); + + if (args->result == 0) { + /* didn't match */ + return 0; + } + + if (args->result == -1) + result = -1; + } + + return result; +} + +static void +search_arg_analyze(struct mail_search_arg *arg, buffer_t *headers, + bool *have_body, bool *have_text) +{ + static const char *date_hdr = "Date"; + struct mail_search_arg *subarg; + + if (arg->result != -1) + return; + + switch (arg->type) { + case SEARCH_OR: + case SEARCH_SUB: + subarg = arg->value.subargs; + while (subarg != NULL) { + if (subarg->result == -1) { + search_arg_analyze(subarg, headers, + have_body, have_text); + } + + subarg = subarg->next; + } + break; + case SEARCH_BEFORE: + case SEARCH_ON: + case SEARCH_SINCE: + if (arg->value.date_type == MAIL_SEARCH_DATE_TYPE_SENT) + buffer_append(headers, &date_hdr, sizeof(const char *)); + break; + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + buffer_append(headers, &arg->hdr_field_name, + sizeof(const char *)); + break; + case SEARCH_BODY: + *have_body = TRUE; + break; + case SEARCH_TEXT: + *have_text = TRUE; + *have_body = TRUE; + break; + default: + break; + } +} + +const char *const * +mail_search_args_analyze(struct mail_search_arg *args, + bool *have_headers, bool *have_body) +{ + const char *null = NULL; + buffer_t *headers; + bool have_text; + + *have_headers = *have_body = have_text = FALSE; + + headers = t_buffer_create(128); + for (; args != NULL; args = args->next) + search_arg_analyze(args, headers, have_body, &have_text); + + *have_headers = have_text || headers->used != 0; + + if (headers->used == 0) + return NULL; + + buffer_append(headers, &null, sizeof(const char *)); + return headers->data; +} + +static bool +mail_search_args_match_mailbox_arg(const struct mail_search_arg *arg, + const char *vname, char sep) +{ + const struct mail_search_arg *subarg; + bool ret; + + switch (arg->type) { + case SEARCH_OR: + subarg = arg->value.subargs; + for (; subarg != NULL; subarg = subarg->next) { + if (mail_search_args_match_mailbox_arg(subarg, + vname, sep)) + return TRUE; + } + return FALSE; + case SEARCH_SUB: + case SEARCH_INTHREAD: + subarg = arg->value.subargs; + for (; subarg != NULL; subarg = subarg->next) { + if (!mail_search_args_match_mailbox_arg(subarg, + vname, sep)) + return FALSE; + } + return TRUE; + case SEARCH_MAILBOX: + ret = strcmp(arg->value.str, vname) == 0; + return ret != arg->match_not; + case SEARCH_MAILBOX_GLOB: { + T_BEGIN { + struct imap_match_glob *glob; + + glob = imap_match_init(pool_datastack_create(), + arg->value.str, TRUE, sep); + ret = imap_match(glob, vname) == IMAP_MATCH_YES; + } T_END; + return ret != arg->match_not; + } + default: + break; + } + return TRUE; +} + +bool mail_search_args_match_mailbox(struct mail_search_args *args, + const char *vname, char sep) +{ + const struct mail_search_arg *arg; + + if (!args->simplified) + mail_search_args_simplify(args); + + for (arg = args->args; arg != NULL; arg = arg->next) { + if (!mail_search_args_match_mailbox_arg(arg, vname, sep)) + return FALSE; + } + return TRUE; +} + +bool mail_search_arg_one_equals(const struct mail_search_arg *arg1, + const struct mail_search_arg *arg2) +{ + if (arg1->type != arg2->type || + arg1->match_not != arg2->match_not || + arg1->fuzzy != arg2->fuzzy || + arg1->value.search_flags != arg2->value.search_flags) + return FALSE; + + switch (arg1->type) { + case SEARCH_OR: + case SEARCH_SUB: + return mail_search_arg_equals(arg1->value.subargs, + arg2->value.subargs); + + case SEARCH_ALL: + case SEARCH_SAVEDATESUPPORTED: + return TRUE; + case SEARCH_SEQSET: + /* sequences may point to different messages at different times, + never assume they match */ + return FALSE; + case SEARCH_UIDSET: + return array_cmp(&arg1->value.seqset, &arg2->value.seqset); + case SEARCH_REAL_UID: + return array_cmp(&arg1->value.seqset, &arg2->value.seqset); + + case SEARCH_FLAGS: + return arg1->value.flags == arg2->value.flags; + case SEARCH_KEYWORDS: + return strcasecmp(arg1->value.str, arg2->value.str) == 0; + + case SEARCH_BEFORE: + case SEARCH_ON: + case SEARCH_SINCE: + return arg1->value.time == arg2->value.time && + arg1->value.date_type == arg2->value.date_type; + + case SEARCH_SMALLER: + case SEARCH_LARGER: + return arg1->value.size == arg2->value.size; + + case SEARCH_HEADER: + case SEARCH_HEADER_ADDRESS: + case SEARCH_HEADER_COMPRESS_LWSP: + if (strcasecmp(arg1->hdr_field_name, arg2->hdr_field_name) != 0) + return FALSE; + /* fall through */ + case SEARCH_BODY: + case SEARCH_TEXT: + case SEARCH_GUID: + case SEARCH_MAILBOX: + case SEARCH_MAILBOX_GUID: + case SEARCH_MAILBOX_GLOB: + /* don't bother doing case-insensitive comparison. it must not + be done for guid/mailbox, and for others we should support + full i18n case-insensitivity (or the active comparator + in future). */ + return strcmp(arg1->value.str, arg2->value.str) == 0; + + case SEARCH_MODSEQ: { + const struct mail_search_modseq *m1 = arg1->value.modseq; + const struct mail_search_modseq *m2 = arg2->value.modseq; + + return m1->modseq == m2->modseq && + m1->type == m2->type; + } + case SEARCH_INTHREAD: + if (arg1->value.thread_type != arg2->value.thread_type) + return FALSE; + return mail_search_arg_equals(arg1->value.subargs, + arg2->value.subargs); + case SEARCH_MIMEPART: + return mail_search_mime_parts_equal(arg1->value.mime_part, + arg2->value.mime_part); + + } + i_unreached(); +} + +bool mail_search_arg_equals(const struct mail_search_arg *arg1, + const struct mail_search_arg *arg2) +{ + while (arg1 != NULL && arg2 != NULL) { + if (!mail_search_arg_one_equals(arg1, arg2)) + return FALSE; + arg1 = arg1->next; + arg2 = arg2->next; + } + return arg1 == NULL && arg2 == NULL; +} + +bool mail_search_args_equal(const struct mail_search_args *args1, + const struct mail_search_args *args2) +{ + i_assert(args1->simplified == args2->simplified); + i_assert(args1->box == args2->box); + + return mail_search_arg_equals(args1->args, args2->args); +} + +static void +mail_search_args_result_serialize_arg(const struct mail_search_arg *arg, + buffer_t *dest) +{ + const struct mail_search_arg *subarg; + + buffer_append_c(dest, arg->result < 0 ? 0xff : arg->result); + + switch (arg->type) { + case SEARCH_OR: + case SEARCH_SUB: + case SEARCH_INTHREAD: + subarg = arg->value.subargs; + for (; subarg != NULL; subarg = subarg->next) + mail_search_args_result_serialize_arg(subarg, dest); + default: + break; + } +} + +void mail_search_args_result_serialize(const struct mail_search_args *args, + buffer_t *dest) +{ + const struct mail_search_arg *arg; + + for (arg = args->args; arg != NULL; arg = arg->next) + mail_search_args_result_serialize_arg(arg, dest); +} + +static void +mail_search_args_result_deserialize_arg(struct mail_search_arg *arg, + const unsigned char **data, + size_t *size) +{ + struct mail_search_arg *subarg; + + i_assert(*size > 0); + arg->result = **data == 0xff ? -1 : **data; + *data += 1; *size -= 1; + + switch (arg->type) { + case SEARCH_OR: + case SEARCH_SUB: + case SEARCH_INTHREAD: + subarg = arg->value.subargs; + for (; subarg != NULL; subarg = subarg->next) { + mail_search_args_result_deserialize_arg(subarg, + data, size); + } + default: + break; + } +} + +void mail_search_args_result_deserialize(struct mail_search_args *args, + const unsigned char *data, size_t size) +{ + struct mail_search_arg *arg; + + for (arg = args->args; arg != NULL; arg = arg->next) + mail_search_args_result_deserialize_arg(arg, &data, &size); +} |