/* 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(&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));
}