imap-search.c revision be6bde0e1f40db97871b657603188fc9041f1d04
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic int imap_search_deinit(struct imap_search_context *ctx);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenimap_partial_range_parse(struct imap_search_context *ctx, const char *str)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen ctx->partial1 = ctx->partial1 * 10 + *str-'0';
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen for (str++; *str >= '0' && *str <= '9'; str++)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen ctx->partial2 = ctx->partial2 * 10 + *str-'0';
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainensearch_parse_fetch_att(struct imap_search_context *ctx,
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen ctx->fetch_pool = pool_alloconly_create("search update fetch", 512);
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen if (imap_fetch_att_list_parse(ctx->cmd->client, ctx->fetch_pool,
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen client_send_command_error(ctx->cmd, t_strconcat(
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainensearch_parse_return_options(struct imap_search_context *ctx,
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen struct client_command_context *cmd = ctx->cmd;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen unsigned int idx;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen "SEARCH return options contain non-atoms.");
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0) {
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen "SEARCH return options have duplicate UPDATE.");
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen if (!search_parse_fetch_att(ctx, update_args))
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen ctx->return_options |= SEARCH_RETURN_RELEVANCY;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen "PARTIAL can be used only once.");
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen "PARTIAL range missing.");
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen "PARTIAL range broken.");
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen "Unknown SEARCH return option");
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0 &&
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen client_search_update_lookup(cmd->client, cmd->tag, &idx) != NULL) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen client_send_command_error(cmd, "Duplicate search update tag");
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_PARTIAL) != 0 &&
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen (ctx->return_options & SEARCH_RETURN_ALL) != 0) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen client_send_command_error(cmd, "PARTIAL conflicts with ALL");
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic void imap_search_args_check(struct imap_search_context *ctx,
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen imap_search_args_check(ctx, sargs->value.subargs);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic void imap_search_result_save(struct imap_search_context *ctx)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if (!array_is_created(&client->search_updates))
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen else if (array_count(&client->search_updates) >=
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* too many updates */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen result = mailbox_search_result_save(ctx->search_ctx,
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen update = array_append_space(&client->search_updates);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic void imap_search_send_result_standard(struct imap_search_context *ctx)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen str_append(str, ctx->sorting ? "* SORT" : "* SEARCH");
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen for (seq = range->seq1; seq <= range->seq2; seq++)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen (unsigned long long)ctx->highest_seen_modseq);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->cmd->client->output, str_data(str), str_len(str));
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenimap_search_send_partial(struct imap_search_context *ctx, string_t *str)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen str_printfa(str, " PARTIAL (%u:%u ", ctx->partial1, ctx->partial2);
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen /* we need to be able to handle non-sorted seq ranges (for SORT
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen replies), so do this ourself instead of using seq_range_array_*()
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen functions. */
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen range = array_get_modifiable(&ctx->result, &count);
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen /* delete everything up to partial1 */
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen for (i = n = 0; i < count; i++) {
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen /* partial1 points past the result */
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen /* delete everything after partial2 */
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen range[i].seq2 = range[i].seq1 + (ctx->partial2 - n);
7da99e97d68f854b8726755d36dfb24b6cf08701Timo Sirainen array_delete(&ctx->result, i + 1, count-(i+1));
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* no results (in range) */
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainenimap_search_send_relevancy(struct imap_search_context *ctx, string_t *dest)
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen const float *scores;
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen unsigned int i, count;
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen scores = array_get(&ctx->relevancy_scores, &count);
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen /* we'll need to convert float scores to numbers 1..100
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen FIXME: would be a good idea to try to detect non-linear score
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen mappings and convert them better.. */
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen diff = ctx->max_relevancy - ctx->min_relevancy;
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen for (i = 0; i < count; i++) {
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen imap_score = (scores[i] - ctx->min_relevancy) / diff * 100.0;
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen str_printfa(dest, "%u", (unsigned int)imap_score);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic void imap_search_send_result(struct imap_search_context *ctx)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen unsigned int count;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_ESEARCH) == 0) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_SAVE)) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* we only wanted to save the result, don't return
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen ESEARCH result. */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_MIN) != 0)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_MAX) != 0)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen str_printfa(str, " MAX %u", range[count-1].seq2);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_ALL) != 0) {
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen if ((ctx->return_options & SEARCH_RETURN_RELEVANCY) != 0) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_PARTIAL) != 0)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_COUNT) != 0)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen str_printfa(str, " COUNT %u", ctx->result_count);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen (unsigned long long)ctx->highest_seen_modseq);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(client->output, str_data(str), str_len(str));
eef4ba0cc3e78f8c26804c1c9251a76580a41f0cTimo Sirainensearch_update_mail(struct imap_search_context *ctx, struct mail *mail)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_MODSEQ) != 0) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen seq_range_array_add(&ctx->cmd->client->search_saved_uidset,
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen if ((ctx->return_options & SEARCH_RETURN_RELEVANCY) != 0) {
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen const char *str;
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen if (mail_get_special(mail, MAIL_FETCH_SEARCH_RELEVANCY, &str) < 0)
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen array_append(&ctx->relevancy_scores, &score, 1);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic void search_add_result_id(struct imap_search_context *ctx, uint32_t id)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen unsigned int count;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* only append the data. this is especially important when we're
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen returning a sort result. */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen range = array_get_modifiable(&ctx->result, &count);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if (count > 0 && id == range[count-1].seq2 + 1) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic bool cmd_search_more(struct client_command_context *cmd)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen struct imap_search_context *ctx = cmd->context;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen enum search_return_options opts = ctx->return_options;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen unsigned int count;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen minmax = (opts & (SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) != 0 &&
eef4ba0cc3e78f8c26804c1c9251a76580a41f0cTimo Sirainen while (mailbox_search_next_nonblock(ctx->search_ctx,
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* we only care about min/max */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if (id_min == 0 && (opts & SEARCH_RETURN_MIN) != 0)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* return option updates are delayed until
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen we know the actual min/max values */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* we only want to count (and get modseqs) */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if (minmax && array_count(&ctx->result) > 0 &&
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen (opts & (SEARCH_RETURN_MODSEQ | SEARCH_RETURN_SAVE)) != 0) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* handle MIN/MAX modseq/save updates */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen lost_data = mailbox_search_seen_lost_data(ctx->search_ctx);
08837f59c1466ec0f533f120b167f2a3e87da738Timo Sirainen client_send_box_error(cmd, cmd->client->mailbox);
115cf0320f679e4e63db25e7f44f47977b8338deTimo Sirainen ok_reply = t_strdup_printf("OK %s%s completed",
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen lost_data ? "["IMAP_RESP_CODE_EXPUNGEISSUED"] " : "",
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen return cmd_sync(cmd, sync_flags, 0, ok_reply);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic void cmd_search_more_callback(struct client_command_context *cmd)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenint cmd_search_parse_return_if_found(struct imap_search_context *ctx,
c2fbbf7515aa419dc8b2d62a3c2bb0471d51a391Timo Sirainen const struct imap_arg *list_args, *args = *_args;
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen struct client_command_context *cmd = ctx->cmd;
c2fbbf7515aa419dc8b2d62a3c2bb0471d51a391Timo Sirainen if (!imap_arg_atom_equals(&args[0], "RETURN") ||
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen if (!search_parse_return_options(ctx, list_args)) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* wait if there is another SEARCH SAVE command running. */
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainen if (client_handle_search_save_ambiguity(cmd)) {
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* make sure the search result gets cleared if SEARCH fails */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if (array_is_created(&cmd->client->search_saved_uidset))
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen array_clear(&cmd->client->search_saved_uidset);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen i_array_init(&cmd->client->search_saved_uidset, 128);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenbool imap_search_start(struct imap_search_context *ctx,
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen struct client_command_context *cmd = ctx->cmd;
51327f2489a4e0e615eb9f7d921473cf8512bb79Timo Sirainen (void)client_enable(cmd->client, MAILBOX_FEATURE_CONDSTORE);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen ctx->trans = mailbox_transaction_begin(ctx->box, 0);
eef4ba0cc3e78f8c26804c1c9251a76580a41f0cTimo Sirainen mailbox_search_init(ctx->trans, sargs, sort_program, 0, NULL);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0)
da2f9bc5d477bdfee1773d7dcbcdd5a293c7d48cTimo Sirainen if ((ctx->return_options & SEARCH_RETURN_RELEVANCY) != 0)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* we may have moved onto syncing by now */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen ctx->to = timeout_add(0, cmd_search_more_callback, cmd);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainenstatic int imap_search_deinit(struct imap_search_context *ctx)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if (mailbox_search_deinit(&ctx->search_ctx) < 0)
be6bde0e1f40db97871b657603188fc9041f1d04Timo Sirainen /* Send the result also after failing. It might have something useful,
be6bde0e1f40db97871b657603188fc9041f1d04Timo Sirainen even though it didn't fully succeed. The client should be able to
be6bde0e1f40db97871b657603188fc9041f1d04Timo Sirainen realize that there was some failure because NO is returned. */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen /* search failed */
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0)
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen array_clear(&ctx->cmd->client->search_saved_uidset);
d43bed2d458520fd01c28229ce2b178a4593a4a7Timo Sirainen (void)mailbox_transaction_commit(&ctx->trans);
51cbc45fc1ac5dde29bc2adbb175945df1b4f7d4Timo Sirainenvoid imap_search_context_free(struct imap_search_context *ctx)