index-search.c revision 06ff2a72c39cb34cc6425f17fc82c5e93fef2018
/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "istream.h"
#include "utc-offset.h"
#include "str.h"
#include "message-address.h"
#include "message-date.h"
#include "message-search.h"
#include "message-parser.h"
#include "mail-index-modseq.h"
#include "index-storage.h"
#include "index-mail.h"
#include "index-sort.h"
#include "mail-search.h"
#include "mailbox-search-result-private.h"
#include <stdlib.h>
#include <ctype.h>
#define TXT_UNKNOWN_CHARSET "[BADCHARSET] Unknown charset"
#define TXT_INVALID_SEARCH_KEY "Invalid search key"
#define SEARCH_NONBLOCK_COUNT 20
#define SEARCH_NOTIFY_INTERVAL_SECS 10
struct index_search_context {
struct mail_search_context mail_ctx;
struct mail_index_view *view;
struct index_mailbox *ibox;
struct index_mail *imail;
struct mail_thread_context *thread_ctx;
const char *error;
unsigned int failed:1;
unsigned int sorted:1;
unsigned int have_seqsets:1;
unsigned int have_index_args:1;
};
struct search_header_context {
struct index_search_context *index_context;
struct mail_search_arg *args;
struct message_header_line *hdr;
unsigned int parse_headers:1;
unsigned int custom_header:1;
unsigned int threading:1;
};
struct search_body_context {
struct index_search_context *index_ctx;
const struct message_part *part;
};
static const enum message_header_parser_flags hdr_parser_flags =
struct mail_search_arg *args,
struct index_search_context *ctx)
{
case SEARCH_SEQSET:
break;
case SEARCH_UIDSET:
case SEARCH_INTHREAD:
case SEARCH_FLAGS:
case SEARCH_KEYWORDS:
case SEARCH_MODSEQ:
break;
case SEARCH_ALL:
break;
default:
break;
}
}
struct index_search_context *ctx)
{
else
ARG_SET_RESULT(arg, 0);
}
}
struct mail_search_arg *arg)
{
const unsigned int *keyword_indexes;
unsigned int i, j, count;
/* there probably aren't many keywords, so O(n*m) for now */
for (i = 0; i < search_kws->count; i++) {
for (j = 0; j < count; j++) {
break;
}
if (j == count)
return 0;
}
return 1;
}
/* Returns >0 = matched, 0 = not matched, -1 = unknown */
struct mail_search_arg *arg,
const struct mail_index_record *rec)
{
enum mail_flags flags;
int ret;
case SEARCH_UIDSET:
case SEARCH_INTHREAD:
case SEARCH_FLAGS:
/* recent flag shouldn't be set, but indexes from v1.0.x
may contain it. */
flags |= MAIL_RECENT;
case SEARCH_KEYWORDS:
T_BEGIN {
} T_END;
return ret;
case SEARCH_MODSEQ: {
} else {
}
}
default:
return -1;
}
}
struct index_search_context *ctx)
{
const struct mail_index_record *rec;
case -1:
/* unknown */
break;
case 0:
ARG_SET_RESULT(arg, 0);
break;
default:
break;
}
}
/* Returns >0 = matched, 0 = not matched, -1 = unknown */
struct mail_search_arg *arg)
{
const char *str;
int timezone_offset;
/* internal dates */
case SEARCH_BEFORE:
case SEARCH_ON:
case SEARCH_SINCE:
return -1;
MAIL_SEARCH_ARG_FLAG_USE_TZ) == 0) {
}
case SEARCH_BEFORE:
case SEARCH_ON:
case SEARCH_SINCE:
default:
/* unreachable */
break;
}
/* sent dates */
case SEARCH_SENTBEFORE:
case SEARCH_SENTON:
case SEARCH_SENTSINCE:
/* NOTE: RFC-3501 specifies that timezone is ignored
in searches. date is returned as UTC, so change it. */
return -1;
MAIL_SEARCH_ARG_FLAG_USE_TZ) == 0)
case SEARCH_SENTBEFORE:
case SEARCH_SENTON:
case SEARCH_SENTSINCE:
default:
/* unreachable */
break;
}
/* sizes */
case SEARCH_SMALLER:
case SEARCH_LARGER:
return -1;
else
case SEARCH_MAILBOX:
&str) < 0)
return -1;
default:
return -1;
}
}
struct index_search_context *ctx)
{
case -1:
/* unknown */
break;
case 0:
ARG_SET_RESULT(arg, 0);
break;
default:
break;
}
}
{
int timezone_offset;
if (sent_value == NULL)
return 0;
/* NOTE: RFC-3501 specifies that timezone is ignored
in searches. sent_time is returned as UTC, so change it. */
&sent_time, &timezone_offset))
return 0;
switch (type) {
case SEARCH_SENTBEFORE:
return sent_time < search_time;
case SEARCH_SENTON:
return sent_time >= search_time &&
case SEARCH_SENTSINCE:
return sent_time >= search_time;
default:
i_unreached();
}
}
static struct message_search_context *
struct mail_search_arg *arg)
{
enum message_search_flags flags;
int ret;
return arg_ctx;
&arg_ctx);
if (ret > 0) {
return arg_ctx;
}
if (ret == 0)
else
return NULL;
}
unsigned int src_len)
{
unsigned int i;
for (i = 0; i < src_len; i++) {
if (!prev_lwsp) {
}
} else {
}
}
}
struct search_header_context *ctx)
{
struct message_search_context *msg_search_ctx;
struct message_block block;
struct message_header_line hdr;
int ret;
/* first check that the field name matches to argument. */
case SEARCH_SENTBEFORE:
case SEARCH_SENTON:
case SEARCH_SENTSINCE:
/* date is handled differently than others */
return;
}
}
return;
case SEARCH_HEADER:
case SEARCH_HEADER_ADDRESS:
return;
break;
default:
return;
}
/* we're just testing existence of the field. always matches. */
return;
}
return;
}
/* We're searching only for values, so drop header name and middle
parts. We use header searching so that MIME words will be decoded. */
hdr.middle_len = 0;
if (msg_search_ctx == NULL)
return;
T_BEGIN {
struct message_address *addr;
case SEARCH_HEADER:
/* simple match */
break;
case SEARCH_HEADER_ADDRESS:
/* we have to match against normalized address */
(unsigned int)-1, TRUE);
break;
/* convert LWSP to single spaces */
break;
default:
i_unreached();
}
} T_END;
}
void *context ATTR_UNUSED)
{
case SEARCH_SENTBEFORE:
case SEARCH_SENTON:
case SEARCH_SENTSINCE:
/* date header not found, so we match only for
NOT searches */
ARG_SET_RESULT(arg, 0);
}
break;
case SEARCH_HEADER:
case SEARCH_HEADER_ADDRESS:
ARG_SET_RESULT(arg, 0);
break;
default:
break;
}
}
struct search_header_context *ctx)
{
/* end of headers, mark all unknown SEARCH_HEADERs unmatched */
return;
}
return;
if (ctx->parse_headers)
}
}
struct search_body_context *ctx)
{
struct message_search_context *msg_search_ctx;
int ret;
return;
case SEARCH_BODY:
case SEARCH_BODY_FAST:
case SEARCH_TEXT:
case SEARCH_TEXT_FAST:
break;
default:
return;
}
if (msg_search_ctx == NULL) {
ARG_SET_RESULT(arg, 0);
return;
}
/* try again without cached parts */
}
}
struct index_search_context *ctx)
{
struct mailbox_header_lookup_ctx *headers_ctx;
const char *const *headers;
bool have_headers, have_body;
/* first check what we need to use */
if (!have_headers && !have_body)
return TRUE;
if (have_headers) {
struct search_header_context hdr_ctx;
if (have_body)
headers_ctx = NULL;
return FALSE;
} else {
/* FIXME: do this once in init */
headers);
&input) < 0) {
return FALSE;
}
}
if (hdr_ctx.parse_headers)
search_header, &hdr_ctx);
if (headers_ctx != NULL)
} else {
struct message_size hdr_size;
return FALSE;
}
if (have_body) {
struct search_body_context body_ctx;
}
return TRUE;
}
{
unsigned int count;
if (count > 0) {
/* "*" used, make sure the last message is in the range
(e.g. with count+1:* we still want to include it) */
}
/* remove all non-existing messages */
(uint32_t)-1);
}
if (!not)
return array_count(seqset) > 0;
else {
/* if all messages are in the range, it can't match */
}
}
{
unsigned int count;
*seq2_r = 0;
return;
}
if (!not) {
} else {
*seq2_r = 0;
return;
}
}
}
struct mail_search_arg *args,
{
case SEARCH_SUB:
break;
case SEARCH_OR:
break;
case SEARCH_SEQSET:
break;
default:
break;
}
if (min_seq1 == 0) {
} else {
}
}
}
struct mail_search_arg *args,
{
case SEARCH_SUB:
break;
case SEARCH_OR:
/* go through our children and use the widest seqset
range */
break;
case SEARCH_SEQSET:
break;
default:
break;
}
}
}
{
if (uid_lowwater == 0)
return;
}
const struct mail_index_header *hdr,
struct mail_search_arg *args,
{
return FALSE;
}
continue;
}
/* SEEN with 0 seen? */
return FALSE;
/* UNSEEN with all seen? */
return FALSE;
/* SEEN with all seen */
/* UNSEEN with lowwater limiting */
}
}
/* DELETED with 0 deleted? */
return FALSE;
if (hdr->deleted_messages_count ==
hdr->messages_count) {
/* UNDELETED with all deleted? */
return FALSE;
/* DELETED with all deleted */
/* DELETED with lowwater limiting */
}
}
}
}
struct mail_search_arg *args)
{
const struct mail_index_header *hdr;
if (hdr->messages_count == 0) {
/* no messages, don't check sequence ranges. although we could
give error message then for FETCH, we shouldn't do it for
UID FETCH. */
return;
}
}
/* no matches */
return;
}
/* UNSEEN and DELETED in root search level may limit the range */
/* no matches */
}
}
{
struct mail_thread_iterate_context *child_iter;
const struct mail_thread_child_node *node;
int ret = 0;
if (child_iter != NULL) {
ret = -1;
}
}
if (mail_thread_iterate_deinit(&iter) < 0)
ret = -1;
return ret;
}
struct mail_search_arg *arg)
{
const struct mail_thread_child_node *node;
int ret;
return -1;
/* failed earlier */
return -1;
}
if (array_count(search_uids) == 0) {
/* search found nothing - no threads can match */
return 0;
}
if (child_iter != NULL) {
&thread_uids) < 0)
ret = -1;
}
/* yes, we want this thread */
}
}
if (mail_thread_iterate_deinit(&iter) < 0)
ret = -1;
return ret;
}
struct mail_search_arg *arg)
{
int ret = 0;
case SEARCH_OR:
case SEARCH_SUB:
ret = -1;
break;
case SEARCH_INTHREAD:
ret = -1;
break;
default:
break;
}
}
return ret;
}
struct mail_search_context *
struct mail_search_args *args,
const enum mail_sort_type *sort_program)
{
struct index_transaction_context *t =
(struct index_transaction_context *)_t;
struct index_search_context *ctx;
sizeof(void *), 5);
if (args->have_inthreads) {
&ctx->thread_ctx) < 0)
}
/* Need to reset results for match_always cases */
}
void *context ATTR_UNUSED)
{
if (search_ctx != NULL) {
}
}
{
int ret;
}
return ret;
}
{
struct mail_search_arg *arg;
int ret;
/* next search only from cached arguments */
if (ret >= 0)
return ret > 0;
/* open the mail file and check the rest */
return FALSE;
return FALSE;
}
return TRUE;
}
struct index_search_context *ctx)
{
const struct mail_index_header *hdr;
float percentage;
/* set the search time in here, in case a plugin
already spent some time indexing the mailbox */
T_BEGIN {
const char *text;
"ETA %d:%02d", (int)percentage,
} T_END;
}
}
{
struct mail_search_arg *subarg;
case SEARCH_OR:
case SEARCH_SUB:
/* they're static only if all subargs are static */
if (!search_arg_is_static(subarg))
return FALSE;
}
return TRUE;
case SEARCH_SEQSET:
/* changes between syncs, but we can't really handle this
currently. seqsets should be converted to uidsets first. */
case SEARCH_FLAGS:
case SEARCH_KEYWORDS:
case SEARCH_MODSEQ:
case SEARCH_INTHREAD:
break;
case SEARCH_ALL:
case SEARCH_UIDSET:
case SEARCH_BEFORE:
case SEARCH_ON:
case SEARCH_SINCE:
case SEARCH_SENTBEFORE:
case SEARCH_SENTON:
case SEARCH_SENTSINCE:
case SEARCH_SMALLER:
case SEARCH_LARGER:
case SEARCH_HEADER:
case SEARCH_HEADER_ADDRESS:
case SEARCH_BODY:
case SEARCH_TEXT:
case SEARCH_BODY_FAST:
case SEARCH_TEXT_FAST:
case SEARCH_MAILBOX:
return TRUE;
}
return FALSE;
}
{
if (search_arg_is_static(arg))
}
}
{
return TRUE;
}
return FALSE;
}
{
unsigned int count = 0;
bool never;
int ret;
*tryagain_r = FALSE;
/* everything searched at this point already. just returning
matches from sort list */
return 0;
return 1;
}
else {
/* see if this message never matches */
if (!never &&
/* we already know that the static data
matches. mark it as such. */
}
}
if (never) {
ret = 0;
} else T_BEGIN {
if (ret == 0 &&
/* if there are saved search results remember
that this message never matches */
}
} T_END;
ret = -1;
if (ret != 0) {
break;
}
if (++count == SEARCH_NONBLOCK_COUNT) {
*tryagain_r = TRUE;
return 0;
}
}
if (ret < 0)
/* finished searching the messages. now sort them and start
returning the messages. */
}
return ret;
}
{
int ret;
/* first time */
} else {
}
ret = 0;
/* check if the sequence matches */
if (ret != 0) {
is done. mail_set_seq() can be a bit slow. */
if (!ctx->have_index_args)
break;
if (ret != 0)
break;
}
/* doesn't, try next one */
}
return ret == 0 ? 0 : 1;
}