# HG changeset patch # User Stephan Bosch # Date 1464134228 -7200 # Node ID e9a0ccfe9b67dae52295672c6d75aa541f24bc75 # Parent 16358228dd24096171255d85b1165baa541ff0b5 Partially implemented IMAP SEARCH=X-MIMEPART capability. This capability is currently Dovecot-specific. diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/Makefile.am --- a/src/lib-storage/Makefile.am Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/Makefile.am Wed May 25 01:57:08 2016 +0200 @@ -33,6 +33,9 @@ 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 @@ 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 \ diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/index/Makefile.am --- a/src/lib-storage/index/Makefile.am Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/index/Makefile.am Wed May 25 01:57:08 2016 +0200 @@ -23,6 +23,7 @@ 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 \ diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/index/imapc/imapc-search.c --- a/src/lib-storage/index/imapc/imapc-search.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/index/imapc/imapc-search.c Wed May 25 01:57:08 2016 +0200 @@ -135,6 +135,7 @@ case SEARCH_MAILBOX_GUID: case SEARCH_MAILBOX_GLOB: case SEARCH_REAL_UID: + case SEARCH_MIMEPART: /* not supported for now */ break; } diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/index/index-search-mime.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-search-mime.c Wed May 25 01:57:08 2016 +0200 @@ -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 : 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 : 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; +} + diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/index/index-search-private.h --- a/src/lib-storage/index/index-search-private.h Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/index/index-search-private.h Wed May 25 01:57:08 2016 +0200 @@ -5,6 +5,9 @@ #include +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 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 diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/index/index-search.c --- a/src/lib-storage/index/index-search.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/index/index-search.c Wed May 25 01:57:08 2016 +0200 @@ -1378,6 +1378,8 @@ 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; } @@ -1420,6 +1422,7 @@ case SEARCH_MAILBOX_GUID: case SEARCH_MAILBOX_GLOB: case SEARCH_REAL_UID: + case SEARCH_MIMEPART: return TRUE; } return FALSE; diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-args-cmdline.c --- a/src/lib-storage/mail-search-args-cmdline.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/mail-search-args-cmdline.c Wed May 25 01:57:08 2016 +0200 @@ -79,6 +79,7 @@ case SEARCH_GUID: case SEARCH_MAILBOX_GLOB: case SEARCH_REAL_UID: + case SEARCH_MIMEPART: break; } new_arg = *arg; diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-args-imap.c --- a/src/lib-storage/mail-search-args-imap.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/mail-search-args-imap.c Wed May 25 01:57:08 2016 +0200 @@ -10,6 +10,7 @@ #include "imap-util.h" #include "imap-quote.h" #include "mail-search.h" +#include "mail-search-mime.h" #include @@ -285,6 +286,12 @@ 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 -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-mime-build.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search-mime-build.c Wed May 25 01:57:08 2016 +0200 @@ -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 -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-mime-build.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search-mime-build.h Wed May 25 01:57:08 2016 +0200 @@ -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 -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-mime-register.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search-mime-register.c Wed May 25 01:57:08 2016 +0200 @@ -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(®->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; + + /* */ + 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; + + /* OR OR ... - 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; + + /* */ + 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 -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-mime-register.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search-mime-register.h Wed May 25 01:57:08 2016 +0200 @@ -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 -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-mime.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search-mime.c Wed May 25 01:57:08 2016 +0200 @@ -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(×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; +} + diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-mime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search-mime.h Wed May 25 01:57:08 2016 +0200 @@ -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 diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search-register-imap.c --- a/src/lib-storage/mail-search-register-imap.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/mail-search-register-imap.c Wed May 25 01:57:08 2016 +0200 @@ -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; @@ -451,6 +451,17 @@ } 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) { struct mail_search_arg *sarg; @@ -581,6 +592,9 @@ /* 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 }, diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search.c --- a/src/lib-storage/mail-search.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/mail-search.c Wed May 25 01:57:08 2016 +0200 @@ -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 @@ 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 @@ 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; diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-search.h --- a/src/lib-storage/mail-search.h Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/mail-search.h Wed May 25 01:57:08 2016 +0200 @@ -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 @@ 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 @@ 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 { diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/mail-storage.c --- a/src/lib-storage/mail-storage.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/mail-storage.c Wed May 25 01:57:08 2016 +0200 @@ -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 @@ 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(); diff -r 16358228dd24 -r e9a0ccfe9b67 src/lib-storage/test-mail-search-args-imap.c --- a/src/lib-storage/test-mail-search-args-imap.c Sat Jan 14 13:33:05 2017 +0100 +++ b/src/lib-storage/test-mail-search-args-imap.c Wed May 25 01:57:08 2016 +0200 @@ -61,7 +61,50 @@ "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 LANGUAGE en )", + "MIMEPART (ID 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 IN-REPLY-TO )", + "MIMEPART (MESSAGE-ID IN-REPLY-TO )" }, + { "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 PARENT ID )", + "MIMEPART (ID PARENT (ID ))" }, + { "MIMEPART ( ID CHILD ( DESCRIPTION frop ID friep ) )", + "MIMEPART (ID CHILD (DESCRIPTION frop ID friep))" }, + { "MIMEPART CHILD EXISTS MIMEPART PARENT EXISTS", NULL }, }; static struct mail_search_arg test_failures[] = {