]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Partially implemented IMAP SEARCH=X-MIMEPART capability.
authorStephan Bosch <stephan@dovecot.fi>
Tue, 24 May 2016 23:57:08 +0000 (01:57 +0200)
committerGitLab <gitlab@git.dovecot.net>
Mon, 20 Feb 2017 09:22:28 +0000 (11:22 +0200)
This capability is currently Dovecot-specific.

19 files changed:
src/lib-storage/Makefile.am
src/lib-storage/index/Makefile.am
src/lib-storage/index/imapc/imapc-search.c
src/lib-storage/index/index-search-mime.c [new file with mode: 0644]
src/lib-storage/index/index-search-private.h
src/lib-storage/index/index-search.c
src/lib-storage/mail-search-args-cmdline.c
src/lib-storage/mail-search-args-imap.c
src/lib-storage/mail-search-mime-build.c [new file with mode: 0644]
src/lib-storage/mail-search-mime-build.h [new file with mode: 0644]
src/lib-storage/mail-search-mime-register.c [new file with mode: 0644]
src/lib-storage/mail-search-mime-register.h [new file with mode: 0644]
src/lib-storage/mail-search-mime.c [new file with mode: 0644]
src/lib-storage/mail-search-mime.h [new file with mode: 0644]
src/lib-storage/mail-search-register-imap.c
src/lib-storage/mail-search.c
src/lib-storage/mail-search.h
src/lib-storage/mail-storage.c
src/lib-storage/test-mail-search-args-imap.c

index 68a07acf01c0e3ec33c476394b435e181915b53c..03702ed47bb823b5abda9c71a0ff71e9d1af0e04 100644 (file)
@@ -33,6 +33,9 @@ libstorage_la_SOURCES = \
        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 \
@@ -67,6 +70,9 @@ headers = \
        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 220ccd7958ab41166d0376a527dfebcf991563ed..397cc0179bdfa60ffe76a5501266b62809fbfa03 100644 (file)
@@ -23,6 +23,7 @@ libstorage_index_la_SOURCES = \
        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 \
index bcf1ea5fd84eacc9d1687ae8e4a06c7562f81989..eb21743de320cf6f252e98e3fccd7fafab10a67f 100644 (file)
@@ -135,6 +135,7 @@ imapc_build_search_query_arg(struct imapc_mailbox *mbox,
        case SEARCH_MAILBOX_GUID:
        case SEARCH_MAILBOX_GLOB:
        case SEARCH_REAL_UID:
+       case SEARCH_MIMEPART:
                /* not supported for now */
                break;
        }
