dnl ** capabilities
dnl **
-capability="IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS UIDPLUS LIST-EXTENDED I18NLEVEL=1 ENABLE CONDSTORE QRESYNC ESEARCH"
+capability="IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS UIDPLUS LIST-EXTENDED I18NLEVEL=1 ENABLE CONDSTORE QRESYNC ESEARCH SEARCHRES"
AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability", IMAP capabilities)
CFLAGS="$CFLAGS $EXTRA_CFLAGS"
i_error("close(client out) failed: %m");
}
+ if (array_is_created(&client->search_saved_uidset))
+ array_free(&client->search_saved_uidset);
pool_unref(&client->command_pool);
i_free(client);
}
}
+bool client_handle_search_save_ambiguity(struct client_command_context *cmd)
+{
+ struct client_command_context *old_cmd = cmd->next;
+
+ /* search only commands that were added before this command
+ (commands are prepended to the queue, so they're after ourself) */
+ for (; old_cmd != NULL; old_cmd = old_cmd->next) {
+ if (old_cmd->search_save_result)
+ break;
+ }
+ if (old_cmd == NULL)
+ return FALSE;
+
+ /* ambiguity, wait until it's over */
+ i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
+ cmd->client->input_lock = cmd;
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
+ io_remove(&cmd->client->io);
+ return TRUE;
+}
+
void client_enable(struct client *client, enum mailbox_feature features)
{
struct mailbox_status status;
unsigned int uid:1; /* used UID command */
unsigned int cancel:1; /* command is wanted to be cancelled */
unsigned int param_error:1;
+ unsigned int search_save_result:1; /* search result is being updated */
unsigned int temp_executed:1; /* temporary execution state tracking */
};
struct imap_parser *free_parser;
/* command_pool is cleared when the command queue gets empty */
pool_t command_pool;
+ /* New commands are always prepended to the queue */
struct client_command_context *command_queue;
unsigned int command_queue_size;
+ /* SEARCHRES extension: Last saved SEARCH result */
+ ARRAY_TYPE(seq_range) search_saved_uidset;
+
/* client input/output is locked by this command */
struct client_command_context *input_lock;
struct client_command_context *output_lock;
bool client_read_string_args(struct client_command_context *cmd,
unsigned int count, ...);
+/* SEARCHRES extension: Call if $ is being used/updated, returns TRUE if we
+ have to wait for an existing SEARCH SAVE to finish. */
+bool client_handle_search_save_ambiguity(struct client_command_context *cmd);
+
void client_enable(struct client *client, enum mailbox_feature features);
void clients_init(void);
if (!client_verify_mailbox_name(cmd, mailbox, TRUE, FALSE))
return TRUE;
- search_arg = imap_search_get_seqset(cmd, messageset, cmd->uid);
- if (search_arg == NULL)
- return TRUE;
+ ret = imap_search_get_seqset(cmd, messageset, cmd->uid, &search_arg);
+ if (ret <= 0)
+ return ret < 0;
storage = client_find_storage(cmd, &mailbox);
if (storage == NULL)
const struct imap_arg *args;
struct mail_search_arg *search_arg;
const char *uidset;
+ int ret;
if (!client_read_args(cmd, 1, 0, &args))
return FALSE;
return TRUE;
}
- search_arg = imap_search_get_seqset(cmd, uidset, TRUE);
- return search_arg == NULL ? TRUE :
- cmd_expunge_finish(cmd, search_arg);
+ ret = imap_search_get_seqset(cmd, uidset, TRUE, &search_arg);
+ if (ret <= 0)
+ return ret < 0;
+ return cmd_expunge_finish(cmd, search_arg);
}
bool cmd_expunge(struct client_command_context *cmd)
bool cmd_fetch(struct client_command_context *cmd)
{
+ struct client *client = cmd->client;
struct imap_fetch_context *ctx;
const struct imap_arg *args;
struct mail_search_arg *search_arg;
const char *messageset;
+ int ret;
if (!client_read_args(cmd, 0, 0, &args))
return FALSE;
if (!client_verify_open_mailbox(cmd))
return TRUE;
- /* <messageset> <field(s)> [(CHANGEDSINCE <modseq>)] */
+ /* <messageset> <field(s)> [(modifiers)] */
messageset = imap_arg_string(&args[0]);
if (messageset == NULL ||
(args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM) ||
return TRUE;
}
- search_arg = imap_search_get_anyset(cmd, messageset, cmd->uid);
- if (search_arg == NULL)
- return TRUE;
+ /* UID FETCH VANISHED needs the uidset, so convert it to
+ sequence set later */
+ ret = imap_search_get_anyset(cmd, messageset, cmd->uid, &search_arg);
+ if (ret <= 0)
+ return ret < 0;
- ctx = imap_fetch_init(cmd, cmd->client->mailbox);
+ ctx = imap_fetch_init(cmd, client->mailbox);
if (ctx == NULL)
return TRUE;
ctx->search_args = search_arg;
SEARCH_RETURN_MAX = 0x04,
SEARCH_RETURN_ALL = 0x08,
SEARCH_RETURN_COUNT = 0x10,
- SEARCH_RETURN_MODSEQ = 0x20
-#define SEARCH_RETURN_EXTRAS (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_MODSEQ)
+ SEARCH_RETURN_MODSEQ = 0x20,
+ SEARCH_RETURN_SAVE = 0x40
+#define SEARCH_RETURN_EXTRAS \
+ (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_MODSEQ | SEARCH_RETURN_SAVE)
};
struct imap_search_context {
struct timeval start_time;
};
-static bool search_parse_return_options(struct imap_search_context *ctx,
- const struct imap_arg *args)
+static bool
+search_parse_return_options(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ enum search_return_options *return_options_r)
{
const char *name;
+ *return_options_r = 0;
+
while (args->type != IMAP_ARG_EOL) {
if (args->type != IMAP_ARG_ATOM) {
- client_send_command_error(ctx->cmd,
+ client_send_command_error(cmd,
"SEARCH return options contain non-atoms.");
return FALSE;
}
name = t_str_ucase(IMAP_ARG_STR(args));
args++;
if (strcmp(name, "MIN") == 0)
- ctx->return_options |= SEARCH_RETURN_MIN;
+ *return_options_r |= SEARCH_RETURN_MIN;
else if (strcmp(name, "MAX") == 0)
- ctx->return_options |= SEARCH_RETURN_MAX;
+ *return_options_r |= SEARCH_RETURN_MAX;
else if (strcmp(name, "ALL") == 0)
- ctx->return_options |= SEARCH_RETURN_ALL;
+ *return_options_r |= SEARCH_RETURN_ALL;
else if (strcmp(name, "COUNT") == 0)
- ctx->return_options |= SEARCH_RETURN_COUNT;
+ *return_options_r |= SEARCH_RETURN_COUNT;
+ else if (strcmp(name, "SAVE") == 0)
+ *return_options_r |= SEARCH_RETURN_SAVE;
else {
- client_send_command_error(ctx->cmd,
+ client_send_command_error(cmd,
"Unknown SEARCH return option");
return FALSE;
}
}
- if (ctx->return_options == 0)
- ctx->return_options = SEARCH_RETURN_ALL;
- ctx->return_options |= SEARCH_RETURN_ESEARCH;
+ if (*return_options_r == 0)
+ *return_options_r = SEARCH_RETURN_ALL;
+ *return_options_r |= SEARCH_RETURN_ESEARCH;
return TRUE;
}
static void imap_search_send_result(struct imap_search_context *ctx)
{
+ struct client *client = ctx->cmd->client;
const struct seq_range *range;
unsigned int count;
string_t *str;
return;
}
+ if (ctx->return_options ==
+ (SEARCH_RETURN_ESEARCH | SEARCH_RETURN_SAVE)) {
+ /* we only wanted to save the result, don't return
+ ESEARCH result. */
+ return;
+ }
+
str = str_new(default_pool, 1024);
str_append(str, "* ESEARCH (TAG ");
imap_quote_append_string(str, ctx->cmd->tag, FALSE);
(unsigned long long)ctx->highest_seen_modseq);
}
str_append(str, "\r\n");
- o_stream_send(ctx->cmd->client->output,
- str_data(str), str_len(str));
+ o_stream_send(client->output, str_data(str), str_len(str));
}
static int imap_search_deinit(struct imap_search_context *ctx)
mail_free(&ctx->mail);
if (mailbox_search_deinit(&ctx->search_ctx) < 0)
ret = -1;
- else if (!ctx->cmd->cancel)
+
+ if (ret == 0 && !ctx->cmd->cancel)
imap_search_send_result(ctx);
+ else {
+ /* search failed */
+ if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0)
+ array_clear(&ctx->cmd->client->search_saved_uidset);
+ }
(void)mailbox_transaction_commit(&ctx->trans);
return ret;
}
+static void search_update_mail(struct imap_search_context *ctx)
+{
+ uint64_t modseq;
+
+ if ((ctx->return_options & SEARCH_RETURN_MODSEQ) != 0) {
+ modseq = mail_get_modseq(ctx->mail);
+ if (ctx->highest_seen_modseq < modseq)
+ ctx->highest_seen_modseq = modseq;
+ }
+ if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
+ seq_range_array_add(&ctx->cmd->client->search_saved_uidset,
+ 0, ctx->mail->uid);
+ }
+}
+
static bool cmd_search_more(struct client_command_context *cmd)
{
struct imap_search_context *ctx = cmd->context;
struct timeval end_time;
const struct seq_range *range;
unsigned int count;
- uint64_t modseq;
uint32_t id, id_min, id_max;
- bool tryagain;
+ bool tryagain, minmax;
if (cmd->cancel) {
(void)imap_search_deinit(ctx);
id_max = range[count-1].seq2;
}
+ minmax = (opts & (SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) != 0 &&
+ (opts & ~(SEARCH_RETURN_EXTRAS |
+ SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) == 0;
while (mailbox_search_next_nonblock(ctx->search_ctx, ctx->mail,
&tryagain) > 0) {
id = cmd->uid ? ctx->mail->uid : ctx->mail->seq;
ctx->result_count++;
- if ((opts & ~(SEARCH_RETURN_EXTRAS |
- SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) == 0) {
+ if (minmax) {
/* we only care about min/max */
if (id < id_min && (opts & SEARCH_RETURN_MIN) != 0)
id_min = id;
if (id > id_max && (opts & SEARCH_RETURN_MAX) != 0)
id_max = id;
if (id == id_min || id == id_max) {
- /* modseq is looked up when we know the
- actual min/max values */
+ /* return option updates are delayed until
+ we know the actual min/max values */
seq_range_array_add(&ctx->result, 0, id);
}
continue;
}
- if ((ctx->return_options & SEARCH_RETURN_MODSEQ) != 0) {
- modseq = mail_get_modseq(ctx->mail);
- if (ctx->highest_seen_modseq < modseq)
- ctx->highest_seen_modseq = modseq;
- }
-
+ search_update_mail(ctx);
if ((opts & ~(SEARCH_RETURN_EXTRAS |
SEARCH_RETURN_COUNT)) == 0) {
/* we only want to count (and get modseqs) */
if (tryagain)
return FALSE;
- if (ctx->highest_seen_modseq == 0 &&
- (ctx->return_options & SEARCH_RETURN_MODSEQ) != 0 &&
- array_count(&ctx->result) > 0) {
- /* get highest modseq only from MIN/MAX messages */
+ if (minmax && array_count(&ctx->result) > 0 &&
+ (opts & (SEARCH_RETURN_MODSEQ | SEARCH_RETURN_SAVE)) != 0) {
+ /* handle MIN/MAX modseq/save updates */
if ((opts & SEARCH_RETURN_MIN) != 0) {
i_assert(id_min != (uint32_t)-1);
if (cmd->uid) {
} else {
mail_set_seq(ctx->mail, id_min);
}
- ctx->highest_seen_modseq = mail_get_modseq(ctx->mail);
+ search_update_mail(ctx);
}
if ((opts & SEARCH_RETURN_MAX) != 0) {
i_assert(id_max != 0);
} else {
mail_set_seq(ctx->mail, id_max);
}
- modseq = mail_get_modseq(ctx->mail);
- if (ctx->highest_seen_modseq < modseq)
- ctx->highest_seen_modseq = modseq;
+ search_update_mail(ctx);
}
}
end_time.tv_sec -= ctx->start_time.tv_sec;
end_time.tv_usec -= ctx->start_time.tv_usec;
if (end_time.tv_usec < 0) {
- end_time.tv_sec++;
+ end_time.tv_sec--;
end_time.tv_usec += 1000000;
}
bool cmd_search(struct client_command_context *cmd)
{
+ struct client *client = cmd->client;
struct imap_search_context *ctx;
struct mail_search_arg *sargs;
const struct imap_arg *args;
- int args_count;
- const char *error, *charset;
+ enum search_return_options return_options;
+ int ret, args_count;
+ const char *charset;
args_count = imap_parser_read_args(cmd->parser, 0, 0, &args);
if (args_count < 1) {
"Missing SEARCH arguments.");
return TRUE;
}
- cmd->client->input_lock = NULL;
+ client->input_lock = NULL;
if (!client_verify_open_mailbox(cmd))
return TRUE;
- ctx = p_new(cmd->pool, struct imap_search_context, 1);
- ctx->cmd = cmd;
-
if (args->type == IMAP_ARG_ATOM && args[1].type == IMAP_ARG_LIST &&
strcasecmp(IMAP_ARG_STR_NONULL(args), "RETURN") == 0) {
args++;
- if (!search_parse_return_options(ctx, IMAP_ARG_LIST_ARGS(args)))
+ if (!search_parse_return_options(cmd, IMAP_ARG_LIST_ARGS(args),
+ &return_options))
return TRUE;
args++;
+
+ if ((return_options & SEARCH_RETURN_SAVE) != 0) {
+ /* wait if there is another SEARCH SAVE command
+ running. */
+ cmd->search_save_result = TRUE;
+ if (client_handle_search_save_ambiguity(cmd))
+ return FALSE;
+ }
} else {
- ctx->return_options = SEARCH_RETURN_ALL;
+ return_options = SEARCH_RETURN_ALL;
+ }
+
+ if ((return_options & SEARCH_RETURN_SAVE) != 0) {
+ /* make sure the search result gets cleared if SEARCH fails */
+ if (array_is_created(&client->search_saved_uidset))
+ array_clear(&client->search_saved_uidset);
+ else
+ i_array_init(&client->search_saved_uidset, 128);
}
+
+ ctx = p_new(cmd->pool, struct imap_search_context, 1);
+ ctx->cmd = cmd;
+ ctx->return_options = return_options;
+
if (args->type == IMAP_ARG_ATOM &&
strcasecmp(IMAP_ARG_STR_NONULL(args), "CHARSET") == 0) {
/* CHARSET specified */
charset = "UTF-8";
}
- sargs = imap_search_args_build(cmd->pool, cmd->client->mailbox,
- args, &error);
- if (sargs == NULL) {
- /* error in search arguments */
- client_send_tagline(cmd, t_strconcat("BAD ", error, NULL));
- return TRUE;
- }
+ ret = imap_search_args_build(cmd, args, &sargs);
+ if (ret <= 0)
+ return ret < 0;
imap_search_init(ctx, charset, sargs);
cmd->func = cmd_search_more;
enum mail_sort_type sorting[MAX_SORT_PROGRAM_SIZE];
const struct imap_arg *args;
int args_count;
- pool_t pool;
- const char *error, *charset;
+ const char *charset;
+ int ret;
args_count = imap_parser_read_args(cmd->parser, 0, 0, &args);
if (args_count == -2)
charset = IMAP_ARG_STR(args);
args++;
- pool = pool_alloconly_create("mail_search_args", 2048);
-
- sargs = imap_search_args_build(pool, client->mailbox, args, &error);
- if (sargs == NULL) {
- /* error in search arguments */
- client_send_tagline(cmd, t_strconcat("NO ", error, NULL));
- } else if (imap_sort(cmd, charset, sargs, sorting) == 0) {
- pool_unref(&pool);
- return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
- (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
- 0, "OK Sort completed.");
- } else {
+ ret = imap_search_args_build(cmd, args, &sargs);
+ if (ret <= 0)
+ return ret < 0;
+
+ if (imap_sort(cmd, charset, sargs, sorting) < 0) {
client_send_storage_error(cmd,
mailbox_get_storage(client->mailbox));
+ return TRUE;
}
- pool_unref(&pool);
- return TRUE;
+ return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+ (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+ 0, "OK Sort completed.");
}
struct imap_store_context {
struct client_command_context *cmd;
- const char *messageset;
uint64_t max_modseq;
enum mail_flags flags;
const char *const *keywords_list = NULL;
ctx->max_modseq = (uint64_t)-1;
- ctx->messageset = imap_arg_string(args++);
-
if (args->type == IMAP_ARG_LIST) {
if (!store_parse_modifiers(ctx, IMAP_ARG_LIST_ARGS(args)))
return FALSE;
}
type = imap_arg_string(args++);
- if (ctx->messageset == NULL || type == NULL ||
- !get_modify_type(ctx, type)) {
+ if (type == NULL || !get_modify_type(ctx, type)) {
client_send_command_error(cmd, "Invalid arguments.");
return FALSE;
}
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;
string_t *str;
- bool failed;
+ int ret;
if (!client_read_args(cmd, 0, 0, &args))
return FALSE;
if (!client_verify_open_mailbox(cmd))
return TRUE;
- memset(&ctx, 0, sizeof(ctx));
- ctx.cmd = cmd;
- if (!store_parse_args(&ctx, args))
+ if (args->type != IMAP_ARG_ATOM) {
+ client_send_command_error(cmd, "Invalid arguments.");
return TRUE;
+ }
+ ret = imap_search_get_seqset(cmd, IMAP_ARG_STR_NONULL(args),
+ cmd->uid, &search_arg);
+ if (ret <= 0)
+ return ret < 0;
- search_arg = imap_search_get_seqset(cmd, ctx.messageset, cmd->uid);
- if (search_arg == NULL)
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.cmd = cmd;
+ if (!store_parse_args(&ctx, ++args))
return TRUE;
if (ctx.silent)
if (ctx.keywords != NULL)
mailbox_keywords_free(client->mailbox, &ctx.keywords);
- if (mailbox_search_deinit(&search_ctx) < 0) {
- failed = TRUE;
+ ret = mailbox_search_deinit(&search_ctx);
+ if (ret < 0)
mailbox_transaction_rollback(&t);
- } else {
- failed = mailbox_transaction_commit(&t) < 0;
- }
-
- if (!failed) {
- /* With UID STORE we have to return UID for the flags as well.
- Unfortunately we don't have the ability to separate those
- flag changes that were caused by UID STORE and those that
- came externally, so we'll just send the UID for all flag
- changes that we see. */
- enum imap_sync_flags imap_sync_flags = 0;
-
- if (cmd->uid &&
- (!ctx.silent || (client->enabled_features &
- MAILBOX_FEATURE_CONDSTORE) != 0))
- imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;
-
- return cmd_sync(cmd,
- (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
- imap_sync_flags, tagged_reply);
- } else {
+ else
+ ret = mailbox_transaction_commit(&t);
+ if (ret < 0) {
client_send_storage_error(cmd,
mailbox_get_storage(client->mailbox));
return TRUE;
}
+
+ /* With UID STORE we have to return UID for the flags as well.
+ Unfortunately we don't have the ability to separate those
+ flag changes that were caused by UID STORE and those that
+ came externally, so we'll just send the UID for all flag
+ changes that we see. */
+ if (cmd->uid && (!ctx.silent || (client->enabled_features &
+ MAILBOX_FEATURE_CONDSTORE) != 0))
+ imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;
+
+ return cmd_sync(cmd, (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+ imap_sync_flags, tagged_reply);
}
enum mail_thread_type threading;
struct mail_search_arg *sargs;
const struct imap_arg *args;
- int args_count;
- pool_t pool;
- const char *error, *charset, *str;
+ int ret, args_count;
+ const char *charset, *str;
args_count = imap_parser_read_args(cmd->parser, 0, 0, &args);
if (args_count == -2)
charset = IMAP_ARG_STR(args);
args++;
- pool = pool_alloconly_create("mail_search_args", 2048);
+ ret = imap_search_args_build(cmd, args, &sargs);
+ if (ret <= 0)
+ return ret < 0;
- sargs = imap_search_args_build(pool, client->mailbox, args, &error);
- if (sargs == NULL) {
- /* error in search arguments */
- client_send_tagline(cmd, t_strconcat("NO ", error, NULL));
- } else if (imap_thread(cmd, charset, sargs, threading) == 0) {
- pool_unref(&pool);
- return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
- (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
- 0, "OK Thread completed.");
- } else {
+ if (imap_thread(cmd, charset, sargs, threading) < 0) {
client_send_storage_error(cmd,
mailbox_get_storage(client->mailbox));
+ return TRUE;
}
- pool_unref(&pool);
- return TRUE;
+ return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
+ (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
+ 0, "OK Thread completed.");
}
ctx->all_headers_ctx);
/* Delayed uidset -> seqset conversion. VANISHED needs the uidset. */
- mail_search_args_init(ctx->search_args, ctx->box, TRUE);
+ mail_search_args_init(ctx->search_args, ctx->box, TRUE,
+ &ctx->cmd->client->search_saved_uidset);
ctx->search_ctx =
mailbox_search_init(ctx->trans, NULL, ctx->search_args, NULL);
return 0;
const char *error;
};
-struct mail_search_arg *
-imap_search_args_build(pool_t pool, struct mailbox *box,
- const struct imap_arg *args, const char **error_r)
+static bool search_args_have_searchres(struct mail_search_arg *sargs)
+{
+ 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;
+ }
+ }
+ return FALSE;
+}
+
+int imap_search_args_build(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ struct mail_search_arg **search_args_r)
{
struct mail_search_arg *sargs;
+ const char *error;
- sargs = mail_search_build_from_imap_args(pool, args, error_r);
- if (sargs == NULL)
- return NULL;
+ sargs = mail_search_build_from_imap_args(cmd->pool, args, &error);
+ if (sargs == NULL) {
+ client_send_command_error(cmd, error);
+ return -1;
+ }
- mail_search_args_init(sargs, box, TRUE);
- return sargs;
+ if (search_args_have_searchres(sargs)) {
+ if (client_handle_search_save_ambiguity(cmd))
+ return 0;
+ }
+
+ mail_search_args_init(sargs, cmd->client->mailbox, TRUE,
+ &cmd->client->search_saved_uidset);
+ *search_args_r = sargs;
+ return 1;
}
static bool
return 0;
}
-struct mail_search_arg *
-imap_search_get_seqset(struct client_command_context *cmd,
- const char *set, bool uid)
+int imap_search_get_seqset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_arg **search_arg_r)
+{
+ int ret;
+
+ ret = imap_search_get_anyset(cmd, set, uid, search_arg_r);
+ if (ret > 0) {
+ mail_search_args_init(*search_arg_r,
+ cmd->client->mailbox, TRUE,
+ &cmd->client->search_saved_uidset);
+ }
+ return ret;
+}
+
+static int imap_search_get_searchres(struct client_command_context *cmd,
+ struct mail_search_arg **search_arg_r)
{
struct mail_search_arg *search_arg;
- search_arg = imap_search_get_anyset(cmd, set, uid);
- if (uid && search_arg != NULL)
- mail_search_args_init(search_arg, cmd->client->mailbox, TRUE);
- return search_arg;
+ if (client_handle_search_save_ambiguity(cmd))
+ return 0;
+ search_arg = p_new(cmd->pool, struct mail_search_arg, 1);
+ if (array_is_created(&cmd->client->search_saved_uidset)) {
+ search_arg->type = SEARCH_UIDSET;
+ p_array_init(&search_arg->value.seqset, cmd->pool,
+ array_count(&cmd->client->search_saved_uidset));
+ array_append_array(&search_arg->value.seqset,
+ &cmd->client->search_saved_uidset);
+ } else {
+ /* $ not set yet, match nothing */
+ search_arg->type = SEARCH_ALL;
+ search_arg->not = TRUE;
+ }
+ *search_arg_r = search_arg;
+ return 1;
}
-struct mail_search_arg *
-imap_search_get_anyset(struct client_command_context *cmd,
- const char *set, bool uid)
+int imap_search_get_anyset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_arg **search_arg_r)
{
- struct mail_search_arg *search_arg = NULL;
const char *error = NULL;
int ret;
+ if (strcmp(set, "$") == 0) {
+ /* SEARCHRES extension: replace $ with the last saved
+ search result */
+ return imap_search_get_searchres(cmd, search_arg_r);
+ }
if (!uid)
- ret = imap_search_get_msgset_arg(cmd, set, &search_arg, &error);
+ ret = imap_search_get_msgset_arg(cmd, set, search_arg_r, &error);
else
- ret = imap_search_get_uidset_arg(cmd, set, &search_arg, &error);
+ ret = imap_search_get_uidset_arg(cmd, set, search_arg_r, &error);
if (ret < 0) {
client_send_command_error(cmd, error);
- return NULL;
+ return -1;
}
-
- return search_arg;
+ return 1;
}
struct mailbox;
struct client_command_context;
-/* Builds search arguments based on IMAP arguments. */
-struct mail_search_arg *
-imap_search_args_build(pool_t pool, struct mailbox *box,
- const struct imap_arg *args, const char **error_r);
+/* Builds search arguments based on IMAP arguments. Returns -1 if search
+ arguments are invalid, 0 if we have to wait for unambiguity,
+ 1 if we can continue. */
+int imap_search_args_build(struct client_command_context *cmd,
+ const struct imap_arg *args,
+ struct mail_search_arg **search_args_r);
-struct mail_search_arg *
-imap_search_get_seqset(struct client_command_context *cmd,
- const char *set, bool uid);
-struct mail_search_arg *
-imap_search_get_anyset(struct client_command_context *cmd,
- const char *set, bool uid);
+/* Returns -1 if set is invalid, 0 if we have to wait for unambiguity,
+ 1 if we can continue. */
+int imap_search_get_seqset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_arg **search_arg_r);
+int imap_search_get_anyset(struct client_command_context *cmd,
+ const char *set, bool uid,
+ struct mail_search_arg **search_arg_r);
#endif
sarg = *next_sarg;
p_array_init(&sarg->value.seqset, data->pool, 16);
+ if (strcmp(sarg->value.str, "$") == 0) {
+ /* SEARCHRES: delay initialization */
+ return TRUE;
+ }
if (imap_messageset_parse(&sarg->value.seqset,
sarg->value.str) < 0) {
data->error = "Invalid UID messageset";
return FALSE;
}
return TRUE;
+ } else if (strcmp(str, "$") == 0) {
+ /* SEARCHRES: delay initialization */
+ if (!ARG_NEW_SINGLE(SEARCH_UIDSET))
+ return FALSE;
+
+ (*next_sarg)->value.str = p_strdup(data->pool, "$");
+ p_array_init(&(*next_sarg)->value.seqset,
+ data->pool, 16);
+ return TRUE;
}
break;
}
}
static void
-mailbox_uidseq_change(struct mail_search_arg *arg, struct mailbox *box)
+mailbox_uidset_change(struct mail_search_arg *arg, struct mailbox *box,
+ const ARRAY_TYPE(seq_range) *search_saved_uidset)
{
struct seq_range *uids;
unsigned int i, count;
uint32_t seq1, seq2;
+ if (strcmp(arg->value.str, "$") == 0) {
+ /* SEARCHRES: Replace with saved uidset */
+ array_clear(&arg->value.seqset);
+ if (search_saved_uidset == NULL ||
+ !array_is_created(search_saved_uidset))
+ return;
+
+ array_append_array(&arg->value.seqset, search_saved_uidset);
+ return;
+ }
+
arg->type = SEARCH_SEQSET;
/* make a copy of the UIDs */
count = array_count(&arg->value.seqset);
+ if (count == 0) {
+ /* empty set, keep it */
+ return;
+ }
uids = t_new(struct seq_range, count);
memcpy(uids, array_idx(&arg->value.seqset, 0), sizeof(*uids) * count);
}
void mail_search_args_init(struct mail_search_arg *args,
- struct mailbox *box, bool change_uidsets)
+ struct mailbox *box, bool change_uidsets,
+ const ARRAY_TYPE(seq_range) *search_saved_uidset)
{
const char *keywords[2];
switch (args->type) {
case SEARCH_UIDSET:
if (change_uidsets) T_BEGIN {
- mailbox_uidseq_change(args, box);
+ mailbox_uidset_change(args, box,
+ search_saved_uidset);
} T_END;
break;
case SEARCH_MODSEQ:
case SEARCH_SUB:
case SEARCH_OR:
mail_search_args_init(args->value.subargs, box,
- change_uidsets);
+ change_uidsets,
+ search_saved_uidset);
break;
default:
break;
/* Allocate keywords for search arguments. If change_uidsets is TRUE,
change uidsets to seqsets. */
void mail_search_args_init(struct mail_search_arg *args,
- struct mailbox *box, bool change_uidsets);
+ struct mailbox *box, bool change_uidsets,
+ const ARRAY_TYPE(seq_range) *search_saved_uidset);
/* Free keywords. The args can initialized afterwards again if needed. */
void mail_search_args_deinit(struct mail_search_arg *args,
struct mailbox *box);