This capability is currently Dovecot-specific.
mail-search-args-imap.c \
mail-search-args-simplify.c \
mail-search-build.c \
+ mail-search-mime.c \
+ mail-search-mime-build.c \
+ mail-search-mime-register.c \
mail-search-parser.c \
mail-search-parser-imap.c \
mail-search-parser-cmdline.c \
mail-namespace.h \
mail-search.h \
mail-search-build.h \
+ mail-search-mime.h \
+ mail-search-mime-build.h \
+ mail-search-mime-register.h \
mail-search-register.h \
mail-thread.h \
mail-storage.h \
index-pop3-uidl.c \
index-rebuild.c \
index-search.c \
+ index-search-mime.c \
index-search-result.c \
index-sort.c \
index-sort-string.c \
case SEARCH_MAILBOX_GUID:
case SEARCH_MAILBOX_GLOB:
case SEARCH_REAL_UID:
+ case SEARCH_MIMEPART:
/* not supported for now */
break;
}
--- /dev/null
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "message-date.h"
+#include "message-address.h"
+#include "message-part-data.h"
+#include "imap-bodystructure.h"
+#include "mail-search.h"
+#include "mail-search-mime.h"
+#include "index-search-private.h"
+
+struct search_mimepart_stack {
+ unsigned int index;
+};
+
+struct search_mimepart_context {
+ pool_t pool;
+ struct index_search_context *index_ctx;
+
+ /* message parts parsed from BODYSTRUCTURE */
+ struct message_part *mime_parts, *mime_part;
+
+ unsigned int depth, index;
+ ARRAY(struct search_mimepart_stack) stack;
+};
+
+static void search_mime_arg(struct mail_search_mime_arg *arg,
+ struct search_mimepart_context *mpctx);
+
+static int seach_arg_mime_parent_match(struct search_mimepart_context *mpctx,
+ struct mail_search_mime_arg *args)
+{
+ struct message_part *part = mpctx->mime_part;
+ unsigned int prev_depth, prev_index;
+ struct search_mimepart_stack *level;
+ int ret;
+
+ if (args->value.subargs == NULL) {
+ /* PARENT EXISTS: matches if this part has a parent.
+ */
+ return (part->parent != NULL ? 1 : 0);
+ }
+
+ /* PARENT <mpart-key>: matches if this part's parent matches the
+ mpart-key (subargs).
+ */
+
+ prev_depth = mpctx->depth;
+ prev_index = mpctx->index;
+
+ level = array_idx_modifiable
+ (&mpctx->stack, mpctx->depth-1);
+
+ mpctx->mime_part = part->parent;
+ mail_search_mime_args_reset(args->value.subargs, TRUE);
+
+ mpctx->index = level->index;
+ mpctx->depth = mpctx->depth-1;
+ ret = mail_search_mime_args_foreach
+ (args->value.subargs, search_mime_arg, mpctx);
+
+ mpctx->mime_part = part;
+ mpctx->index = prev_index;
+ mpctx->depth = prev_depth;
+ return ret;
+}
+
+static int seach_arg_mime_child_match(struct search_mimepart_context *mpctx,
+ struct mail_search_mime_arg *args)
+{
+ struct message_part *part, *prev_part;
+ unsigned int prev_depth, prev_index, depth;
+ struct search_mimepart_stack *level;
+ int ret = 0;
+
+ part = mpctx->mime_part;
+ if (args->value.subargs == NULL) {
+ /* CHILD EXISTS: matches if this part has any children; i.e., it is
+ multipart.
+ */
+ return (part->children != NULL ? 1 : 0);
+ }
+
+ /* CHILD <mpart-key>: matches if this part has any child that mathes
+ the mpart-key (subargs).
+ */
+
+ prev_part = part;
+ prev_depth = mpctx->depth;
+ prev_index = mpctx->index;
+
+ depth = mpctx->depth;
+ T_BEGIN {
+ ARRAY(struct search_mimepart_stack) prev_stack;
+
+ /* preserve current stack for any nested CHILD PARENT nastyness */
+ t_array_init(&prev_stack, 16);
+ array_copy(&prev_stack.arr, 0, &mpctx->stack.arr, 0,
+ array_count(&mpctx->stack));
+
+ depth++;
+ if (depth < array_count(&mpctx->stack))
+ level = array_idx_modifiable(&mpctx->stack, depth);
+ else {
+ i_assert(depth == array_count(&mpctx->stack));
+ level = array_append_space(&mpctx->stack);
+ }
+ level->index = 1;
+
+ part = part->children;
+ while (part != NULL) {
+ mpctx->mime_part = part;
+ mail_search_mime_args_reset(args->value.subargs, TRUE);
+
+ mpctx->depth = depth - prev_depth;
+ mpctx->index = level->index;
+ if ((ret=mail_search_mime_args_foreach
+ (args->value.subargs, search_mime_arg, mpctx)) != 0)
+ break;
+ if (part->children != NULL) {
+ depth++;
+ if (depth < array_count(&mpctx->stack))
+ level = array_idx_modifiable(&mpctx->stack, depth);
+ else {
+ i_assert(depth == array_count(&mpctx->stack));
+ level = array_append_space(&mpctx->stack);
+ }
+ level->index = 1;
+ part = part->children;
+ } else {
+ while (part->next == NULL) {
+ if (part->parent == NULL || part->parent == prev_part)
+ break;
+ depth--;
+ level = array_idx_modifiable(&mpctx->stack, depth);
+ part = part->parent;
+ }
+ level->index++;
+ part = part->next;
+ }
+ }
+
+ array_clear(&mpctx->stack);
+ array_copy(&mpctx->stack.arr, 0, &prev_stack.arr, 0,
+ array_count(&prev_stack));
+ } T_END;
+
+ mpctx->mime_part = prev_part;
+ mpctx->index = prev_index;
+ mpctx->depth = prev_depth;
+ return ret;
+}
+
+static int
+seach_arg_mime_substring_match(
+ struct search_mimepart_context *mpctx ATTR_UNUSED,
+ const char *key, const char *value)
+{
+ if (value == NULL)
+ return 0;
+
+ /* FIXME: Normalization is required */
+ return (strstr(value, key) != NULL ? 1 : 0);
+}
+
+static int
+seach_arg_mime_envelope_time_match(
+ struct search_mimepart_context *mpctx ATTR_UNUSED,
+ enum mail_search_mime_arg_type type, time_t search_time,
+ const struct message_part_envelope *envelope)
+{
+ time_t sent_time;
+ int timezone_offset;
+
+ if (envelope == NULL)
+ return 0;
+
+ /* NOTE: RFC-3501 specifies that timezone is ignored
+ in searches. sent_time is returned as UTC, so change it. */
+ // FIXME: adjust comment
+ if (!message_date_parse((const unsigned char *)envelope->date,
+ strlen(envelope->date), &sent_time, &timezone_offset))
+ return 0;
+ sent_time += timezone_offset * 60;
+
+ switch (type) {
+ case SEARCH_MIME_SENTBEFORE:
+ return sent_time < search_time ? 1 : 0;
+ case SEARCH_MIME_SENTON:
+ return (sent_time >= search_time &&
+ sent_time < search_time + 3600*24) ? 1 : 0;
+ case SEARCH_MIME_SENTSINCE:
+ return sent_time >= search_time ? 1 : 0;
+ default:
+ i_unreached();
+ }
+}
+
+static int
+seach_arg_mime_envelope_address_match(
+ struct search_mimepart_context *mpctx ATTR_UNUSED,
+ enum mail_search_mime_arg_type type, const char *key,
+ const struct message_part_envelope *envelope)
+{
+ const struct message_address *addrs;
+ string_t *addrs_enc;
+
+ if (envelope == NULL)
+ return 0;
+
+ switch (type) {
+ case SEARCH_MIME_CC:
+ addrs = envelope->cc;
+ break;
+ case SEARCH_MIME_BCC:
+ addrs = envelope->bcc;
+ break;
+ case SEARCH_MIME_FROM:
+ addrs = envelope->from;
+ break;
+ case SEARCH_MIME_SENDER:
+ addrs = envelope->sender;
+ break;
+ case SEARCH_MIME_REPLY_TO:
+ addrs = envelope->reply_to;
+ break;
+ case SEARCH_MIME_TO:
+ addrs = envelope->to;
+ break;
+ default:
+ i_unreached();
+ }
+
+ /* FIXME: do we need to normalize anything? at least case insensitivity.
+ MIME header encoding will make this a bit difficult, so it should
+ probably be normalized directly in the struct message_address. */
+
+ addrs_enc = t_str_new(128);
+ message_address_write(addrs_enc, addrs);
+ return (strstr(str_c(addrs_enc), key) != NULL ? 1 : 0);
+}
+
+static int
+seach_arg_mime_filename_match(struct search_mimepart_context *mpctx,
+ enum mail_search_mime_arg_type type, const char *key)
+{
+ struct message_part *part = mpctx->mime_part;
+ const char *value;
+ size_t vlen, alen;
+
+ if (!message_part_data_get_filename(part, &value))
+ return 0;
+
+ /* FIXME: Normalization is probably required */
+
+ switch (type) {
+ case SEARCH_MIME_FILENAME_IS:
+ return (strcmp(value, key) == 0 ? 1 : 0);
+ case SEARCH_MIME_FILENAME_CONTAINS:
+ return (strstr(value, key) != NULL ? 1 : 0);
+ case SEARCH_MIME_FILENAME_BEGINS:
+ return (strncmp(value, key, strlen(key)) == 0 ? 1 : 0);
+ case SEARCH_MIME_FILENAME_ENDS:
+ vlen = strlen(value);
+ alen = strlen(key);
+ return (strncmp(value + (vlen - alen), key, alen) == 0 ? 1 : 0);
+ default:
+ break;
+ }
+ i_unreached();
+}
+
+static int
+seach_arg_mime_param_match(const struct message_part_param *params,
+ unsigned int params_count,
+ const char *name, const char *key)
+{
+ unsigned int i;
+
+ /* FIXME: Is normalization required? */
+
+ for (i = 0; i < params_count; i++) {
+ if (strcasecmp(params[i].name, name) == 0) {
+ if (key == NULL || *key == '\0')
+ return 1;
+ return (strstr(params[i].value, key) != NULL ? 1 : 0);
+ }
+ }
+ return 0;
+}
+
+static int
+seach_arg_mime_language_match(struct search_mimepart_context *mpctx,
+ const char *key)
+{
+ struct message_part_data *data = mpctx->mime_part->data;
+ const char *const *lang;
+
+ i_assert(data != NULL);
+
+ lang = data->content_language;
+ if (lang != NULL) {
+ while (*lang != NULL) {
+ /* FIXME: Should use RFC 4647 matching rules */
+ if (strcasecmp(*lang, key) == 0)
+ return 1;
+ lang++;
+ }
+ }
+ return 0;
+}
+
+/* Returns >0 = matched, 0 = not matched (unused), -1 = unknown */
+static int search_mime_arg_match(struct search_mimepart_context *mpctx,
+ struct mail_search_mime_arg *arg)
+{
+ struct message_part *part = mpctx->mime_part;
+ const struct message_part_data *data = part->data;
+
+ i_assert(data != NULL);
+
+ switch (arg->type) {
+ case SEARCH_MIME_OR:
+ case SEARCH_MIME_SUB:
+ i_unreached();
+
+ case SEARCH_MIME_SIZE_EQUAL:
+ return (part->body_size.virtual_size == arg->value.size ? 1 : 0);
+ case SEARCH_MIME_SIZE_LARGER:
+ return (part->body_size.virtual_size > arg->value.size ? 1 : 0);
+ case SEARCH_MIME_SIZE_SMALLER:
+ return (part->body_size.virtual_size < arg->value.size ? 1 : 0);
+
+ case SEARCH_MIME_DESCRIPTION:
+ return seach_arg_mime_substring_match(mpctx,
+ arg->value.str, data->content_description);
+ case SEARCH_MIME_DISPOSITION_TYPE:
+ return (data->content_disposition != NULL &&
+ strcasecmp(data->content_disposition,
+ arg->value.str) == 0 ? 1 : 0);
+ case SEARCH_MIME_DISPOSITION_PARAM:
+ return seach_arg_mime_param_match
+ (data->content_disposition_params,
+ data->content_disposition_params_count,
+ arg->field_name, arg->value.str);
+ case SEARCH_MIME_ENCODING:
+ return (data->content_transfer_encoding != NULL &&
+ strcasecmp(data->content_transfer_encoding,
+ arg->value.str) == 0 ? 1 : 0);
+ case SEARCH_MIME_ID:
+ return (data->content_id != NULL &&
+ strcasecmp(data->content_id,
+ arg->value.str) == 0 ? 1 : 0);
+ case SEARCH_MIME_LANGUAGE:
+ return seach_arg_mime_language_match(mpctx, arg->value.str);
+ case SEARCH_MIME_LOCATION:
+ return (data->content_location != NULL &&
+ strcasecmp(data->content_location,
+ arg->value.str) == 0 ? 1 : 0);
+ case SEARCH_MIME_MD5:
+ return (data->content_md5 != NULL &&
+ strcmp(data->content_md5,
+ arg->value.str) == 0 ? 1 : 0);
+
+ case SEARCH_MIME_TYPE:
+ return (data->content_type != NULL &&
+ strcasecmp(data->content_type,
+ arg->value.str) == 0 ? 1 : 0);
+ case SEARCH_MIME_SUBTYPE:
+ return (data->content_subtype != NULL &&
+ strcasecmp(data->content_subtype,
+ arg->value.str) == 0 ? 1 : 0);
+ case SEARCH_MIME_PARAM:
+ return seach_arg_mime_param_match
+ (data->content_type_params,
+ data->content_type_params_count,
+ arg->field_name, arg->value.str);
+
+ case SEARCH_MIME_SENTBEFORE:
+ case SEARCH_MIME_SENTON:
+ case SEARCH_MIME_SENTSINCE:
+ return seach_arg_mime_envelope_time_match
+ (mpctx, arg->type, arg->value.time, data->envelope);
+
+ case SEARCH_MIME_CC:
+ case SEARCH_MIME_BCC:
+ case SEARCH_MIME_FROM:
+ case SEARCH_MIME_REPLY_TO:
+ case SEARCH_MIME_SENDER:
+ case SEARCH_MIME_TO:
+ return seach_arg_mime_envelope_address_match
+ (mpctx, arg->type, arg->value.str, data->envelope);
+
+ case SEARCH_MIME_SUBJECT:
+ if (data->envelope == NULL)
+ return 0;
+ return seach_arg_mime_substring_match(mpctx,
+ arg->value.str, data->envelope->subject);
+ case SEARCH_MIME_IN_REPLY_TO:
+ if (data->envelope == NULL)
+ return 0;
+ return seach_arg_mime_substring_match(mpctx,
+ arg->value.str, data->envelope->in_reply_to);
+ case SEARCH_MIME_MESSAGE_ID:
+ if (data->envelope == NULL)
+ return 0;
+ return seach_arg_mime_substring_match(mpctx,
+ arg->value.str, data->envelope->message_id);
+
+ case SEARCH_MIME_DEPTH_EQUAL:
+ return (mpctx->depth == arg->value.number ? 1 : 0);
+ case SEARCH_MIME_DEPTH_MIN:
+ return (mpctx->depth >= arg->value.number ? 1 : 0);
+ case SEARCH_MIME_DEPTH_MAX:
+ return (mpctx->depth <= arg->value.number ? 1 : 0);
+ case SEARCH_MIME_INDEX:
+ return (mpctx->index == arg->value.number ? 1 : 0);
+
+ case SEARCH_MIME_PARENT:
+ return seach_arg_mime_parent_match(mpctx, arg);
+ case SEARCH_MIME_CHILD:
+ return seach_arg_mime_child_match(mpctx, arg);
+
+ case SEARCH_MIME_FILENAME_IS:
+ case SEARCH_MIME_FILENAME_CONTAINS:
+ case SEARCH_MIME_FILENAME_BEGINS:
+ case SEARCH_MIME_FILENAME_ENDS:
+ return seach_arg_mime_filename_match(mpctx,
+ arg->type, arg->value.str);
+
+ case SEARCH_MIME_HEADER:
+ case SEARCH_MIME_BODY:
+ case SEARCH_MIME_TEXT:
+ break;
+ }
+ return -1;
+}
+
+static void search_mime_arg(struct mail_search_mime_arg *arg,
+ struct search_mimepart_context *mpctx)
+{
+ switch (search_mime_arg_match(mpctx, arg)) {
+ case -1:
+ /* unknown */
+ break;
+ case 0:
+ ARG_SET_RESULT(arg, 0);
+ break;
+ default:
+ ARG_SET_RESULT(arg, 1);
+ break;
+ }
+}
+
+static int seach_arg_mime_parts_match(struct search_mimepart_context *mpctx,
+ struct mail_search_mime_arg *args,
+ struct message_part *parts)
+{
+ struct message_part *part;
+ struct search_mimepart_stack *level;
+ int ret;
+
+ level = array_append_space(&mpctx->stack);
+ level->index = 1;
+
+ part = parts;
+ while (part != NULL) {
+ mpctx->mime_part = part;
+ mail_search_mime_args_reset(args, TRUE);
+
+ mpctx->index = level->index;
+ mpctx->depth = array_count(&mpctx->stack)-1;
+
+ if ((ret=mail_search_mime_args_foreach
+ (args, search_mime_arg, mpctx)) != 0)
+ return ret;
+ if (part->children != NULL) {
+ level = array_append_space(&mpctx->stack);
+ level->index = 1;
+ part = part->children;
+ } else {
+ while (part->next == NULL) {
+ if (part->parent == NULL)
+ break;
+ array_delete(&mpctx->stack, array_count(&mpctx->stack)-1, 1);
+ level = array_idx_modifiable
+ (&mpctx->stack, array_count(&mpctx->stack)-1);
+ part = part->parent;
+ }
+ level->index++;
+ part = part->next;
+ }
+ }
+
+ return 0;
+}
+
+/* Returns >0 = matched, 0 = not matched, -1 = unknown */
+static int search_arg_match_mimepart(struct search_mimepart_context *mpctx,
+ struct mail_search_arg *arg)
+{
+ struct index_search_context *ctx = mpctx->index_ctx;
+ const char *bodystructure, *error;
+
+ if (arg->type != SEARCH_MIMEPART)
+ return -1;
+
+ if (mpctx->pool == NULL) {
+ mpctx->pool = pool_alloconly_create
+ (MEMPOOL_GROWING"search mime parts", 4096);
+ p_array_init(&mpctx->stack, mpctx->pool, 16);
+ }
+ if (mpctx->mime_parts == NULL) {
+ /* FIXME: could the mail object already have message_part tree with
+ data? */
+ if (mail_get_special(ctx->cur_mail,
+ MAIL_FETCH_IMAP_BODYSTRUCTURE, &bodystructure) < 0)
+ return -1;
+ if (imap_bodystructure_parse_full(bodystructure, mpctx->pool,
+ &mpctx->mime_parts, &error) < 0)
+ return -1;
+ }
+
+ /* FIXME: implement HEADER, BODY and TEXT (not from BODYSTRUCTURE)
+ Needs to support FTS */
+ return seach_arg_mime_parts_match
+ (mpctx, arg->value.mime_part->args, mpctx->mime_parts);
+}
+
+static void search_mimepart_arg(struct mail_search_arg *arg,
+ struct search_mimepart_context *mpctx)
+{
+ switch (search_arg_match_mimepart(mpctx, arg)) {
+ case -1:
+ /* unknown */
+ break;
+ case 0:
+ ARG_SET_RESULT(arg, 0);
+ break;
+ default:
+ ARG_SET_RESULT(arg, 1);
+ break;
+ }
+}
+
+int index_search_mime_arg_match(struct mail_search_arg *args,
+ struct index_search_context *ctx)
+{
+ struct search_mimepart_context mpctx;
+ int ret;
+
+ i_zero(&mpctx);
+ mpctx.index_ctx = ctx;
+
+ ret = mail_search_args_foreach(args,
+ search_mimepart_arg, &mpctx);
+
+ if (mpctx.pool != NULL)
+ pool_unref(&mpctx.pool);
+ return ret;
+}
+
#include <sys/time.h>
+struct mail_search_mime_part;
+struct imap_message_part;
+
struct index_search_context {
struct mail_search_context mail_ctx;
struct mail_index_view *view;
struct mail *index_search_get_mail(struct index_search_context *ctx);
+int index_search_mime_arg_match(struct mail_search_arg *args,
+ struct index_search_context *ctx);
+
#endif
search_cached_arg, ctx);
if (ret < 0)
ret = search_arg_match_text(ctx->mail_ctx.args->args, ctx);
+ if (ret < 0)
+ ret = index_search_mime_arg_match(ctx->mail_ctx.args->args, ctx);
return ret;
}
case SEARCH_MAILBOX_GUID:
case SEARCH_MAILBOX_GLOB:
case SEARCH_REAL_UID:
+ case SEARCH_MIMEPART:
return TRUE;
}
return FALSE;
case SEARCH_GUID:
case SEARCH_MAILBOX_GLOB:
case SEARCH_REAL_UID:
+ case SEARCH_MIMEPART:
break;
}
new_arg = *arg;
#include "imap-util.h"
#include "imap-quote.h"
#include "mail-search.h"
+#include "mail-search-mime.h"
#include <time.h>
str_append(dest, "X-REAL-UID ");
imap_write_seq_range(dest, &arg->value.seqset);
break;
+ case SEARCH_MIMEPART:
+ str_append(dest, "MIMEPART ");
+ if (!mail_search_mime_part_to_imap(dest,
+ arg->value.mime_part, error_r))
+ return FALSE;
+ break;
}
return TRUE;
}
--- /dev/null
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "charset-utf8.h"
+#include "mail-storage-private.h"
+#include "mail-search-parser.h"
+#include "mail-search-mime-register.h"
+#include "mail-search-mime-build.h"
+
+static int mail_search_mime_build_list(struct mail_search_mime_build_context *ctx,
+ struct mail_search_mime_arg **arg_r);
+
+struct mail_search_mime_arg *
+mail_search_mime_build_new(struct mail_search_mime_build_context *ctx,
+ enum mail_search_mime_arg_type type)
+{
+ struct mail_search_mime_arg *arg;
+
+ arg = p_new(ctx->ctx->pool, struct mail_search_mime_arg, 1);
+ arg->type = type;
+ return arg;
+}
+
+struct mail_search_mime_arg *
+mail_search_mime_build_str(struct mail_search_mime_build_context *ctx,
+ enum mail_search_mime_arg_type type)
+{
+ struct mail_search_mime_arg *sarg;
+ const char *value;
+
+ sarg = mail_search_mime_build_new(ctx, type);
+ if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
+ return NULL;
+ sarg->value.str = p_strdup(ctx->ctx->pool, value);
+ return sarg;
+}
+
+static int
+mail_search_mime_build_key_int(struct mail_search_mime_build_context *ctx,
+ struct mail_search_mime_arg *parent,
+ struct mail_search_mime_arg **arg_r)
+{
+ struct mail_search_mime_arg *sarg;
+ struct mail_search_mime_arg *old_parent = ctx->parent;
+ const char *key;
+ const struct mail_search_mime_register_arg *reg_arg;
+ int ret;
+
+ ctx->parent = parent;
+
+ if ((ret = mail_search_parse_key(ctx->ctx->parser, &key)) <= 0)
+ return ret;
+
+ if (strcmp(key, MAIL_SEARCH_PARSER_KEY_LIST) == 0) {
+ if (mail_search_mime_build_list(ctx, &sarg) < 0)
+ return -1;
+ if (sarg->value.subargs == NULL) {
+ ctx->ctx->_error = "No MIMEPART keys inside list";
+ return -1;
+ }
+
+ ctx->parent = old_parent;
+ *arg_r = sarg;
+ return 1;
+ }
+ key = t_str_ucase(key);
+
+ reg_arg = mail_search_mime_register_find(key);
+ if (reg_arg != NULL)
+ sarg = reg_arg->build(ctx);
+ else {
+ sarg = NULL;
+ ctx->ctx->_error = p_strconcat
+ (ctx->ctx->pool, "Unknown MIMEPART key ", key, NULL);
+ }
+
+ ctx->parent = old_parent;
+ *arg_r = sarg;
+ return sarg == NULL ? -1 : 1;
+}
+
+int mail_search_mime_build_key(struct mail_search_mime_build_context *ctx,
+ struct mail_search_mime_arg *parent,
+ struct mail_search_mime_arg **arg_r)
+{
+ int ret;
+
+ ret = mail_search_mime_build_key_int(ctx, parent, arg_r);
+ if (ret <= 0) {
+ if (ret == 0)
+ ctx->ctx->_error = "Missing MIMEPART key";
+ return -1;
+ }
+ return 0;
+}
+
+static int mail_search_mime_build_list(struct mail_search_mime_build_context *ctx,
+ struct mail_search_mime_arg **arg_r)
+{
+ struct mail_search_mime_arg *sarg, **subargs;
+ enum mail_search_mime_arg_type cur_type = SEARCH_MIME_SUB;
+ int ret;
+
+ sarg = p_new(ctx->ctx->pool, struct mail_search_mime_arg, 1);
+ sarg->type = cur_type;
+
+ subargs = &sarg->value.subargs;
+ while ((ret = mail_search_mime_build_key_int(ctx, sarg, subargs)) > 0) {
+ if (cur_type == sarg->type) {
+ /* expected type */
+ } else if (cur_type == SEARCH_MIME_SUB) {
+ /* type changed. everything in this list must now
+ belong to this type. */
+ cur_type = sarg->type;
+ } else {
+ ctx->ctx->_error =
+ "Use parenthesis when mixing ANDs and ORs";
+ return -1;
+ }
+ subargs = &(*subargs)->next;
+ sarg->type = SEARCH_MIME_SUB;
+ }
+ if (ret < 0)
+ return -1;
+ sarg->type = cur_type;
+ *arg_r = sarg;
+ return 0;
+}
+
+int mail_search_mime_build(struct mail_search_build_context *bctx,
+ struct mail_search_mime_part **mpart_r)
+{
+ struct mail_search_mime_build_context ctx;
+ struct mail_search_mime_part *mpart;
+ struct mail_search_mime_arg *root;
+ int ret;
+
+ *mpart_r = NULL;
+
+ i_zero(&ctx);
+ ctx.ctx = bctx;
+ ctx.mime_part = mpart =
+ p_new(bctx->pool, struct mail_search_mime_part, 1);
+
+ if ((ret=mail_search_mime_build_key(&ctx, NULL, &root)) < 0)
+ return ret;
+
+ if (root->type == SEARCH_MIME_SUB && !root->match_not) {
+ /* simple SUB root */
+ mpart->args = root->value.subargs;
+ } else {
+ mpart->args = root;
+ }
+
+ *mpart_r = mpart;
+ return 0;
+}
+
+struct mail_search_mime_arg *
+mail_search_mime_build_add(pool_t pool,
+ struct mail_search_mime_part *mpart,
+ enum mail_search_mime_arg_type type)
+{
+ struct mail_search_mime_arg *arg;
+
+ arg = p_new(pool, struct mail_search_mime_arg, 1);
+ arg->type = type;
+
+ arg->next = mpart->args;
+ mpart->args = arg;
+ return arg;
+}
--- /dev/null
+#ifndef MAIL_SEARCH_MIME_BUILD_H
+#define MAIL_SEARCH_MIME_BUILD_H
+
+#include "mail-search.h"
+#include "mail-search-build.h"
+#include "mail-search-register.h"
+#include "mail-search-mime.h"
+
+struct mailbox;
+
+struct mail_search_mime_build_context {
+ struct mail_search_build_context *ctx;
+ struct mail_search_mime_part *mime_part;
+
+ struct mail_search_mime_arg *parent;
+};
+
+/* Start building a new MIMPART search key. Use mail_search_mime_args_unref()
+ to free it. */
+struct mail_search_mime_part *mail_search_mime_build_init(void);
+
+/* Convert IMAP SEARCH command compatible parameters to
+ mail_search_mime_args. */
+int mail_search_mime_build(struct mail_search_build_context *bctx,
+ struct mail_search_mime_part **mpart_r);
+
+/* Add new search arg with given type. */
+struct mail_search_mime_arg *
+mail_search_mime_build_add(pool_t pool,
+ struct mail_search_mime_part *mpart,
+ enum mail_search_mime_arg_type type);
+
+struct mail_search_mime_arg *
+mail_search_mime_build_new(struct mail_search_mime_build_context *ctx,
+ enum mail_search_mime_arg_type type);
+struct mail_search_mime_arg *
+mail_search_mime_build_str(struct mail_search_mime_build_context *ctx,
+ enum mail_search_mime_arg_type type);
+/* Returns 0 if arg is returned, -1 if error. */
+int mail_search_mime_build_key(struct mail_search_mime_build_context *ctx,
+ struct mail_search_mime_arg *parent,
+ struct mail_search_mime_arg **arg_r);
+
+#endif
--- /dev/null
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "str.h"
+#include "imap-date.h"
+#include "imap-seqset.h"
+#include "imap-utf7.h"
+#include "imap-util.h"
+#include "mail-search-parser.h"
+#include "mail-search-mime-register.h"
+#include "mail-search-mime-build.h"
+#include "mail-search-mime.h"
+
+struct mail_search_mime_register {
+ ARRAY(struct mail_search_mime_register_arg) args;
+
+ bool args_sorted:1;
+};
+
+struct mail_search_mime_register *mail_search_mime_register = NULL;
+
+static void
+mail_search_register_add_default(void);
+
+/*
+ * Register
+ */
+
+static struct mail_search_mime_register *
+mail_search_mime_register_init(void)
+{
+ struct mail_search_mime_register *reg =
+ mail_search_mime_register;
+ if (reg == NULL) {
+ reg = i_new(struct mail_search_mime_register, 1);
+ i_array_init(®->args, 64);
+
+ mail_search_mime_register = reg;
+ mail_search_register_add_default();
+ }
+
+ return reg;
+}
+
+void mail_search_mime_register_deinit(void)
+{
+ struct mail_search_mime_register *reg =
+ mail_search_mime_register;
+
+ mail_search_mime_register = NULL;
+ if (reg == NULL)
+ return;
+
+ array_free(®->args);
+ i_free(reg);
+}
+
+void mail_search_mime_register_add(
+ const struct mail_search_mime_register_arg *arg,
+ unsigned int count)
+{
+ struct mail_search_mime_register *reg =
+ mail_search_mime_register_init();
+
+ array_append(®->args, arg, count);
+ reg->args_sorted = FALSE;
+}
+
+static int
+mail_search_mime_register_arg_cmp(
+ const struct mail_search_mime_register_arg *arg1,
+ const struct mail_search_mime_register_arg *arg2)
+{
+ return strcmp(arg1->key, arg2->key);
+}
+
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_get(unsigned int *count_r)
+{
+ struct mail_search_mime_register *reg =
+ mail_search_mime_register_init();
+
+ if (!reg->args_sorted) {
+ array_sort(®->args, mail_search_mime_register_arg_cmp);
+ reg->args_sorted = TRUE;
+ }
+
+ return array_get(®->args, count_r);
+}
+
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_find(const char *key)
+{
+ struct mail_search_mime_register_arg arg;
+ struct mail_search_mime_register *reg =
+ mail_search_mime_register_init();
+
+ if (!reg->args_sorted) {
+ array_sort(®->args, mail_search_mime_register_arg_cmp);
+ reg->args_sorted = TRUE;
+ }
+
+ arg.key = key;
+ return array_bsearch(®->args, &arg, mail_search_mime_register_arg_cmp);
+}
+
+/*
+ * Default MIMEPART args
+ */
+
+static struct mail_search_mime_arg *
+mail_search_mime_not(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg;
+
+ if (mail_search_mime_build_key(ctx, ctx->parent, &smarg) < 0)
+ return NULL;
+
+ smarg->match_not = !smarg->match_not;
+ return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_or(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg, **subargs;
+
+ /* <search-key1> <search-key2> */
+ smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_OR);
+
+ subargs = &smarg->value.subargs;
+ do {
+ if (mail_search_mime_build_key(ctx, smarg, subargs) < 0)
+ return NULL;
+ subargs = &(*subargs)->next;
+
+ /* <key> OR <key> OR ... <key> - put them all
+ under one SEARCH_MIME_OR list. */
+ } while (mail_search_parse_skip_next(ctx->ctx->parser, "OR"));
+
+ if (mail_search_mime_build_key(ctx, smarg, subargs) < 0)
+ return NULL;
+ return smarg;
+}
+
+#define CALLBACK_STR(_func, _type) \
+static struct mail_search_mime_arg *\
+mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
+{ \
+ return mail_search_mime_build_str(ctx, _type); \
+}
+
+static struct mail_search_mime_arg *
+arg_new_date(struct mail_search_mime_build_context *ctx,
+ enum mail_search_mime_arg_type type)
+{
+ struct mail_search_mime_arg *smarg;
+ const char *value;
+
+ smarg = mail_search_mime_build_new(ctx, type);
+ if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
+ return NULL;
+ if (!imap_parse_date(value, &smarg->value.time)) {
+ ctx->ctx->_error = "Invalid search date parameter";
+ return NULL;
+ }
+ return smarg;
+}
+
+#define CALLBACK_DATE(_func, _type) \
+static struct mail_search_mime_arg *\
+mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
+{ \
+ return arg_new_date(ctx, _type); \
+}
+CALLBACK_DATE(sentbefore, SEARCH_MIME_SENTBEFORE)
+CALLBACK_DATE(senton, SEARCH_MIME_SENTON)
+CALLBACK_DATE(sentsince, SEARCH_MIME_SENTSINCE)
+
+static struct mail_search_mime_arg *
+mail_search_mime_size(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg;
+ enum mail_search_mime_arg_type type;
+ const char *key, *value;
+ uoff_t size;
+
+ if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+ ctx->ctx->_error = "Invalid MIMEPART SIZE key type";
+ return NULL;
+ }
+
+ key = t_str_ucase(key);
+ if (strcmp(key, "LARGER") == 0)
+ type = SEARCH_MIME_SIZE_LARGER;
+ else if (strcmp(key, "SMALLER") == 0)
+ type = SEARCH_MIME_SIZE_SMALLER;
+ else {
+ type = SEARCH_MIME_SIZE_EQUAL;
+ value = key;
+ }
+
+ if (type != SEARCH_MIME_SIZE_EQUAL &&
+ mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART SIZE value";
+ return NULL;
+ }
+
+ if (str_to_uoff(value, &size) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART SIZE value";
+ return NULL;
+ }
+
+ smarg = mail_search_mime_build_new(ctx, type);
+ smarg->value.size = size;
+ return smarg;
+}
+
+CALLBACK_STR(description, SEARCH_MIME_DESCRIPTION)
+CALLBACK_STR(encoding, SEARCH_MIME_ENCODING)
+CALLBACK_STR(id, SEARCH_MIME_ID)
+CALLBACK_STR(language, SEARCH_MIME_LANGUAGE)
+CALLBACK_STR(location, SEARCH_MIME_LOCATION)
+CALLBACK_STR(md5, SEARCH_MIME_MD5)
+
+CALLBACK_STR(type, SEARCH_MIME_TYPE)
+CALLBACK_STR(subtype, SEARCH_MIME_SUBTYPE)
+
+CALLBACK_STR(bcc, SEARCH_MIME_BCC)
+CALLBACK_STR(cc, SEARCH_MIME_CC)
+CALLBACK_STR(from, SEARCH_MIME_FROM)
+CALLBACK_STR(in_reply_to, SEARCH_MIME_IN_REPLY_TO)
+CALLBACK_STR(message_id, SEARCH_MIME_MESSAGE_ID)
+CALLBACK_STR(reply_to, SEARCH_MIME_REPLY_TO)
+CALLBACK_STR(sender, SEARCH_MIME_SENDER)
+CALLBACK_STR(subject, SEARCH_MIME_SUBJECT)
+CALLBACK_STR(to, SEARCH_MIME_TO)
+
+static struct mail_search_mime_arg *
+arg_new_field(struct mail_search_mime_build_context *ctx,
+ enum mail_search_mime_arg_type type)
+{
+ struct mail_search_mime_arg *smarg;
+ const char *field_name, *value;
+
+ /* <field-name> <string> */
+ if (mail_search_parse_string(ctx->ctx->parser, &field_name) < 0)
+ return NULL;
+ if (mail_search_build_get_utf8(ctx->ctx, field_name, &field_name) < 0)
+ return NULL;
+ if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
+ return NULL;
+ if (mail_search_build_get_utf8(ctx->ctx, value, &value) < 0)
+ return NULL;
+
+ smarg = mail_search_mime_build_new(ctx, type);
+ smarg->field_name = str_ucase(p_strdup(ctx->ctx->pool, field_name));
+ smarg->value.str = value;
+
+ return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_param(struct mail_search_mime_build_context *ctx)
+{
+ return arg_new_field
+ (ctx, SEARCH_MIME_PARAM);
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_header(struct mail_search_mime_build_context *ctx)
+{
+ return arg_new_field
+ (ctx, SEARCH_MIME_HEADER);
+}
+
+static struct mail_search_mime_arg *
+arg_new_body(struct mail_search_mime_build_context *ctx,
+ enum mail_search_mime_arg_type type)
+{
+ struct mail_search_mime_arg *smarg;
+
+ smarg = mail_search_mime_build_str(ctx, type);
+ if (smarg == NULL)
+ return NULL;
+
+ if (mail_search_build_get_utf8(ctx->ctx, smarg->value.str,
+ &smarg->value.str) < 0)
+ return NULL;
+ return smarg;
+}
+
+#define CALLBACK_BODY(_func, _type) \
+static struct mail_search_mime_arg *\
+mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
+{ \
+ return arg_new_body(ctx, _type); \
+}
+CALLBACK_BODY(body, SEARCH_MIME_BODY)
+CALLBACK_BODY(text, SEARCH_MIME_TEXT)
+
+static struct mail_search_mime_arg *
+mail_search_mime_disposition(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg;
+ const char *key, *value;
+
+ if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+ ctx->ctx->_error = "Invalid MIMEPART DISPOSITION key type";
+ return NULL;
+ }
+
+ key = t_str_ucase(key);
+ if (strcmp(key, "TYPE") == 0) {
+ if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART DISPOSITION TYPE value";
+ return NULL;
+ }
+ smarg = mail_search_mime_build_new
+ (ctx, SEARCH_MIME_DISPOSITION_TYPE);
+ smarg->value.str = p_strdup(ctx->ctx->pool, value);
+ return smarg;
+ } else if (strcmp(key, "PARAM") == 0) {
+ return arg_new_field
+ (ctx, SEARCH_MIME_DISPOSITION_PARAM);
+ }
+
+ ctx->ctx->_error = "Invalid MIMEPART DISPOSITION key type";
+ return NULL;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_depth(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg;
+ enum mail_search_mime_arg_type type;
+ const char *key, *value;
+ unsigned int depth;
+
+ if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+ ctx->ctx->_error = "Invalid MIMEPART DEPTH key";
+ return NULL;
+ }
+
+ key = t_str_ucase(key);
+ if (strcmp(key, "MIN") == 0)
+ type = SEARCH_MIME_DEPTH_MIN;
+ else if (strcmp(key, "MAX") == 0)
+ type = SEARCH_MIME_DEPTH_MAX;
+ else {
+ type = SEARCH_MIME_DEPTH_EQUAL;
+ value = key;
+ }
+
+ if (type != SEARCH_MIME_DEPTH_EQUAL &&
+ mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART DEPTH value";
+ return NULL;
+ }
+
+ if (str_to_uint(value, &depth) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART DEPTH level";
+ return NULL;
+ }
+
+ smarg = mail_search_mime_build_new(ctx, type);
+ smarg->value.number = depth;
+ return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_index(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg;
+ const char *value;
+ unsigned int index;
+
+ if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART INDEX value";
+ return NULL;
+ }
+
+ if (str_to_uint(value, &index) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART INDEX number";
+ return NULL;
+ }
+
+ smarg = mail_search_mime_build_new
+ (ctx, SEARCH_MIME_INDEX);
+ smarg->value.number = index;
+ return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_filename(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg;
+ enum mail_search_mime_arg_type type;
+ const char *key, *value;
+
+ if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
+ ctx->ctx->_error = "Invalid MIMEPART FILENAME match type";
+ return NULL;
+ }
+
+ key = t_str_ucase(key);
+ if (strcmp(key, "IS") == 0)
+ type = SEARCH_MIME_FILENAME_IS;
+ else if (strcmp(key, "CONTAINS") == 0)
+ type = SEARCH_MIME_FILENAME_CONTAINS;
+ else if (strcmp(key, "BEGINS") == 0)
+ type = SEARCH_MIME_FILENAME_BEGINS;
+ else if (strcmp(key, "ENDS") == 0)
+ type = SEARCH_MIME_FILENAME_ENDS;
+ else {
+ ctx->ctx->_error = "Invalid MIMEPART FILENAME match type";
+ return NULL;
+ }
+
+ if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART FILENAME string value";
+ return NULL;
+ }
+
+ if (mail_search_build_get_utf8(ctx->ctx, value, &value) < 0) {
+ ctx->ctx->_error = "Invalid MIMEPART FILENAME stromg value";
+ return NULL;
+ }
+
+ smarg = mail_search_mime_build_new(ctx, type);
+ smarg->value.str = value;
+ return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_parent(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg, *subargs;
+
+ smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_PARENT);
+ if (mail_search_mime_build_key(ctx, smarg, &subargs) < 0)
+ return NULL;
+ if (subargs == smarg)
+ smarg->value.subargs = NULL;
+ else if (subargs->type == SEARCH_MIME_SUB)
+ smarg->value.subargs = subargs->value.subargs;
+ else
+ smarg->value.subargs = subargs;
+ return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_child(struct mail_search_mime_build_context *ctx)
+{
+ struct mail_search_mime_arg *smarg, *subargs;
+
+ smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_CHILD);
+ if (mail_search_mime_build_key(ctx, smarg, &subargs) < 0)
+ return NULL;
+ if (subargs == smarg)
+ smarg->value.subargs = NULL;
+ else if (subargs->type == SEARCH_MIME_SUB)
+ smarg->value.subargs = subargs->value.subargs;
+ else
+ smarg->value.subargs = subargs;
+ return smarg;
+}
+
+static struct mail_search_mime_arg *
+mail_search_mime_exists(struct mail_search_mime_build_context *ctx)
+{
+ if (ctx->parent == NULL ||
+ (ctx->parent->type != SEARCH_MIME_PARENT &&
+ ctx->parent->type != SEARCH_MIME_CHILD)) {
+ ctx->ctx->_error = "EXISTS key can only be used with PARENT or CHILD";
+ return NULL;
+ }
+ return ctx->parent;
+}
+
+static const struct mail_search_mime_register_arg
+mime_register_args[] = {
+ /* argument set operations */
+ { "NOT", mail_search_mime_not },
+ { "OR", mail_search_mime_or },
+
+ /* dates */
+ { "SENTBEFORE", mail_search_mime_sentbefore },
+ { "SENTON", mail_search_mime_senton },
+ { "SENTSINCE", mail_search_mime_sentsince },
+
+ /* size */
+ { "SIZE", mail_search_mime_size },
+
+ /* part properties */
+ { "DESCRIPTION", mail_search_mime_description },
+ { "DISPOSITION", mail_search_mime_disposition },
+ { "ENCODING", mail_search_mime_encoding },
+ { "ID", mail_search_mime_id },
+ { "LANGUAGE", mail_search_mime_language },
+ { "LOCATION", mail_search_mime_location },
+ { "MD5", mail_search_mime_md5 },
+
+ /* content-type */
+ { "TYPE", mail_search_mime_type },
+ { "SUBTYPE", mail_search_mime_subtype },
+ { "PARAM", mail_search_mime_param },
+
+ /* headers */
+ { "HEADER", mail_search_mime_header },
+
+ /* message */
+ { "BCC", mail_search_mime_bcc },
+ { "CC", mail_search_mime_cc },
+ { "FROM", mail_search_mime_from },
+ { "IN-REPLY-TO", mail_search_mime_in_reply_to },
+ { "MESSAGE-ID", mail_search_mime_message_id },
+ { "REPLY-TO", mail_search_mime_reply_to },
+ { "SENDER", mail_search_mime_sender },
+ { "SUBJECT", mail_search_mime_subject },
+ { "TO", mail_search_mime_to },
+
+ /* body */
+ { "BODY", mail_search_mime_body },
+ { "TEXT", mail_search_mime_text },
+
+ /* position */
+ { "DEPTH", mail_search_mime_depth },
+ { "INDEX", mail_search_mime_index },
+
+ /* relations */
+ { "PARENT", mail_search_mime_parent },
+ { "CHILD", mail_search_mime_child },
+ { "EXISTS", mail_search_mime_exists },
+
+ /* filename */
+ { "FILENAME", mail_search_mime_filename },
+};
+
+static void
+mail_search_register_add_default(void)
+{
+ mail_search_mime_register_add(mime_register_args,
+ N_ELEMENTS(mime_register_args));
+}
--- /dev/null
+#ifndef MAIL_SEARCH_MIME_REGISTER_H
+#define MAIL_SEARCH_MIME_REGISTER_H
+
+struct mail_search_mime_arg;
+struct mail_search_mime_build_context;
+
+struct mail_search_mime_register_arg {
+ const char *key;
+
+ /* returns parsed arg or NULL if error. error message is set to ctx->ctx. */
+ struct mail_search_mime_arg *
+ (*build)(struct mail_search_mime_build_context *ctx);
+};
+
+void mail_search_mime_register_deinit(void);
+
+void mail_search_mime_register_add(
+ const struct mail_search_mime_register_arg *arg,
+ unsigned int count);
+
+/* Return all registered args sorted. */
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_get(unsigned int *count_r);
+
+/* Find key's registered arg, or NULL if not found. */
+const struct mail_search_mime_register_arg *
+mail_search_mime_register_find(const char *key);
+
+
+#endif
--- /dev/null
+/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "utc-offset.h"
+#include "imap-date.h"
+#include "imap-util.h"
+#include "imap-quote.h"
+#include "mail-search.h"
+#include "mail-search-mime.h"
+
+/*
+ *
+ */
+
+static struct mail_search_mime_arg *
+mail_search_mime_arg_dup_one(pool_t pool,
+ const struct mail_search_mime_arg *arg)
+{
+ struct mail_search_mime_arg *new_arg;
+
+ new_arg = p_new(pool, struct mail_search_mime_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;
+
+ switch (arg->type) {
+ case SEARCH_MIME_OR:
+ case SEARCH_MIME_SUB:
+ new_arg->value.subargs =
+ mail_search_mime_arg_dup(pool, arg->value.subargs);
+ break;
+ case SEARCH_MIME_SIZE_EQUAL:
+ case SEARCH_MIME_SIZE_LARGER:
+ case SEARCH_MIME_SIZE_SMALLER:
+ new_arg->value.size = arg->value.size;
+ break;
+ case SEARCH_MIME_HEADER:
+ new_arg->field_name = p_strdup(pool, arg->field_name);
+ /* fall through */
+ case SEARCH_MIME_DESCRIPTION:
+ case SEARCH_MIME_DISPOSITION_TYPE:
+ case SEARCH_MIME_DISPOSITION_PARAM:
+ case SEARCH_MIME_ENCODING:
+ case SEARCH_MIME_ID:
+ case SEARCH_MIME_LANGUAGE:
+ case SEARCH_MIME_LOCATION:
+ case SEARCH_MIME_MD5:
+ case SEARCH_MIME_TYPE:
+ case SEARCH_MIME_SUBTYPE:
+ case SEARCH_MIME_PARAM:
+ case SEARCH_MIME_BODY:
+ case SEARCH_MIME_TEXT:
+ case SEARCH_MIME_CC:
+ case SEARCH_MIME_BCC:
+ case SEARCH_MIME_FROM:
+ case SEARCH_MIME_IN_REPLY_TO:
+ case SEARCH_MIME_MESSAGE_ID:
+ case SEARCH_MIME_REPLY_TO:
+ case SEARCH_MIME_SENDER:
+ case SEARCH_MIME_SUBJECT:
+ case SEARCH_MIME_TO:
+ case SEARCH_MIME_FILENAME_IS:
+ case SEARCH_MIME_FILENAME_CONTAINS:
+ case SEARCH_MIME_FILENAME_BEGINS:
+ case SEARCH_MIME_FILENAME_ENDS:
+ new_arg->value.str =
+ p_strdup(pool, arg->value.str);
+ break;
+ case SEARCH_MIME_SENTBEFORE:
+ case SEARCH_MIME_SENTON:
+ case SEARCH_MIME_SENTSINCE:
+ new_arg->value.time = arg->value.time;
+ break;
+ case SEARCH_MIME_PARENT:
+ case SEARCH_MIME_CHILD:
+ if (new_arg->value.subargs != NULL) {
+ new_arg->value.subargs =
+ mail_search_mime_arg_dup(pool, arg->value.subargs);
+ }
+ break;
+ case SEARCH_MIME_DEPTH_EQUAL:
+ case SEARCH_MIME_DEPTH_MIN:
+ case SEARCH_MIME_DEPTH_MAX:
+ case SEARCH_MIME_INDEX:
+ new_arg->value.number = arg->value.number;
+ break;
+ }
+ return new_arg;
+}
+
+struct mail_search_mime_arg *
+mail_search_mime_arg_dup(pool_t pool,
+ const struct mail_search_mime_arg *arg)
+{
+ struct mail_search_mime_arg *new_arg = NULL, **dest = &new_arg;
+
+ for (; arg != NULL; arg = arg->next) {
+ *dest = mail_search_mime_arg_dup_one(pool, arg);
+ dest = &(*dest)->next;
+ }
+ return new_arg;
+}
+
+struct mail_search_mime_part *
+mail_search_mime_part_dup(pool_t pool,
+ const struct mail_search_mime_part *mpart)
+{
+ struct mail_search_mime_part *new_mpart;
+
+ new_mpart = p_new(pool, struct mail_search_mime_part, 1);
+ new_mpart->simplified = mpart->simplified;
+ new_mpart->args = mail_search_mime_arg_dup(pool, mpart->args);
+ return new_mpart;
+}
+
+/*
+ *
+ */
+
+void mail_search_mime_args_reset(struct mail_search_mime_arg *args,
+ bool full_reset)
+{
+ while (args != NULL) {
+ if (args->type == SEARCH_MIME_OR || args->type == SEARCH_MIME_SUB)
+ mail_search_mime_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_mime_arg_foreach(struct mail_search_mime_arg *arg,
+ mail_search_mime_foreach_callback_t *callback,
+ void *context)
+{
+ struct mail_search_mime_arg *subarg;
+
+ if (arg->result != -1)
+ return;
+
+ if (arg->type == SEARCH_MIME_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_mime_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_MIME_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_mime_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_mime_args_foreach
+int mail_search_mime_args_foreach(struct mail_search_mime_arg *args,
+ mail_search_mime_foreach_callback_t *callback,
+ void *context)
+{
+ int result;
+
+ result = 1;
+ for (; args != NULL; args = args->next) {
+ search_mime_arg_foreach(args, callback, context);
+
+ if (args->result == 0) {
+ /* didn't match */
+ return 0;
+ }
+
+ if (args->result == -1)
+ result = -1;
+ }
+
+ return result;
+}
+
+/*
+ *
+ */
+
+bool mail_search_mime_arg_one_equals(const struct mail_search_mime_arg *arg1,
+ const struct mail_search_mime_arg *arg2)
+{
+ if (arg1->type != arg2->type ||
+ arg1->match_not != arg2->match_not)
+ return FALSE;
+
+ switch (arg1->type) {
+ case SEARCH_MIME_OR:
+ case SEARCH_MIME_SUB:
+ return mail_search_mime_arg_equals(arg1->value.subargs,
+ arg2->value.subargs);
+
+ case SEARCH_MIME_SIZE_EQUAL:
+ case SEARCH_MIME_SIZE_LARGER:
+ case SEARCH_MIME_SIZE_SMALLER:
+ return arg1->value.size == arg2->value.size;
+
+ case SEARCH_MIME_HEADER:
+ case SEARCH_MIME_DISPOSITION_PARAM:
+ case SEARCH_MIME_PARAM:
+ if (strcasecmp(arg1->field_name, arg2->field_name) != 0)
+ return FALSE;
+ /* fall through */
+ case SEARCH_MIME_DESCRIPTION:
+ case SEARCH_MIME_DISPOSITION_TYPE:
+ case SEARCH_MIME_ENCODING:
+ case SEARCH_MIME_ID:
+ case SEARCH_MIME_LANGUAGE:
+ case SEARCH_MIME_LOCATION:
+ case SEARCH_MIME_MD5:
+ case SEARCH_MIME_TYPE:
+ case SEARCH_MIME_SUBTYPE:
+ case SEARCH_MIME_BODY:
+ case SEARCH_MIME_TEXT:
+ case SEARCH_MIME_CC:
+ case SEARCH_MIME_BCC:
+ case SEARCH_MIME_FROM:
+ case SEARCH_MIME_IN_REPLY_TO:
+ case SEARCH_MIME_MESSAGE_ID:
+ case SEARCH_MIME_REPLY_TO:
+ case SEARCH_MIME_SENDER:
+ case SEARCH_MIME_SUBJECT:
+ case SEARCH_MIME_TO:
+ case SEARCH_MIME_FILENAME_IS:
+ case SEARCH_MIME_FILENAME_CONTAINS:
+ case SEARCH_MIME_FILENAME_BEGINS:
+ case SEARCH_MIME_FILENAME_ENDS:
+ /* don't bother doing case-insensitive comparison. we should support
+ full i18n case-insensitivity (or the active comparator
+ in future). */
+ return strcmp(arg1->value.str, arg2->value.str) == 0;
+
+ case SEARCH_MIME_SENTBEFORE:
+ case SEARCH_MIME_SENTON:
+ case SEARCH_MIME_SENTSINCE:
+ return arg1->value.time == arg2->value.time;
+
+ case SEARCH_MIME_PARENT:
+ case SEARCH_MIME_CHILD:
+ if (arg1->value.subargs == NULL)
+ return arg2->value.subargs == NULL;
+ if (arg2->value.subargs == NULL)
+ return FALSE;
+ return mail_search_mime_arg_equals(arg1->value.subargs,
+ arg2->value.subargs);
+
+ case SEARCH_MIME_DEPTH_EQUAL:
+ case SEARCH_MIME_DEPTH_MIN:
+ case SEARCH_MIME_DEPTH_MAX:
+ case SEARCH_MIME_INDEX:
+ return arg1->value.number == arg2->value.number;
+ break;
+ }
+ i_unreached();
+ return FALSE;
+}
+
+bool mail_search_mime_arg_equals(const struct mail_search_mime_arg *arg1,
+ const struct mail_search_mime_arg *arg2)
+{
+ while (arg1 != NULL && arg2 != NULL) {
+ if (!mail_search_mime_arg_one_equals(arg1, arg2))
+ return FALSE;
+ arg1 = arg1->next;
+ arg2 = arg2->next;
+ }
+ return arg1 == NULL && arg2 == NULL;
+}
+
+bool mail_search_mime_parts_equal(const struct mail_search_mime_part *mpart1,
+ const struct mail_search_mime_part *mpart2)
+{
+ i_assert(mpart1->simplified == mpart2->simplified);
+
+ return mail_search_mime_arg_equals(mpart1->args, mpart2->args);
+}
+
+/*
+ *
+ */
+
+void mail_search_mime_simplify(struct mail_search_mime_part *mpart)
+{
+ mpart->simplified = TRUE;
+
+ // FIXME: implement and use
+}
+
+/*
+ *
+ */
+
+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 struct mail_search_mime_arg *arg;
+
+ str_append_c(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))
+ return FALSE;
+ if (arg->next != NULL)
+ str_append_c(dest, ' ');
+ }
+ str_append_c(dest, ')');
+ return TRUE;
+}
+
+static bool
+mail_search_mime_arg_to_imap_date(string_t *dest,
+ const struct mail_search_mime_arg *arg)
+{
+ time_t timestamp = arg->value.time;
+ const char *str;
+ struct tm *tm;
+ int tz_offset;
+
+ tm = localtime(×tamp);
+ tz_offset = utc_offset(tm, timestamp);
+ timestamp -= tz_offset * 60;
+
+ if (!imap_to_date(timestamp, &str))
+ return FALSE;
+ str_printfa(dest, " \"%s\"", str);
+ return TRUE;
+}
+
+bool mail_search_mime_arg_to_imap(string_t *dest,
+ const struct mail_search_mime_arg *arg, const char **error_r)
+{
+ 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))
+ return FALSE;
+ break;
+ case SEARCH_MIME_SUB:
+ if (!mail_search_mime_subargs_to_imap
+ (dest, arg->value.subargs, "", error_r))
+ return FALSE;
+ break;
+ case SEARCH_MIME_SIZE_EQUAL:
+ str_printfa(dest, "SIZE %llu",
+ (unsigned long long)arg->value.size);
+ break;
+ case SEARCH_MIME_SIZE_LARGER:
+ str_printfa(dest, "SIZE LARGER %llu",
+ (unsigned long long)arg->value.size);
+ break;
+ case SEARCH_MIME_SIZE_SMALLER:
+ str_printfa(dest, "SIZE SMALLER %llu",
+ (unsigned long long)arg->value.size);
+ break;
+ case SEARCH_MIME_DESCRIPTION:
+ str_append(dest, "DESCRIPTION ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_DISPOSITION_TYPE:
+ str_append(dest, "DISPOSITION TYPE ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_DISPOSITION_PARAM:
+ str_append(dest, "DISPOSITION PARAM ");
+ imap_append_astring(dest, arg->field_name);
+ str_append_c(dest, ' ');
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_ENCODING:
+ str_append(dest, "ENCODING ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_ID:
+ str_append(dest, "ID ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_LANGUAGE:
+ str_append(dest, "LANGUAGE ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_LOCATION:
+ str_append(dest, "LOCATION ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_MD5:
+ str_append(dest, "MD5 ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_TYPE:
+ str_append(dest, "TYPE ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_SUBTYPE:
+ str_append(dest, "SUBTYPE ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_PARAM:
+ str_append(dest, "PARAM ");
+ imap_append_astring(dest, arg->field_name);
+ str_append_c(dest, ' ');
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_HEADER:
+ str_append(dest, "HEADER ");
+ imap_append_astring(dest, arg->field_name);
+ str_append_c(dest, ' ');
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_BODY:
+ str_append(dest, "BODY ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_TEXT:
+ str_append(dest, "TEXT ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_CC:
+ str_append(dest, "CC ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_BCC:
+ str_append(dest, "BCC ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_FROM:
+ str_append(dest, "FROM ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_IN_REPLY_TO:
+ str_append(dest, "IN-REPLY-TO ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_MESSAGE_ID:
+ str_append(dest, "MESSAGE-ID ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_REPLY_TO:
+ str_append(dest, "REPLY-TO ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_SENDER:
+ str_append(dest, "SENDER ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_SENTBEFORE:
+ str_append(dest, "SENTBEFORE");
+ if (!mail_search_mime_arg_to_imap_date(dest, arg)) {
+ *error_r = t_strdup_printf(
+ "SENTBEFORE can't be written as IMAP MIMEPART key "
+ "for timestamp %ld", (long)arg->value.time);
+ return FALSE;
+ }
+ break;
+ case SEARCH_MIME_SENTON:
+ str_append(dest, "SENTON");
+ if (!mail_search_mime_arg_to_imap_date(dest, arg)) {
+ *error_r = t_strdup_printf(
+ "SENTON can't be written as IMAP MIMEPART key "
+ "for timestamp %ld", (long)arg->value.time);
+ return FALSE;
+ }
+ break;
+ case SEARCH_MIME_SENTSINCE:
+ str_append(dest, "SENTSINCE");
+ if (!mail_search_mime_arg_to_imap_date(dest, arg)) {
+ *error_r = t_strdup_printf(
+ "SENTSINCE can't be written as IMAP MIMEPART key "
+ "for timestamp %ld", (long)arg->value.time);
+ return FALSE;
+ }
+ break;
+ case SEARCH_MIME_SUBJECT:
+ str_append(dest, "SUBJECT ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_TO:
+ str_append(dest, "TO ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_DEPTH_EQUAL:
+ str_printfa(dest, "DEPTH %u", arg->value.number);
+ break;
+ case SEARCH_MIME_DEPTH_MIN:
+ str_printfa(dest, "DEPTH MIN %u", arg->value.number);
+ break;
+ case SEARCH_MIME_DEPTH_MAX:
+ str_printfa(dest, "DEPTH MAX %u", arg->value.number);
+ break;
+ case SEARCH_MIME_INDEX:
+ str_printfa(dest, "INDEX %u", arg->value.number);
+ break;
+ case SEARCH_MIME_PARENT:
+ str_append(dest, "PARENT ");
+ if (arg->value.subargs == NULL)
+ str_append(dest, "EXISTS");
+ else if (!mail_search_mime_subargs_to_imap
+ (dest, arg->value.subargs, "", error_r))
+ return FALSE;
+ break;
+ case SEARCH_MIME_CHILD:
+ str_append(dest, "CHILD ");
+ if (arg->value.subargs == NULL)
+ str_append(dest, "EXISTS");
+ else if (!mail_search_mime_subargs_to_imap
+ (dest, arg->value.subargs, "", error_r))
+ return FALSE;
+ break;
+ case SEARCH_MIME_FILENAME_IS:
+ str_append(dest, "FILENAME IS ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_FILENAME_CONTAINS:
+ str_append(dest, "FILENAME CONTAINS ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_FILENAME_BEGINS:
+ str_append(dest, "FILENAME BEGINS ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ case SEARCH_MIME_FILENAME_ENDS:
+ str_append(dest, "FILENAME ENDS ");
+ imap_append_astring(dest, arg->value.str);
+ break;
+ }
+ return TRUE;
+}
+
+bool mail_search_mime_part_to_imap(string_t *dest,
+ const struct mail_search_mime_part *mpart, 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))
+ 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))
+ return FALSE;
+ if (arg->next != NULL)
+ str_append_c(dest, ' ');
+ }
+ str_append_c(dest, ')');
+ }
+ return TRUE;
+}
+
--- /dev/null
+#ifndef MAIL_SEARCH_MIMEPART_H
+#define MAIL_SEARCH_MIMEPART_H
+
+enum mail_search_mime_arg_type {
+ SEARCH_MIME_OR,
+ SEARCH_MIME_SUB,
+
+ /* sizes */
+ SEARCH_MIME_SIZE_EQUAL,
+ SEARCH_MIME_SIZE_LARGER,
+ SEARCH_MIME_SIZE_SMALLER,
+
+ /* part properties */
+ SEARCH_MIME_DESCRIPTION,
+ SEARCH_MIME_DISPOSITION_TYPE,
+ SEARCH_MIME_DISPOSITION_PARAM,
+ SEARCH_MIME_ENCODING,
+ SEARCH_MIME_ID,
+ SEARCH_MIME_LANGUAGE,
+ SEARCH_MIME_LOCATION,
+ SEARCH_MIME_MD5,
+
+ /* content-type */
+ SEARCH_MIME_TYPE,
+ SEARCH_MIME_SUBTYPE,
+ SEARCH_MIME_PARAM,
+
+ /* headers */
+ SEARCH_MIME_HEADER,
+
+ /* body */
+ SEARCH_MIME_BODY,
+ SEARCH_MIME_TEXT,
+
+ /* message */
+ SEARCH_MIME_CC,
+ SEARCH_MIME_BCC,
+ SEARCH_MIME_FROM,
+ SEARCH_MIME_IN_REPLY_TO,
+ SEARCH_MIME_MESSAGE_ID,
+ SEARCH_MIME_REPLY_TO,
+ SEARCH_MIME_SENDER,
+ SEARCH_MIME_SENTBEFORE,
+ SEARCH_MIME_SENTON, /* time must point to beginning of the day */
+ SEARCH_MIME_SENTSINCE,
+ SEARCH_MIME_SUBJECT,
+ SEARCH_MIME_TO,
+
+ /* relations */
+ SEARCH_MIME_PARENT,
+ SEARCH_MIME_CHILD,
+
+ /* position */
+ SEARCH_MIME_DEPTH_EQUAL,
+ SEARCH_MIME_DEPTH_MIN,
+ SEARCH_MIME_DEPTH_MAX,
+ SEARCH_MIME_INDEX,
+
+ /* filename */
+ SEARCH_MIME_FILENAME_IS,
+ SEARCH_MIME_FILENAME_CONTAINS,
+ SEARCH_MIME_FILENAME_BEGINS,
+ SEARCH_MIME_FILENAME_ENDS
+};
+
+struct mail_search_mime_arg {
+ /* NOTE: when adding new fields, make sure mail_search_mime_arg_dup_one()
+ and mail_search_mime_arg_one_equals() are updated. */
+ struct mail_search_mime_arg *next;
+
+ enum mail_search_mime_arg_type type;
+ union {
+ struct mail_search_mime_arg *subargs;
+ const char *str;
+ time_t time;
+ uoff_t size;
+ unsigned int number;
+ } value;
+
+ void *context;
+ const char *field_name; /* for SEARCH_HEADER* */
+ bool match_not:1; /* result = !result */
+ bool match_always:1; /* result = 1 always */
+ bool nonmatch_always:1; /* result = 0 always */
+
+ int result; /* -1 = unknown, 0 = unmatched, 1 = matched */
+};
+
+struct mail_search_mime_part {
+ struct mail_search_mime_arg *args;
+
+ bool simplified:1;
+};
+
+typedef void
+mail_search_mime_foreach_callback_t(struct mail_search_mime_arg *arg,
+ void *context);
+
+/* Returns TRUE if the two mimepart search keys are fully compatible. */
+bool mail_search_mime_parts_equal(const struct mail_search_mime_part *mpart1,
+ const struct mail_search_mime_part *mpart2);
+/* Same as mail_search_mime_part_equal(), but for individual
+ mail_search_mime_arg structs. All the siblings of arg1 and arg2 are
+ also compared. */
+bool mail_search_mime_arg_equals(const struct mail_search_mime_arg *arg1,
+ const struct mail_search_mime_arg *arg2);
+/* Same as mail_search_mime_arg_equals(), but don't compare siblings. */
+bool mail_search_mime_arg_one_equals(const struct mail_search_mime_arg *arg1,
+ const struct mail_search_mime_arg *arg2);
+
+struct mail_search_mime_part *
+mail_search_mime_part_dup(pool_t pool,
+ const struct mail_search_mime_part *mpart);
+struct mail_search_mime_arg *
+mail_search_mime_arg_dup(pool_t pool,
+ const struct mail_search_mime_arg *arg);
+
+/* Reset the results in search arguments. match_always is reset only if
+ full_reset is TRUE. */
+void mail_search_mime_args_reset(struct mail_search_mime_arg *args,
+ bool full_reset);
+
+/* goes through arguments in list that don't have a result yet.
+ Returns 1 = search matched, 0 = search unmatched, -1 = don't know yet */
+int mail_search_mime_args_foreach(struct mail_search_mime_arg *args,
+ mail_search_mime_foreach_callback_t *callback,
+ void *context) ATTR_NULL(3);
+#define mail_search_mime_args_foreach(args, callback, context) \
+ mail_search_mime_args_foreach(args + \
+ CALLBACK_TYPECHECK(callback, void (*)( \
+ struct mail_search_mime_arg *, typeof(context))), \
+ (mail_search_mime_foreach_callback_t *)callback, context)
+
+/* Simplify/optimize search arguments. Afterwards all OR/SUB args are
+ guaranteed to have match_not=FALSE. */
+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);
+/* 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);
+
+#endif
#include "mail-search-register.h"
#include "mail-search-parser.h"
#include "mail-search-build.h"
-
+#include "mail-search-mime-build.h"
struct mail_search_register *mail_search_register_imap;
return sarg;
}
+static struct mail_search_arg *
+imap_search_mimepart(struct mail_search_build_context *ctx)
+{
+ struct mail_search_arg *sarg;
+
+ sarg = mail_search_build_new(ctx, SEARCH_MIMEPART);
+ if (mail_search_mime_build(ctx, &sarg->value.mime_part) < 0)
+ return NULL;
+ return sarg;
+}
+
static struct mail_search_arg *
imap_search_inthread(struct mail_search_build_context *ctx)
{
/* FUZZY extension: */
{ "FUZZY", imap_search_fuzzy },
+ /* SEARCH=MIMEPART extension: */
+ { "MIMEPART", imap_search_mimepart },
+
/* Other Dovecot extensions: */
{ "INTHREAD", imap_search_inthread },
{ "X-GUID", imap_search_x_guid },
#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,
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;
}
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();
return FALSE;
#include "mail-types.h"
#include "mail-thread.h"
+struct mail_search_mime_part;
+
enum mail_search_arg_type {
SEARCH_OR,
SEARCH_SUB,
SEARCH_MAILBOX,
SEARCH_MAILBOX_GUID,
SEARCH_MAILBOX_GLOB,
- SEARCH_REAL_UID
+ SEARCH_REAL_UID,
+ SEARCH_MIMEPART
};
enum mail_search_date_type {
enum mail_thread_type thread_type;
struct mail_search_modseq *modseq;
struct mail_search_result *search_result;
+ struct mail_search_mime_part *mime_part;
} value;
/* set by mail_search_args_init(): */
struct {
#include "mail-namespace.h"
#include "mail-search.h"
#include "mail-search-register.h"
+#include "mail-search-mime-register.h"
#include "mailbox-search-result-private.h"
#include "mailbox-guid-cache.h"
#include "mail-cache.h"
mail_search_register_deinit(&mail_search_register_human);
if (mail_search_register_imap != NULL)
mail_search_register_deinit(&mail_search_register_imap);
+ mail_search_mime_register_deinit();
if (array_is_created(&mail_storage_classes))
array_free(&mail_storage_classes);
mail_storage_hooks_deinit();
"INTHREAD REFS (((OR TEXT foo OR KEYWORD bar (SEEN))))" },
{ "X-GUID foo", NULL },
{ "X-MAILBOX foo", NULL },
- { "X-REAL-UID 1,5:6,10:15", NULL }
+ { "X-REAL-UID 1,5:6,10:15", NULL },
+ /* SEARCH=X-MIMEPART */
+ { "MIMEPART CHILD EXISTS", NULL },
+ { "MIMEPART ( CHILD EXISTS )",
+ "MIMEPART CHILD EXISTS" },
+ { "MIMEPART ( CHILD EXISTS HEADER Comment Hopla )",
+ "MIMEPART (CHILD EXISTS HEADER COMMENT Hopla)" },
+ { "MIMEPART ( DESCRIPTION Frop ENCODING base64 )",
+ "MIMEPART (DESCRIPTION Frop ENCODING base64)" },
+ { "MIMEPART ( DISPOSITION TYPE attachment "
+ "DISPOSITION PARAM FILENAME frop.txt )",
+ "MIMEPART (DISPOSITION TYPE attachment "
+ "DISPOSITION PARAM FILENAME frop.txt)" },
+ { "MIMEPART ( ID <frop.example.com> LANGUAGE en )",
+ "MIMEPART (ID <frop.example.com> LANGUAGE en)" },
+ { "MIMEPART ( LOCATION http://www.dovecot.org )",
+ "MIMEPART LOCATION http://www.dovecot.org" },
+ { "MIMEPART NOT MD5 373def35afde6378efd6172dfeadfd", NULL },
+ { "MIMEPART OR PARAM charset utf-8 TYPE text",
+ "MIMEPART (OR PARAM CHARSET utf-8 TYPE text)" },
+ { "MIMEPART ( OR SIZE LARGER 25 SIZE SMALLER 1023 )",
+ "MIMEPART (OR SIZE LARGER 25 SIZE SMALLER 1023)" },
+ { "MIMEPART ( TYPE video SUBTYPE mpeg )",
+ "MIMEPART (TYPE video SUBTYPE mpeg)" },
+ { "( OR MIMEPART ( DEPTH 2 INDEX 1 ) MIMEPART ( DEPTH MAX 4 INDEX 3 ) )",
+ "((OR MIMEPART (DEPTH 2 INDEX 1) MIMEPART (DEPTH MAX 4 INDEX 3)))" },
+ { "MIMEPART FILENAME IS frop.txt", NULL },
+ { "MIMEPART FILENAME BEGINS frop", NULL },
+ { "MIMEPART FILENAME ENDS .txt", NULL },
+ { "MIMEPART FILENAME CONTAINS frop", NULL },
+ { "MIMEPART BODY frop MIMEPART TEXT frop", NULL },
+ { "MIMEPART ( CC appie BCC theo FROM leo REPLY-TO henk SENDER arie )",
+ "MIMEPART (CC appie BCC theo FROM leo REPLY-TO henk SENDER arie)" },
+ { "MIMEPART ( MESSAGE-ID <frop4222> IN-REPLY-TO <frop421> )",
+ "MIMEPART (MESSAGE-ID <frop4222> IN-REPLY-TO <frop421>)" },
+ { "MIMEPART ( SUBJECT Frop TO henkie SENTON 20-Feb-2017 )",
+ "MIMEPART (SUBJECT Frop TO henkie SENTON \"20-Feb-2017\")" },
+ { "MIMEPART ( OR SENTBEFORE 20-May-2015 SENTSINCE 20-Feb-2017 )",
+ "MIMEPART (OR SENTBEFORE \"20-May-2015\" SENTSINCE \"20-Feb-2017\")" },
+ { "MIMEPART ( ID <frop> PARENT ID <friep> )",
+ "MIMEPART (ID <frop> PARENT (ID <friep>))" },
+ { "MIMEPART ( ID <frop> CHILD ( DESCRIPTION frop ID friep ) )",
+ "MIMEPART (ID <frop> CHILD (DESCRIPTION frop ID friep))" },
+ { "MIMEPART CHILD EXISTS MIMEPART PARENT EXISTS", NULL },
};
static struct mail_search_arg test_failures[] = {