diff --git a/src/lib-storage/index/index-search-mime.c b/src/lib-storage/index/index-search-mime.c
new file mode 100644 (file)
index 0000000..8f19142
--- /dev/null
@@ -0,0 +1,563 @@
+/* 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;
+}
+
index e08f382c7d10aafa4b897f1e224142bdf0002677..0cabbfd7ea51bb522931d22b3e84520d61b10b20 100644 (file)
@@ -5,6 +5,9 @@
 
 #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;
@@ -37,4 +40,7 @@ struct index_search_context {
 
 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
index c22d57fcf04d221dd5771f0b6e152159749a49d7..38dbba0fd94a25ce4b3645198fb1d39cc9dbf2f8 100644 (file)
@@ -1379,6 +1379,8 @@ static int search_match_once(struct index_search_context *ctx)
                                       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;
 }
 
@@ -1421,6 +1423,7 @@ static bool search_arg_is_static(struct mail_search_arg *arg)
        case SEARCH_MAILBOX_GUID:
        case SEARCH_MAILBOX_GLOB:
        case SEARCH_REAL_UID:
+       case SEARCH_MIMEPART:
                return TRUE;
        }
        return FALSE;
index 6274ef38bbe5e3f6ceca9df1b40018e72976ebdb..8554d84d77eab126decec1213535127fc4169e25 100644 (file)
@@ -79,6 +79,7 @@ mail_search_arg_to_cmdline(string_t *dest, const struct mail_search_arg *arg)
        case SEARCH_GUID:
        case SEARCH_MAILBOX_GLOB:
        case SEARCH_REAL_UID:
+       case SEARCH_MIMEPART:
                break;
        }
        new_arg = *arg;
index 90b67f65f2bd23c740f586139957c9a4a1e9e0fa..436d3b1cecfdc0d54ee9c40df6734284cd083af8 100644 (file)
@@ -10,6 +10,7 @@
 #include "imap-util.h"
 #include "imap-quote.h"
 #include "mail-search.h"
+#include "mail-search-mime.h"
 
 #include <time.h>
 
@@ -285,6 +286,12 @@ bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
                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;
 }
diff --git a/src/lib-storage/mail-search-mime-build.c b/src/lib-storage/mail-search-mime-build.c
new file mode 100644 (file)
index 0000000..cced670
--- /dev/null
@@ -0,0 +1,173 @@
+/* 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;
+}
diff --git a/src/lib-storage/mail-search-mime-build.h b/src/lib-storage/mail-search-mime-build.h
new file mode 100644 (file)
index 0000000..e690454
--- /dev/null
@@ -0,0 +1,44 @@
+#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
diff --git a/src/lib-storage/mail-search-mime-register.c b/src/lib-storage/mail-search-mime-register.c
new file mode 100644 (file)
index 0000000..6a3cc3d
--- /dev/null
@@ -0,0 +1,547 @@
+/* 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(&reg->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(&reg->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(&reg->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(&reg->args, mail_search_mime_register_arg_cmp);
+               reg->args_sorted = TRUE;
+       }
+
+       return array_get(&reg->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(&reg->args, mail_search_mime_register_arg_cmp);
+               reg->args_sorted = TRUE;
+       }
+
+       arg.key = key;
+       return array_bsearch(&reg->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));
+}
diff --git a/src/lib-storage/mail-search-mime-register.h b/src/lib-storage/mail-search-mime-register.h
new file mode 100644 (file)
index 0000000..70bd5fd
--- /dev/null
@@ -0,0 +1,30 @@
+#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
diff --git a/src/lib-storage/mail-search-mime.c b/src/lib-storage/mail-search-mime.c
new file mode 100644 (file)
index 0000000..411a48e
--- /dev/null
@@ -0,0 +1,612 @@
+/* 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(&timestamp);
+       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;
+}
+
diff --git a/src/lib-storage/mail-search-mime.h b/src/lib-storage/mail-search-mime.h
new file mode 100644 (file)
index 0000000..612659a
--- /dev/null
@@ -0,0 +1,146 @@
+#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
index 628c169d4e2cdbe1352d3cdb0e3410d3b7059d36..3f84348abfeec6e793ba5a4a6b8874be23898987 100644 (file)
@@ -11,7 +11,7 @@
 #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;
 
@@ -450,6 +450,17 @@ imap_search_fuzzy(struct mail_search_build_context *ctx)
        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)
 {
@@ -581,6 +592,9 @@ static const struct mail_search_register_arg imap_register_args[] = {
        /* 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 },
index 05a60a099dd899f68c1f58141c66134e86b01224..53a9ad927f0f4729077ca217e471861c760c146e 100644 (file)
@@ -8,6 +8,7 @@
 #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,
@@ -314,6 +315,10 @@ mail_search_arg_dup_one(pool_t pool, const struct mail_search_arg *arg)
                        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;
 }
@@ -652,6 +657,10 @@ bool mail_search_arg_one_equals(const struct mail_search_arg *arg1,
                        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;
index df874210a4c3678803b8f26c69eaf106b9d86f35..b98d6004daad9b67823e8f1b03c5344607bd8d0e 100644 (file)
@@ -5,6 +5,8 @@
 #include "mail-types.h"
 #include "mail-thread.h"
 
+struct mail_search_mime_part;
+
 enum mail_search_arg_type {
        SEARCH_OR,
        SEARCH_SUB,
@@ -43,7 +45,8 @@ enum mail_search_arg_type {
        SEARCH_MAILBOX,
        SEARCH_MAILBOX_GUID,
        SEARCH_MAILBOX_GLOB,
-       SEARCH_REAL_UID
+       SEARCH_REAL_UID,
+       SEARCH_MIMEPART
 };
 
 enum mail_search_date_type {
@@ -101,6 +104,7 @@ struct mail_search_arg {
                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 {
index ecbcf0847a70cb3433f1040b6a8c2b18435c2852..c6e7393579db5ae628bc19d64aceb6be97108593 100644 (file)
@@ -24,6 +24,7 @@
 #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"
@@ -67,6 +68,7 @@ void mail_storage_deinit(void)
                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();
index 1fda0941ab983755c8a539be25dc1588606275df..d38ff5531669533e14cc9d53e628bd9fb1c356d9 100644 (file)
@@ -61,7 +61,50 @@ static const struct {
          "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[] = {