/* Copyright (c) 2016-2018 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)); }