struct mail_search_context *search_ctx;
struct mailbox_transaction_context *t;
struct mail *mail;
- const char *messageset, *item;
- bool silent, failed;
+ struct imap_store_context ctx;
+ ARRAY_TYPE(seq_range) modified_set = ARRAY_INIT;
+ enum mailbox_transaction_flags flags = 0;
+ enum imap_sync_flags imap_sync_flags = 0;
- const char *tagged_reply;
++ const char *reply, *tagged_reply;
+ string_t *str;
+ int ret;
if (!client_read_args(cmd, 0, 0, &args))
return FALSE;
client_send_command_error(cmd, "Invalid arguments.");
return TRUE;
}
-
- if (!get_modify_type(cmd, item, &modify_type, &silent))
- return TRUE;
-
- if (args[2].type == IMAP_ARG_LIST) {
- if (!client_parse_mail_flags(cmd,
- IMAP_ARG_LIST_ARGS(&args[2]),
- &flags, &keywords_list))
- return TRUE;
- } else {
- if (!client_parse_mail_flags(cmd, args+2,
- &flags, &keywords_list))
- return TRUE;
- }
-
- box = client->mailbox;
- search_arg = imap_search_get_arg(cmd, messageset, cmd->uid);
- if (search_arg == NULL)
+ ret = imap_search_get_seqset(cmd, IMAP_ARG_STR_NONULL(args),
+ cmd->uid, &search_args);
+ if (ret <= 0)
+ return ret < 0;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.cmd = cmd;
+ if (!store_parse_args(&ctx, ++args))
return TRUE;
- if (mailbox_is_readonly(box)) {
++ if (mailbox_is_readonly(client->mailbox)) {
++ if (ctx.max_modseq < (uint64_t)-1)
++ reply = "NO CONDSTORE failed: Mailbox is read-only.";
++ else
++ reply = "OK Store ignored with read-only mailbox.";
+ return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+ (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
- 0, "OK Store ignored with read-only mailbox.");
++ 0, reply);
+ }
+
- t = mailbox_transaction_begin(box, !silent ? 0 :
- MAILBOX_TRANSACTION_FLAG_HIDE);
- if (keywords_list == NULL && modify_type != MODIFY_REPLACE)
- keywords = NULL;
- else if (mailbox_keywords_create(box, keywords_list, &keywords) < 0) {
- /* invalid keywords */
- mailbox_transaction_rollback(&t);
- client_send_storage_error(cmd, mailbox_get_storage(box));
- return TRUE;
- }
- search_ctx = mailbox_search_init(t, NULL, search_arg, NULL);
+ if (ctx.silent)
+ flags |= MAILBOX_TRANSACTION_FLAG_HIDE;
+ if (ctx.max_modseq < (uint64_t)-1)
+ flags |= MAILBOX_TRANSACTION_FLAG_REFRESH;
++
+ t = mailbox_transaction_begin(client->mailbox, flags);
+ search_ctx = mailbox_search_init(t, search_args, NULL);
+ mail_search_args_unref(&search_args);
+ /* FIXME: UNCHANGEDSINCE should be atomic, but this requires support
+ from mail-storage API. So for now we fake it. */
mail = mail_alloc(t, MAIL_FETCH_FLAGS, NULL);
while (mailbox_search_next(search_ctx, mail) > 0) {
- if (modify_type == MODIFY_REPLACE || flags != 0)
- mail_update_flags(mail, modify_type, flags);
- if (modify_type == MODIFY_REPLACE || keywords != NULL)
- mail_update_keywords(mail, modify_type, keywords);
+ if (ctx.max_modseq < (uint64_t)-1) {
+ if (mail_get_modseq(mail) > ctx.max_modseq) {
+ seq_range_array_add(&modified_set, 64,
+ cmd->uid ? mail->uid : mail->seq);
+ continue;
+ }
+ }
+ if (ctx.modify_type == MODIFY_REPLACE || ctx.flags != 0)
+ mail_update_flags(mail, ctx.modify_type, ctx.flags);
+ if (ctx.modify_type == MODIFY_REPLACE || ctx.keywords != NULL) {
+ mail_update_keywords(mail, ctx.modify_type,
+ ctx.keywords);
+ }
}
mail_free(&mail);
const char *error;
};
-static int
-imap_uidset_parse(pool_t pool, struct mailbox *box, const char *uidset,
- struct mail_search_seqset **seqset_r, const char **error_r)
+static bool search_args_have_searchres(struct mail_search_arg *sargs)
{
- struct mail_search_seqset *seqset, **p;
- bool last;
-
- *seqset_r = imap_messageset_parse(pool, uidset);
- if (*seqset_r == NULL) {
- *error_r = "Invalid UID messageset";
- return -1;
- }
-
- p = seqset_r;
- for (seqset = *seqset_r; seqset != NULL; seqset = seqset->next) {
- if (seqset->seq1 == (uint32_t)-1) {
- /* last message, stays same */
- continue;
+ for (; sargs != NULL; sargs = sargs->next) {
+ switch (sargs->type) {
+ case SEARCH_UIDSET:
+ if (strcmp(sargs->value.str, "$") == 0)
+ return TRUE;
+ break;
+ case SEARCH_SUB:
+ case SEARCH_OR:
+ if (search_args_have_searchres(sargs->value.subargs))
+ return TRUE;
+ break;
+ default:
+ break;
}
-
- last = seqset->seq2 == (uint32_t)-1;
- mailbox_get_uids(box, seqset->seq1, seqset->seq2,
- &seqset->seq1, &seqset->seq2);
- if (seqset->seq1 == 0 && last) {
- /* we need special case for too_high_uid:* case */
- seqset->seq1 = seqset->seq2 = (uint32_t)-1;
- }
-
- if (seqset->seq1 != 0)
- p = &seqset->next;
- else
- *p = seqset->next;
}
-
- *error_r = NULL;
- return 0;
+ return FALSE;
}
-static struct mail_search_arg *
-search_arg_new(pool_t pool, enum mail_search_arg_type type)
+int imap_search_args_build(struct client_command_context *cmd,
+ const struct imap_arg *args, const char *charset,
+ struct mail_search_args **search_args_r)
{
- struct mail_search_arg *arg;
-
- arg = p_new(pool, struct mail_search_arg, 1);
- arg->type = type;
-
- return arg;
-}
+ struct mail_search_args *sargs;
+ const char *error;
-static bool
-arg_get_next(struct search_build_data *data, const struct imap_arg **args,
- const char **value_r)
-{
- if ((*args)->type == IMAP_ARG_EOL) {
- data->error = "Missing parameter for argument";
- return FALSE;
- }
- if ((*args)->type != IMAP_ARG_ATOM &&
- (*args)->type != IMAP_ARG_STRING) {
- data->error = "Invalid parameter for argument";
- return FALSE;
++ if (args->type == IMAP_ARG_EOL) {
++ client_send_command_error(cmd, "Missing search parameters");
++ return -1;
+ }
+
- *value_r = IMAP_ARG_STR(*args);
- *args += 1;
- return TRUE;
-}
-
-#define ARG_NEW_SINGLE(type) \
- arg_new_single(data, next_sarg, type)
-static bool
-arg_new_single(struct search_build_data *data,
- struct mail_search_arg **next_sarg,
- enum mail_search_arg_type type)
-{
- *next_sarg = search_arg_new(data->pool, type);
- return TRUE;
-}
-
-#define ARG_NEW_STR(type) \
- arg_new_str(data, args, next_sarg, type)
-static bool
-arg_new_str(struct search_build_data *data,
- const struct imap_arg **args, struct mail_search_arg **next_sarg,
- enum mail_search_arg_type type)
-{
- struct mail_search_arg *sarg;
- const char *value;
-
- *next_sarg = sarg = search_arg_new(data->pool, type);
- if (!arg_get_next(data, args, &value))
- return FALSE;
- sarg->value.str = p_strdup(data->pool, value);
- return TRUE;
-}
-
-#define ARG_NEW_FLAGS(flags) \
- arg_new_flags(data, next_sarg, flags)
-static bool
-arg_new_flags(struct search_build_data *data,
- struct mail_search_arg **next_sarg, enum mail_flags flags)
-{
- struct mail_search_arg *sarg;
-
- *next_sarg = sarg = search_arg_new(data->pool, SEARCH_FLAGS);
- sarg->value.flags = flags;
- return TRUE;
-}
-
-static bool
-arg_new_keyword(struct search_build_data *data,
- const struct imap_arg **args,
- struct mail_search_arg **next_sarg)
-{
- struct mail_search_arg *sarg;
- const char *value, *keywords[2];
- struct mail_storage *storage;
- enum mail_error error;
-
- *next_sarg = sarg = search_arg_new(data->pool, SEARCH_KEYWORDS);
- if (!arg_get_next(data, args, &value))
- return FALSE;
-
- keywords[0] = value;
- keywords[1] = NULL;
-
- if (mailbox_keywords_create(data->box, keywords,
- &sarg->value.keywords) < 0) {
- storage = mailbox_get_storage(data->box);
- data->error = mail_storage_get_last_error(storage, &error);
- return FALSE;
+ if (mail_search_build_from_imap_args(args, charset,
+ &sargs, &error) < 0) {
+ client_send_command_error(cmd, error);
+ return -1;
}
- return TRUE;
-}
-
-#define ARG_NEW_SIZE(type) \
- arg_new_size(data, args, next_sarg, type)
-static bool
-arg_new_size(struct search_build_data *data,
- const struct imap_arg **args, struct mail_search_arg **next_sarg,
- enum mail_search_arg_type type)
-{
- struct mail_search_arg *sarg;
- const char *value;
- char *p;
-
- *next_sarg = sarg = search_arg_new(data->pool, type);
- if (!arg_get_next(data, args, &value))
- return FALSE;
- sarg->value.size = strtoull(value, &p, 10);
- if (*p != '\0') {
- data->error = "Invalid search size parameter";
- return FALSE;
+ if (search_args_have_searchres(sargs->args)) {
+ if (client_handle_search_save_ambiguity(cmd))
+ return 0;
}
- return TRUE;
-}
-
-#define ARG_NEW_DATE(type) \
- arg_new_date(data, args, next_sarg, type)
-static bool
-arg_new_date(struct search_build_data *data,
- const struct imap_arg **args, struct mail_search_arg **next_sarg,
- enum mail_search_arg_type type)
-{
- struct mail_search_arg *sarg;
- const char *value;
- *next_sarg = sarg = search_arg_new(data->pool, type);
- if (!arg_get_next(data, args, &value))
- return FALSE;
- if (!imap_parse_date(value, &sarg->value.time)) {
- data->error = "Invalid search date parameter";
- return FALSE;
- }
- return TRUE;
+ mail_search_args_init(sargs, cmd->client->mailbox, TRUE,
+ &cmd->client->search_saved_uidset);
+ *search_args_r = sargs;
+ return 1;
}
-#define ARG_NEW_HEADER(type, hdr_name) \
- arg_new_header(data, args, next_sarg, type, hdr_name)
static bool
-arg_new_header(struct search_build_data *data,
- const struct imap_arg **args, struct mail_search_arg **next_sarg,
- enum mail_search_arg_type type, const char *hdr_name)
+msgset_is_valid(ARRAY_TYPE(seq_range) *seqset, uint32_t messages_count)
{
- struct mail_search_arg *sarg;
- const char *value;
+ const struct seq_range *range;
+ unsigned int count;
- *next_sarg = sarg = search_arg_new(data->pool, type);
- if (!arg_get_next(data, args, &value))
- return FALSE;
-
- sarg->hdr_field_name = p_strdup(data->pool, hdr_name);
- sarg->value.str = p_strdup(data->pool, value);
- return TRUE;
-}
-
-static bool search_arg_build(struct search_build_data *data,
- const struct imap_arg **args,
- struct mail_search_arg **next_sarg)
-{
- struct mail_search_seqset *seqset;
- struct mail_search_arg **subargs;
- const struct imap_arg *arg;
- const char *str;
-
- if ((*args)->type == IMAP_ARG_EOL) {
- data->error = "Missing argument";
- return FALSE;
- }
-
- arg = *args;
-
- if (arg->type == IMAP_ARG_NIL) {
- /* NIL not allowed */
- data->error = "NIL not allowed";
- return FALSE;
- }
-
- if (arg->type == IMAP_ARG_LIST) {
- const struct imap_arg *listargs = IMAP_ARG_LIST_ARGS(arg);
-
- if (listargs->type == IMAP_ARG_EOL) {
- data->error = "Empty list not allowed";
- return FALSE;
- }
-
- *next_sarg = search_arg_new(data->pool, SEARCH_SUB);
- subargs = &(*next_sarg)->value.subargs;
- while (listargs->type != IMAP_ARG_EOL) {
- if (!search_arg_build(data, &listargs, subargs))
- return FALSE;
- subargs = &(*subargs)->next;
- }
-
- *args += 1;
- return TRUE;
- }
-
- i_assert(arg->type == IMAP_ARG_ATOM ||
- arg->type == IMAP_ARG_STRING);
-
- /* string argument - get the name and jump to next */
- str = IMAP_ARG_STR(arg);
- *args += 1;
- str = t_str_ucase(str);
-
- switch (*str) {
- case 'A':
- if (strcmp(str, "ANSWERED") == 0)
- return ARG_NEW_FLAGS(MAIL_ANSWERED);
- else if (strcmp(str, "ALL") == 0)
- return ARG_NEW_SINGLE(SEARCH_ALL);
- break;
- case 'B':
- if (strcmp(str, "BODY") == 0) {
- /* <string> */
- if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
- *IMAP_ARG_STR(*args) == '\0') {
- *args += 1;
- return ARG_NEW_SINGLE(SEARCH_ALL);
- }
- return ARG_NEW_STR(SEARCH_BODY);
- } else if (strcmp(str, "BEFORE") == 0) {
- /* <date> */
- return ARG_NEW_DATE(SEARCH_BEFORE);
- } else if (strcmp(str, "BCC") == 0) {
- /* <string> */
- return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
- }
- break;
- case 'C':
- if (strcmp(str, "CC") == 0) {
- /* <string> */
- return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
- }
- break;
- case 'D':
- if (strcmp(str, "DELETED") == 0)
- return ARG_NEW_FLAGS(MAIL_DELETED);
- else if (strcmp(str, "DRAFT") == 0)
- return ARG_NEW_FLAGS(MAIL_DRAFT);
- break;
- case 'F':
- if (strcmp(str, "FLAGGED") == 0)
- return ARG_NEW_FLAGS(MAIL_FLAGGED);
- else if (strcmp(str, "FROM") == 0) {
- /* <string> */
- return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
- }
- break;
- case 'H':
- if (strcmp(str, "HEADER") == 0) {
- /* <field-name> <string> */
- const char *key;
-
- if ((*args)->type == IMAP_ARG_EOL) {
- data->error = "Missing parameter for HEADER";
- return FALSE;
- }
- if ((*args)->type != IMAP_ARG_ATOM &&
- (*args)->type != IMAP_ARG_STRING) {
- data->error = "Invalid parameter for HEADER";
- return FALSE;
- }
-
- key = t_str_ucase(IMAP_ARG_STR(*args));
- *args += 1;
- return ARG_NEW_HEADER(SEARCH_HEADER, key);
- }
- break;
- case 'K':
- if (strcmp(str, "KEYWORD") == 0) {
- /* <flag> */
- return arg_new_keyword(data, args, next_sarg);
- }
- break;
- case 'L':
- if (strcmp(str, "LARGER") == 0) {
- /* <n> */
- return ARG_NEW_SIZE(SEARCH_LARGER);
- }
- break;
- case 'N':
- if (strcmp(str, "NOT") == 0) {
- if (!search_arg_build(data, args, next_sarg))
- return FALSE;
- (*next_sarg)->not = !(*next_sarg)->not;
- return TRUE;
- } else if (strcmp(str, "NEW") == 0) {
- /* NEW == (RECENT UNSEEN) */
- *next_sarg = search_arg_new(data->pool, SEARCH_SUB);
-
- subargs = &(*next_sarg)->value.subargs;
- *subargs = search_arg_new(data->pool, SEARCH_FLAGS);
- (*subargs)->value.flags = MAIL_RECENT;
- (*subargs)->next = search_arg_new(data->pool,
- SEARCH_FLAGS);
- (*subargs)->next->value.flags = MAIL_SEEN;
- (*subargs)->next->not = TRUE;
- return TRUE;
- }
- break;
- case 'O':
- if (strcmp(str, "OR") == 0) {
- /* <search-key1> <search-key2> */
- *next_sarg = search_arg_new(data->pool, SEARCH_OR);
-
- subargs = &(*next_sarg)->value.subargs;
- for (;;) {
- if (!search_arg_build(data, args, subargs))
- return FALSE;
-
- subargs = &(*subargs)->next;
-
- /* <key> OR <key> OR ... <key> - put them all
- under one SEARCH_OR list. */
- if ((*args)->type == IMAP_ARG_EOL)
- break;
-
- if ((*args)->type != IMAP_ARG_ATOM ||
- strcasecmp(IMAP_ARG_STR_NONULL(*args),
- "OR") != 0)
- break;
-
- *args += 1;
- }
-
- if (!search_arg_build(data, args, subargs))
- return FALSE;
- return TRUE;
- } if (strcmp(str, "ON") == 0) {
- /* <date> */
- return ARG_NEW_DATE(SEARCH_ON);
- } if (strcmp(str, "OLD") == 0) {
- /* OLD == NOT RECENT */
- if (!ARG_NEW_FLAGS(MAIL_RECENT))
- return FALSE;
-
- (*next_sarg)->not = TRUE;
- return TRUE;
- }
- break;
- case 'R':
- if (strcmp(str, "RECENT") == 0)
- return ARG_NEW_FLAGS(MAIL_RECENT);
- break;
- case 'S':
- if (strcmp(str, "SEEN") == 0)
- return ARG_NEW_FLAGS(MAIL_SEEN);
- else if (strcmp(str, "SUBJECT") == 0) {
- /* <string> */
- return ARG_NEW_HEADER(SEARCH_HEADER_COMPRESS_LWSP, str);
- } else if (strcmp(str, "SENTBEFORE") == 0) {
- /* <date> */
- return ARG_NEW_DATE(SEARCH_SENTBEFORE);
- } else if (strcmp(str, "SENTON") == 0) {
- /* <date> */
- return ARG_NEW_DATE(SEARCH_SENTON);
- } else if (strcmp(str, "SENTSINCE") == 0) {
- /* <date> */
- return ARG_NEW_DATE(SEARCH_SENTSINCE);
- } else if (strcmp(str, "SINCE") == 0) {
- /* <date> */
- return ARG_NEW_DATE(SEARCH_SINCE);
- } else if (strcmp(str, "SMALLER") == 0) {
- /* <n> */
- return ARG_NEW_SIZE(SEARCH_SMALLER);
- }
- break;
- case 'T':
- if (strcmp(str, "TEXT") == 0) {
- /* <string> */
- if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
- *IMAP_ARG_STR(*args) == '\0') {
- *args += 1;
- return ARG_NEW_SINGLE(SEARCH_ALL);
- }
- return ARG_NEW_STR(SEARCH_TEXT);
- } else if (strcmp(str, "TO") == 0) {
- /* <string> */
- return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
- }
- break;
- case 'U':
- if (strcmp(str, "UID") == 0) {
- /* <message set> */
- if (!ARG_NEW_STR(SEARCH_SEQSET))
- return FALSE;
-
- return imap_uidset_parse(data->pool, data->box,
- (*next_sarg)->value.str,
- &(*next_sarg)->value.seqset,
- &data->error) == 0;
- } else if (strcmp(str, "UNANSWERED") == 0) {
- if (!ARG_NEW_FLAGS(MAIL_ANSWERED))
- return FALSE;
- (*next_sarg)->not = TRUE;
- return TRUE;
- } else if (strcmp(str, "UNDELETED") == 0) {
- if (!ARG_NEW_FLAGS(MAIL_DELETED))
- return FALSE;
- (*next_sarg)->not = TRUE;
- return TRUE;
- } else if (strcmp(str, "UNDRAFT") == 0) {
- if (!ARG_NEW_FLAGS(MAIL_DRAFT))
- return FALSE;
- (*next_sarg)->not = TRUE;
- return TRUE;
- } else if (strcmp(str, "UNFLAGGED") == 0) {
- if (!ARG_NEW_FLAGS(MAIL_FLAGGED))
- return FALSE;
- (*next_sarg)->not = TRUE;
- return TRUE;
- } else if (strcmp(str, "UNKEYWORD") == 0) {
- /* <flag> */
- if (!arg_new_keyword(data, args, next_sarg))
- return FALSE;
- (*next_sarg)->not = TRUE;
- return TRUE;
- } else if (strcmp(str, "UNSEEN") == 0) {
- if (!ARG_NEW_FLAGS(MAIL_SEEN))
- return FALSE;
- (*next_sarg)->not = TRUE;
- return TRUE;
- }
- break;
- case 'X':
- if (strcmp(str, "X-BODY-FAST") == 0) {
- /* <string> */
- if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
- *IMAP_ARG_STR(*args) == '\0') {
- *args += 1;
- return ARG_NEW_SINGLE(SEARCH_ALL);
- }
- return ARG_NEW_STR(SEARCH_BODY_FAST);
- } else if (strcmp(str, "X-TEXT-FAST") == 0) {
- /* <string> */
- if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
- *IMAP_ARG_STR(*args) == '\0') {
- *args += 1;
- return ARG_NEW_SINGLE(SEARCH_ALL);
- }
- return ARG_NEW_STR(SEARCH_TEXT_FAST);
- }
- break;
- default:
- if (*str == '*' || (*str >= '0' && *str <= '9')) {
- /* <message-set> */
- seqset = imap_messageset_parse(data->pool, str);
- if (seqset == NULL) {
- data->error = "Invalid messageset";
- return FALSE;
- }
-
- if (!ARG_NEW_SINGLE(SEARCH_SEQSET))
- return FALSE;
-
- (*next_sarg)->value.seqset = seqset;
- return TRUE;
- }
- break;
- }
-
- data->error = t_strconcat("Unknown argument ", str, NULL);
- return FALSE;
-}
-
-struct mail_search_arg *
-imap_search_args_build(pool_t pool, struct mailbox *box,
- const struct imap_arg *args, const char **error_r)
-{
- struct search_build_data data;
- struct mail_search_arg *first_sarg, **sargs;
-
- *error_r = NULL;
-
- data.box = box;
- data.pool = pool;
- data.error = NULL;
-
- /* get the first arg */
- first_sarg = NULL; sargs = &first_sarg;
- while (args->type != IMAP_ARG_EOL) {
- if (!search_arg_build(&data, &args, sargs)) {
- imap_search_args_free(box, first_sarg);
- *error_r = data.error;
- return NULL;
- }
- sargs = &(*sargs)->next;
- }
-
- if (first_sarg == NULL)
- *error_r = "Missing search parameters";
- return first_sarg;
-}
-
-static bool
-msgset_is_valid(const struct mail_search_seqset *set, uint32_t messages_count)
-{
/* when there are no messages, all messagesets are invalid.
if there's at least one message:
- * gives seq1 = seq2 = (uint32_t)-1