/* Copyright (c) 2016-2018 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;
}
i_unreached();
}
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;
if (prefix[0] == '\0')
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, ' ');
}
if (prefix[0] == '\0')
str_append_c(dest, ')');
return TRUE;
}
static bool
mail_search_mime_arg_to_imap_date(string_t *dest,
const struct mail_search_mime_arg *arg)
{
time_t timestamp = arg->value.time;
const char *str;
struct tm *tm;
int tz_offset;
tm = localtime(&timestamp);
tz_offset = utc_offset(tm, timestamp);
timestamp -= tz_offset * 60;
if (!imap_to_date(timestamp, &str))
return FALSE;
str_printfa(dest, " \"%s\"", str);
return TRUE;
}
bool mail_search_mime_arg_to_imap(string_t *dest,
const struct mail_search_mime_arg *arg, const char **error_r)
{
if (arg->match_not)
str_append(dest, "NOT ");
switch (arg->type) {
case SEARCH_MIME_OR:
if (!mail_search_mime_subargs_to_imap
(dest, arg->value.subargs, "OR ", error_r))
return FALSE;
break;
case SEARCH_MIME_SUB:
if (!mail_search_mime_subargs_to_imap
(dest, arg->value.subargs, "", error_r))
return FALSE;
break;
case SEARCH_MIME_SIZE_EQUAL:
str_printfa(dest, "SIZE %"PRIuUOFF_T, arg->value.size);
break;
case SEARCH_MIME_SIZE_LARGER:
str_printfa(dest, "SIZE LARGER %"PRIuUOFF_T, arg->value.size);
break;
case SEARCH_MIME_SIZE_SMALLER:
str_printfa(dest, "SIZE SMALLER %"PRIuUOFF_T, 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 %"PRIdTIME_T, 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 %"PRIdTIME_T, 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 %"PRIdTIME_T, 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;
}