1
1
/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
4
#include "mail-storage.h"
5
#include "mail-search.h"
6
#include "seq-range-array.h"
8
#include "imap-resp-code.h"
9
#include "imap-quote.h"
10
#include "imap-seqset.h"
11
#include "imap-util.h"
12
#include "mail-search-build.h"
14
#include "imap-search-args.h"
7
15
#include "imap-search.h"
8
#include "imap-parser.h"
9
#include "imap-messageset.h"
13
struct search_build_data {
17
static int imap_search_deinit(struct imap_search_context *ctx);
20
imap_uidset_parse(pool_t pool, struct mailbox *box, const char *uidset,
21
struct mail_search_seqset **seqset_r, const char **error_r)
23
struct mail_search_seqset *seqset, **p;
26
*seqset_r = imap_messageset_parse(pool, uidset);
27
if (*seqset_r == NULL) {
28
*error_r = "Invalid UID messageset";
33
for (seqset = *seqset_r; seqset != NULL; seqset = seqset->next) {
34
if (seqset->seq1 == (uint32_t)-1) {
35
/* last message, stays same */
39
last = seqset->seq2 == (uint32_t)-1;
40
mailbox_get_uids(box, seqset->seq1, seqset->seq2,
41
&seqset->seq1, &seqset->seq2);
42
if (seqset->seq1 == 0 && last) {
43
/* we need special case for too_high_uid:* case */
44
seqset->seq1 = seqset->seq2 = (uint32_t)-1;
47
if (seqset->seq1 != 0)
20
imap_partial_range_parse(struct imap_search_context *ctx, const char *str)
24
for (; *str >= '0' && *str <= '9'; str++)
25
ctx->partial1 = ctx->partial1 * 10 + *str-'0';
26
if (*str != ':' || ctx->partial1 == 0)
28
for (str++; *str >= '0' && *str <= '9'; str++)
29
ctx->partial2 = ctx->partial2 * 10 + *str-'0';
30
if (*str != '\0' || ctx->partial2 == 0)
33
if (ctx->partial1 > ctx->partial2) {
34
uint32_t temp = ctx->partial2;
35
ctx->partial2 = ctx->partial1;
43
search_parse_return_options(struct imap_search_context *ctx,
44
const struct imap_arg *args)
46
struct client_command_context *cmd = ctx->cmd;
47
const char *name, *str;
50
while (args->type != IMAP_ARG_EOL) {
51
if (args->type != IMAP_ARG_ATOM) {
52
client_send_command_error(cmd,
53
"SEARCH return options contain non-atoms.");
56
name = t_str_ucase(IMAP_ARG_STR_NONULL(args));
58
if (strcmp(name, "MIN") == 0)
59
ctx->return_options |= SEARCH_RETURN_MIN;
60
else if (strcmp(name, "MAX") == 0)
61
ctx->return_options |= SEARCH_RETURN_MAX;
62
else if (strcmp(name, "ALL") == 0)
63
ctx->return_options |= SEARCH_RETURN_ALL;
64
else if (strcmp(name, "COUNT") == 0)
65
ctx->return_options |= SEARCH_RETURN_COUNT;
66
else if (strcmp(name, "SAVE") == 0)
67
ctx->return_options |= SEARCH_RETURN_SAVE;
68
else if (strcmp(name, "UPDATE") == 0)
69
ctx->return_options |= SEARCH_RETURN_UPDATE;
70
else if (strcmp(name, "PARTIAL") == 0) {
71
if (ctx->partial1 != 0) {
72
client_send_command_error(cmd,
73
"PARTIAL can be used only once.");
76
ctx->return_options |= SEARCH_RETURN_PARTIAL;
77
if (args->type != IMAP_ARG_ATOM) {
78
client_send_command_error(cmd,
79
"PARTIAL range missing.");
82
str = IMAP_ARG_STR_NONULL(args);
83
if (imap_partial_range_parse(ctx, str) < 0) {
84
client_send_command_error(cmd,
85
"PARTIAL range broken.");
90
client_send_command_error(cmd,
91
"Unknown SEARCH return option");
96
if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0 &&
97
client_search_update_lookup(cmd->client, cmd->tag, &idx) != NULL) {
98
client_send_command_error(cmd, "Duplicate search update tag");
101
if ((ctx->return_options & SEARCH_RETURN_PARTIAL) != 0 &&
102
(ctx->return_options & SEARCH_RETURN_ALL) != 0) {
103
client_send_command_error(cmd, "PARTIAL conflicts with ALL");
107
if (ctx->return_options == 0)
108
ctx->return_options = SEARCH_RETURN_ALL;
109
ctx->return_options |= SEARCH_RETURN_ESEARCH;
113
static void imap_search_args_check(struct imap_search_context *ctx,
114
const struct mail_search_arg *sargs)
116
for (; sargs != NULL; sargs = sargs->next) {
117
switch (sargs->type) {
119
ctx->have_seqsets = TRUE;
122
ctx->have_modseqs = TRUE;
126
imap_search_args_check(ctx, sargs->value.subargs);
134
static void imap_search_result_save(struct imap_search_context *ctx)
136
struct client *client = ctx->cmd->client;
137
struct mail_search_result *result;
138
struct imap_search_update *update;
140
if (!array_is_created(&client->search_updates))
141
i_array_init(&client->search_updates, 32);
142
else if (array_count(&client->search_updates) >=
143
CLIENT_MAX_SEARCH_UPDATES) {
144
/* too many updates */
145
string_t *str = t_str_new(256);
146
str_append(str, "* NO [NOUPDATE ");
147
imap_quote_append_string(str, ctx->cmd->tag, FALSE);
148
str_append_c(str, ']');
149
client_send_line(client, str_c(str));
150
ctx->return_options &= ~SEARCH_RETURN_UPDATE;
153
result = mailbox_search_result_save(ctx->search_ctx,
154
MAILBOX_SEARCH_RESULT_FLAG_UPDATE |
155
MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC);
157
update = array_append_space(&client->search_updates);
158
update->tag = i_strdup(ctx->cmd->tag);
159
update->result = result;
160
update->return_uids = ctx->cmd->uid;
163
static void imap_search_send_result_standard(struct imap_search_context *ctx)
165
const struct seq_range *range;
166
unsigned int i, count;
170
str = t_str_new(1024);
171
range = array_get(&ctx->result, &count);
172
str_append(str, ctx->sorting ? "* SORT" : "* SEARCH");
173
for (i = 0; i < count; i++) {
174
for (seq = range[i].seq1; seq <= range[i].seq2; seq++)
175
str_printfa(str, " %u", seq);
176
if (str_len(str) >= 1024-32) {
177
o_stream_send(ctx->cmd->client->output,
178
str_data(str), str_len(str));
179
str_truncate(str, 0);
183
if (ctx->highest_seen_modseq != 0) {
184
str_printfa(str, " (MODSEQ %llu)",
185
(unsigned long long)ctx->highest_seen_modseq);
187
str_append(str, "\r\n");
188
o_stream_send(ctx->cmd->client->output,
189
str_data(str), str_len(str));
193
imap_search_send_partial(struct imap_search_context *ctx, string_t *str)
195
struct seq_range *range;
197
unsigned int i, count, delete_count;
199
str_printfa(str, " PARTIAL (%u:%u ", ctx->partial1, ctx->partial2);
203
/* we need to be able to handle non-sorted seq ranges, so do this
204
ourself instead of using seq_range_array_*() functions. */
205
range = array_get_modifiable(&ctx->result, &count);
207
for (i = n = 0; i < count; i++) {
208
diff = range[i].seq2 - range[i].seq1;
209
if (n + diff >= ctx->partial1) {
210
range[i].seq1 += ctx->partial1 - n;
216
for (n = ctx->partial1; i < count; i++) {
217
diff = range[i].seq2 - range[i].seq1;
218
if (n + diff >= ctx->partial2) {
219
range[i].seq2 = range[i].seq1 + (ctx->partial2 - n);
220
array_delete(&ctx->result, i + 1, count-(i+1));
225
array_delete(&ctx->result, 0, delete_count);
227
if (array_count(&ctx->result) == 0) {
228
/* no results (in range) */
229
str_append(str, "NIL");
231
imap_write_seq_range(str, &ctx->result);
233
str_append_c(str, ')');
236
static void imap_search_send_result(struct imap_search_context *ctx)
238
struct client *client = ctx->cmd->client;
239
const struct seq_range *range;
243
if ((ctx->return_options & SEARCH_RETURN_ESEARCH) == 0) {
244
imap_search_send_result_standard(ctx);
248
if (ctx->return_options ==
249
(SEARCH_RETURN_ESEARCH | SEARCH_RETURN_SAVE)) {
250
/* we only wanted to save the result, don't return
255
str = str_new(default_pool, 1024);
256
str_append(str, "* ESEARCH (TAG ");
257
imap_quote_append_string(str, ctx->cmd->tag, FALSE);
258
str_append_c(str, ')');
261
str_append(str, " UID");
263
range = array_get(&ctx->result, &count);
265
if ((ctx->return_options & SEARCH_RETURN_MIN) != 0)
266
str_printfa(str, " MIN %u", range[0].seq1);
267
if ((ctx->return_options & SEARCH_RETURN_MAX) != 0)
268
str_printfa(str, " MAX %u", range[count-1].seq2);
269
if ((ctx->return_options & SEARCH_RETURN_ALL) != 0) {
270
str_append(str, " ALL ");
271
imap_write_seq_range(str, &ctx->result);
275
if ((ctx->return_options & SEARCH_RETURN_PARTIAL) != 0)
276
imap_search_send_partial(ctx, str);
278
if ((ctx->return_options & SEARCH_RETURN_COUNT) != 0)
279
str_printfa(str, " COUNT %u", ctx->result_count);
280
if (ctx->highest_seen_modseq != 0) {
281
str_printfa(str, " MODSEQ %llu",
282
(unsigned long long)ctx->highest_seen_modseq);
284
str_append(str, "\r\n");
285
o_stream_send(client->output, str_data(str), str_len(str));
288
static void search_update_mail(struct imap_search_context *ctx)
292
if ((ctx->return_options & SEARCH_RETURN_MODSEQ) != 0) {
293
modseq = mail_get_modseq(ctx->mail);
294
if (ctx->highest_seen_modseq < modseq)
295
ctx->highest_seen_modseq = modseq;
297
if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
298
seq_range_array_add(&ctx->cmd->client->search_saved_uidset,
303
static void search_add_result_id(struct imap_search_context *ctx, uint32_t id)
305
struct seq_range *range;
308
/* only append the data. this is especially important when we're
309
returning a sort result. */
310
range = array_get_modifiable(&ctx->result, &count);
311
if (count > 0 && id == range[count-1].seq2 + 1) {
312
range[count-1].seq2++;
314
range = array_append_space(&ctx->result);
315
range->seq1 = range->seq2 = id;
319
static bool cmd_search_more(struct client_command_context *cmd)
321
struct imap_search_context *ctx = cmd->context;
322
enum search_return_options opts = ctx->return_options;
323
enum mailbox_sync_flags sync_flags;
324
struct timeval end_time;
325
const struct seq_range *range;
327
uint32_t id, id_min, id_max;
328
const char *ok_reply;
330
bool tryagain, minmax, lost_data;
333
(void)imap_search_deinit(ctx);
337
range = array_get(&ctx->result, &count);
342
id_min = range[0].seq1;
343
id_max = range[count-1].seq2;
346
minmax = (opts & (SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) != 0 &&
347
(opts & ~(SEARCH_RETURN_NORESULTS |
348
SEARCH_RETURN_MIN | SEARCH_RETURN_MAX)) == 0;
349
while (mailbox_search_next_nonblock(ctx->search_ctx, ctx->mail,
351
id = cmd->uid ? ctx->mail->uid : ctx->mail->seq;
355
/* we only care about min/max */
356
if (id_min == 0 && (opts & SEARCH_RETURN_MIN) != 0)
358
if ((opts & SEARCH_RETURN_MAX) != 0)
360
if (id == id_min || id == id_max) {
361
/* return option updates are delayed until
362
we know the actual min/max values */
363
search_add_result_id(ctx, id);
368
search_update_mail(ctx);
369
if ((opts & ~(SEARCH_RETURN_NORESULTS |
370
SEARCH_RETURN_COUNT)) == 0) {
371
/* we only want to count (and get modseqs) */
374
search_add_result_id(ctx, id);
379
if (minmax && array_count(&ctx->result) > 0 &&
380
(opts & (SEARCH_RETURN_MODSEQ | SEARCH_RETURN_SAVE)) != 0) {
381
/* handle MIN/MAX modseq/save updates */
382
if ((opts & SEARCH_RETURN_MIN) != 0) {
383
i_assert(id_min != 0);
385
if (!mail_set_uid(ctx->mail, id_min))
388
mail_set_seq(ctx->mail, id_min);
390
search_update_mail(ctx);
392
if ((opts & SEARCH_RETURN_MAX) != 0) {
393
i_assert(id_max != 0);
395
if (!mail_set_uid(ctx->mail, id_max))
398
mail_set_seq(ctx->mail, id_max);
400
search_update_mail(ctx);
404
lost_data = mailbox_search_seen_lost_data(ctx->search_ctx);
405
if (imap_search_deinit(ctx) < 0) {
406
client_send_storage_error(cmd,
407
mailbox_get_storage(cmd->client->mailbox));
411
if (gettimeofday(&end_time, NULL) < 0)
412
memset(&end_time, 0, sizeof(end_time));
414
time_msecs = timeval_diff_msecs(&end_time, &ctx->start_time);
416
sync_flags = MAILBOX_SYNC_FLAG_FAST;
417
if (!cmd->uid || ctx->have_seqsets)
418
sync_flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
419
ok_reply = t_strdup_printf("OK %s%s completed (%d.%03d secs).",
420
lost_data ? "["IMAP_RESP_CODE_EXPUNGEISSUED"] " : "",
421
!ctx->sorting ? "Search" : "Sort",
422
time_msecs/1000, time_msecs%1000);
423
return cmd_sync(cmd, sync_flags, 0, ok_reply);
426
static void cmd_search_more_callback(struct client_command_context *cmd)
428
struct client *client = cmd->client;
431
o_stream_cork(client->output);
432
finished = cmd_search_more(cmd);
433
o_stream_uncork(client->output);
436
(void)client_handle_unfinished_cmd(cmd);
438
client_command_free(&cmd);
439
(void)cmd_sync_delayed(client);
441
if (client->disconnected)
442
client_destroy(client, NULL);
444
client_continue_pending_input(client);
447
int cmd_search_parse_return_if_found(struct imap_search_context *ctx,
448
const struct imap_arg **_args)
450
const struct imap_arg *args = *_args;
451
struct client_command_context *cmd = ctx->cmd;
453
if (!(args->type == IMAP_ARG_ATOM && args[1].type == IMAP_ARG_LIST &&
454
strcasecmp(IMAP_ARG_STR_NONULL(args), "RETURN") == 0)) {
455
ctx->return_options = SEARCH_RETURN_ALL;
460
if (!search_parse_return_options(ctx, IMAP_ARG_LIST_ARGS(args)))
464
if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0) {
465
/* wait if there is another SEARCH SAVE command running. */
466
cmd->search_save_result = TRUE;
467
if (client_handle_search_save_ambiguity(cmd))
470
/* make sure the search result gets cleared if SEARCH fails */
471
if (array_is_created(&cmd->client->search_saved_uidset))
472
array_clear(&cmd->client->search_saved_uidset);
57
static struct mail_search_arg *
58
search_arg_new(pool_t pool, enum mail_search_arg_type type)
60
struct mail_search_arg *arg;
62
arg = p_new(pool, struct mail_search_arg, 1);
69
arg_get_next(struct search_build_data *data, const struct imap_arg **args,
72
if ((*args)->type == IMAP_ARG_EOL) {
73
data->error = "Missing parameter for argument";
76
if ((*args)->type != IMAP_ARG_ATOM &&
77
(*args)->type != IMAP_ARG_STRING) {
78
data->error = "Invalid parameter for argument";
82
*value_r = IMAP_ARG_STR(*args);
87
#define ARG_NEW_SINGLE(type) \
88
arg_new_single(data, next_sarg, type)
90
arg_new_single(struct search_build_data *data,
91
struct mail_search_arg **next_sarg,
92
enum mail_search_arg_type type)
94
*next_sarg = search_arg_new(data->pool, type);
98
#define ARG_NEW_STR(type) \
99
arg_new_str(data, args, next_sarg, type)
101
arg_new_str(struct search_build_data *data,
102
const struct imap_arg **args, struct mail_search_arg **next_sarg,
103
enum mail_search_arg_type type)
105
struct mail_search_arg *sarg;
108
*next_sarg = sarg = search_arg_new(data->pool, type);
109
if (!arg_get_next(data, args, &value))
111
sarg->value.str = p_strdup(data->pool, value);
115
#define ARG_NEW_FLAGS(flags) \
116
arg_new_flags(data, next_sarg, flags)
118
arg_new_flags(struct search_build_data *data,
119
struct mail_search_arg **next_sarg, enum mail_flags flags)
121
struct mail_search_arg *sarg;
123
*next_sarg = sarg = search_arg_new(data->pool, SEARCH_FLAGS);
124
sarg->value.flags = flags;
129
arg_new_keyword(struct search_build_data *data,
130
const struct imap_arg **args,
131
struct mail_search_arg **next_sarg)
133
struct mail_search_arg *sarg;
134
const char *value, *keywords[2];
135
struct mail_storage *storage;
136
enum mail_error error;
138
*next_sarg = sarg = search_arg_new(data->pool, SEARCH_KEYWORDS);
139
if (!arg_get_next(data, args, &value))
145
if (mailbox_keywords_create(data->box, keywords,
146
&sarg->value.keywords) < 0) {
147
storage = mailbox_get_storage(data->box);
148
data->error = mail_storage_get_last_error(storage, &error);
154
#define ARG_NEW_SIZE(type) \
155
arg_new_size(data, args, next_sarg, type)
157
arg_new_size(struct search_build_data *data,
158
const struct imap_arg **args, struct mail_search_arg **next_sarg,
159
enum mail_search_arg_type type)
161
struct mail_search_arg *sarg;
165
*next_sarg = sarg = search_arg_new(data->pool, type);
166
if (!arg_get_next(data, args, &value))
169
sarg->value.size = strtoull(value, &p, 10);
171
data->error = "Invalid search size parameter";
177
#define ARG_NEW_DATE(type) \
178
arg_new_date(data, args, next_sarg, type)
180
arg_new_date(struct search_build_data *data,
181
const struct imap_arg **args, struct mail_search_arg **next_sarg,
182
enum mail_search_arg_type type)
184
struct mail_search_arg *sarg;
187
*next_sarg = sarg = search_arg_new(data->pool, type);
188
if (!arg_get_next(data, args, &value))
190
if (!imap_parse_date(value, &sarg->value.time)) {
191
data->error = "Invalid search date parameter";
197
#define ARG_NEW_HEADER(type, hdr_name) \
198
arg_new_header(data, args, next_sarg, type, hdr_name)
200
arg_new_header(struct search_build_data *data,
201
const struct imap_arg **args, struct mail_search_arg **next_sarg,
202
enum mail_search_arg_type type, const char *hdr_name)
204
struct mail_search_arg *sarg;
207
*next_sarg = sarg = search_arg_new(data->pool, type);
208
if (!arg_get_next(data, args, &value))
211
sarg->hdr_field_name = p_strdup(data->pool, hdr_name);
212
sarg->value.str = p_strdup(data->pool, value);
216
static bool search_arg_build(struct search_build_data *data,
217
const struct imap_arg **args,
218
struct mail_search_arg **next_sarg)
220
struct mail_search_seqset *seqset;
221
struct mail_search_arg **subargs;
222
const struct imap_arg *arg;
225
if ((*args)->type == IMAP_ARG_EOL) {
226
data->error = "Missing argument";
232
if (arg->type == IMAP_ARG_NIL) {
233
/* NIL not allowed */
234
data->error = "NIL not allowed";
238
if (arg->type == IMAP_ARG_LIST) {
239
const struct imap_arg *listargs = IMAP_ARG_LIST_ARGS(arg);
241
if (listargs->type == IMAP_ARG_EOL) {
242
data->error = "Empty list not allowed";
246
*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
247
subargs = &(*next_sarg)->value.subargs;
248
while (listargs->type != IMAP_ARG_EOL) {
249
if (!search_arg_build(data, &listargs, subargs))
251
subargs = &(*subargs)->next;
474
i_array_init(&cmd->client->search_saved_uidset, 128);
481
static void wanted_fields_get(struct mailbox *box,
482
const enum mail_sort_type *sort_program,
483
enum mail_fetch_field *wanted_fields_r,
484
struct mailbox_header_lookup_ctx **headers_ctx_r)
486
const char *headers[2];
488
*wanted_fields_r = 0;
489
*headers_ctx_r = NULL;
491
if (sort_program == NULL)
494
headers[0] = headers[1] = NULL;
495
switch (sort_program[0] & MAIL_SORT_MASK) {
496
case MAIL_SORT_ARRIVAL:
497
*wanted_fields_r = MAIL_FETCH_RECEIVED_DATE;
503
*wanted_fields_r = MAIL_FETCH_DATE;
509
*wanted_fields_r = MAIL_FETCH_VIRTUAL_SIZE;
511
case MAIL_SORT_SUBJECT:
512
headers[0] = "Subject";
519
if (headers[0] != NULL)
520
*headers_ctx_r = mailbox_header_lookup_init(box, headers);
523
bool imap_search_start(struct imap_search_context *ctx,
524
struct mail_search_args *sargs,
525
const enum mail_sort_type *sort_program)
527
struct client_command_context *cmd = ctx->cmd;
528
enum mail_fetch_field wanted_fields;
529
struct mailbox_header_lookup_ctx *wanted_headers;
531
imap_search_args_check(ctx, sargs->args);
533
if (ctx->have_modseqs) {
534
ctx->return_options |= SEARCH_RETURN_MODSEQ;
535
client_enable(cmd->client, MAILBOX_FEATURE_CONDSTORE);
538
ctx->box = cmd->client->mailbox;
539
wanted_fields_get(ctx->box, sort_program,
540
&wanted_fields, &wanted_headers);
542
ctx->trans = mailbox_transaction_begin(ctx->box, 0);
544
ctx->search_ctx = mailbox_search_init(ctx->trans, sargs, sort_program);
545
ctx->mail = mail_alloc(ctx->trans, wanted_fields, wanted_headers);
546
ctx->sorting = sort_program != NULL;
547
(void)gettimeofday(&ctx->start_time, NULL);
548
i_array_init(&ctx->result, 128);
549
if ((ctx->return_options & SEARCH_RETURN_UPDATE) != 0)
550
imap_search_result_save(ctx);
552
cmd->func = cmd_search_more;
555
if (cmd_search_more(cmd))
258
i_assert(arg->type == IMAP_ARG_ATOM ||
259
arg->type == IMAP_ARG_STRING);
261
/* string argument - get the name and jump to next */
262
str = IMAP_ARG_STR(arg);
264
str = t_str_ucase(str);
268
if (strcmp(str, "ANSWERED") == 0)
269
return ARG_NEW_FLAGS(MAIL_ANSWERED);
270
else if (strcmp(str, "ALL") == 0)
271
return ARG_NEW_SINGLE(SEARCH_ALL);
274
if (strcmp(str, "BODY") == 0) {
276
if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
277
*IMAP_ARG_STR(*args) == '\0') {
279
return ARG_NEW_SINGLE(SEARCH_ALL);
281
return ARG_NEW_STR(SEARCH_BODY);
282
} else if (strcmp(str, "BEFORE") == 0) {
284
return ARG_NEW_DATE(SEARCH_BEFORE);
285
} else if (strcmp(str, "BCC") == 0) {
287
return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
291
if (strcmp(str, "CC") == 0) {
293
return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
297
if (strcmp(str, "DELETED") == 0)
298
return ARG_NEW_FLAGS(MAIL_DELETED);
299
else if (strcmp(str, "DRAFT") == 0)
300
return ARG_NEW_FLAGS(MAIL_DRAFT);
303
if (strcmp(str, "FLAGGED") == 0)
304
return ARG_NEW_FLAGS(MAIL_FLAGGED);
305
else if (strcmp(str, "FROM") == 0) {
307
return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
311
if (strcmp(str, "HEADER") == 0) {
312
/* <field-name> <string> */
315
if ((*args)->type == IMAP_ARG_EOL) {
316
data->error = "Missing parameter for HEADER";
319
if ((*args)->type != IMAP_ARG_ATOM &&
320
(*args)->type != IMAP_ARG_STRING) {
321
data->error = "Invalid parameter for HEADER";
325
key = t_str_ucase(IMAP_ARG_STR(*args));
327
return ARG_NEW_HEADER(SEARCH_HEADER, key);
331
if (strcmp(str, "KEYWORD") == 0) {
333
return arg_new_keyword(data, args, next_sarg);
337
if (strcmp(str, "LARGER") == 0) {
339
return ARG_NEW_SIZE(SEARCH_LARGER);
343
if (strcmp(str, "NOT") == 0) {
344
if (!search_arg_build(data, args, next_sarg))
346
(*next_sarg)->not = !(*next_sarg)->not;
348
} else if (strcmp(str, "NEW") == 0) {
349
/* NEW == (RECENT UNSEEN) */
350
*next_sarg = search_arg_new(data->pool, SEARCH_SUB);
352
subargs = &(*next_sarg)->value.subargs;
353
*subargs = search_arg_new(data->pool, SEARCH_FLAGS);
354
(*subargs)->value.flags = MAIL_RECENT;
355
(*subargs)->next = search_arg_new(data->pool,
357
(*subargs)->next->value.flags = MAIL_SEEN;
358
(*subargs)->next->not = TRUE;
363
if (strcmp(str, "OR") == 0) {
364
/* <search-key1> <search-key2> */
365
*next_sarg = search_arg_new(data->pool, SEARCH_OR);
367
subargs = &(*next_sarg)->value.subargs;
369
if (!search_arg_build(data, args, subargs))
372
subargs = &(*subargs)->next;
374
/* <key> OR <key> OR ... <key> - put them all
375
under one SEARCH_OR list. */
376
if ((*args)->type == IMAP_ARG_EOL)
379
if ((*args)->type != IMAP_ARG_ATOM ||
380
strcasecmp(IMAP_ARG_STR_NONULL(*args),
387
if (!search_arg_build(data, args, subargs))
390
} if (strcmp(str, "ON") == 0) {
392
return ARG_NEW_DATE(SEARCH_ON);
393
} if (strcmp(str, "OLD") == 0) {
394
/* OLD == NOT RECENT */
395
if (!ARG_NEW_FLAGS(MAIL_RECENT))
398
(*next_sarg)->not = TRUE;
403
if (strcmp(str, "RECENT") == 0)
404
return ARG_NEW_FLAGS(MAIL_RECENT);
407
if (strcmp(str, "SEEN") == 0)
408
return ARG_NEW_FLAGS(MAIL_SEEN);
409
else if (strcmp(str, "SUBJECT") == 0) {
411
return ARG_NEW_HEADER(SEARCH_HEADER_COMPRESS_LWSP, str);
412
} else if (strcmp(str, "SENTBEFORE") == 0) {
414
return ARG_NEW_DATE(SEARCH_SENTBEFORE);
415
} else if (strcmp(str, "SENTON") == 0) {
417
return ARG_NEW_DATE(SEARCH_SENTON);
418
} else if (strcmp(str, "SENTSINCE") == 0) {
420
return ARG_NEW_DATE(SEARCH_SENTSINCE);
421
} else if (strcmp(str, "SINCE") == 0) {
423
return ARG_NEW_DATE(SEARCH_SINCE);
424
} else if (strcmp(str, "SMALLER") == 0) {
426
return ARG_NEW_SIZE(SEARCH_SMALLER);
430
if (strcmp(str, "TEXT") == 0) {
432
if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
433
*IMAP_ARG_STR(*args) == '\0') {
435
return ARG_NEW_SINGLE(SEARCH_ALL);
437
return ARG_NEW_STR(SEARCH_TEXT);
438
} else if (strcmp(str, "TO") == 0) {
440
return ARG_NEW_HEADER(SEARCH_HEADER_ADDRESS, str);
444
if (strcmp(str, "UID") == 0) {
446
if (!ARG_NEW_STR(SEARCH_SEQSET))
449
return imap_uidset_parse(data->pool, data->box,
450
(*next_sarg)->value.str,
451
&(*next_sarg)->value.seqset,
453
} else if (strcmp(str, "UNANSWERED") == 0) {
454
if (!ARG_NEW_FLAGS(MAIL_ANSWERED))
456
(*next_sarg)->not = TRUE;
458
} else if (strcmp(str, "UNDELETED") == 0) {
459
if (!ARG_NEW_FLAGS(MAIL_DELETED))
461
(*next_sarg)->not = TRUE;
463
} else if (strcmp(str, "UNDRAFT") == 0) {
464
if (!ARG_NEW_FLAGS(MAIL_DRAFT))
466
(*next_sarg)->not = TRUE;
468
} else if (strcmp(str, "UNFLAGGED") == 0) {
469
if (!ARG_NEW_FLAGS(MAIL_FLAGGED))
471
(*next_sarg)->not = TRUE;
473
} else if (strcmp(str, "UNKEYWORD") == 0) {
475
if (!arg_new_keyword(data, args, next_sarg))
477
(*next_sarg)->not = TRUE;
479
} else if (strcmp(str, "UNSEEN") == 0) {
480
if (!ARG_NEW_FLAGS(MAIL_SEEN))
482
(*next_sarg)->not = TRUE;
487
if (strcmp(str, "X-BODY-FAST") == 0) {
489
if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
490
*IMAP_ARG_STR(*args) == '\0') {
492
return ARG_NEW_SINGLE(SEARCH_ALL);
494
return ARG_NEW_STR(SEARCH_BODY_FAST);
495
} else if (strcmp(str, "X-TEXT-FAST") == 0) {
497
if (IMAP_ARG_TYPE_IS_STRING((*args)->type) &&
498
*IMAP_ARG_STR(*args) == '\0') {
500
return ARG_NEW_SINGLE(SEARCH_ALL);
502
return ARG_NEW_STR(SEARCH_TEXT_FAST);
506
if (*str == '*' || (*str >= '0' && *str <= '9')) {
508
seqset = imap_messageset_parse(data->pool, str);
509
if (seqset == NULL) {
510
data->error = "Invalid messageset";
514
if (!ARG_NEW_SINGLE(SEARCH_SEQSET))
517
(*next_sarg)->value.seqset = seqset;
523
data->error = t_strconcat("Unknown argument ", str, NULL);
558
/* we may have moved onto syncing by now */
559
if (cmd->func == cmd_search_more)
560
ctx->to = timeout_add(0, cmd_search_more_callback, cmd);
527
struct mail_search_arg *
528
imap_search_args_build(pool_t pool, struct mailbox *box,
529
const struct imap_arg *args, const char **error_r)
531
struct search_build_data data;
532
struct mail_search_arg *first_sarg, **sargs;
540
/* get the first arg */
541
first_sarg = NULL; sargs = &first_sarg;
542
while (args->type != IMAP_ARG_EOL) {
543
if (!search_arg_build(&data, &args, sargs)) {
544
imap_search_args_free(box, first_sarg);
545
*error_r = data.error;
548
sargs = &(*sargs)->next;
551
if (first_sarg == NULL)
552
*error_r = "Missing search parameters";
557
msgset_is_valid(const struct mail_search_seqset *set, uint32_t messages_count)
559
/* when there are no messages, all messagesets are invalid.
560
if there's at least one message:
561
- * gives seq1 = seq2 = (uint32_t)-1
562
- n:* should work if n <= messages_count
563
- n:m or m should work if m <= messages_count
565
if (set == NULL || messages_count == 0)
568
for (; set != NULL; set = set->next) {
569
if ((set->seq1 > messages_count && set->seq1 != (uint32_t)-1) ||
570
(set->seq2 > messages_count && set->seq2 != (uint32_t)-1))
576
static int imap_search_get_msgset_arg(struct client_command_context *cmd,
577
const char *messageset,
578
struct mail_search_arg **arg_r,
579
const char **error_r)
581
struct mail_search_arg *arg;
583
arg = p_new(cmd->pool, struct mail_search_arg, 1);
584
arg->type = SEARCH_SEQSET;
585
arg->value.seqset = imap_messageset_parse(cmd->pool, messageset);
586
if (!msgset_is_valid(arg->value.seqset, cmd->client->messages_count)) {
587
*error_r = "Invalid messageset";
594
void imap_search_args_free(struct mailbox *box, struct mail_search_arg *args)
596
for (; args != NULL; args = args->next) {
597
if (args->type == SEARCH_KEYWORDS)
598
mailbox_keywords_free(box, &args->value.keywords);
599
else if (args->type == SEARCH_SUB || args->type == SEARCH_OR)
600
imap_search_args_free(box, args->value.subargs);
605
imap_search_get_uidset_arg(pool_t pool, struct mailbox *box, const char *uidset,
606
struct mail_search_arg **arg_r, const char **error_r)
608
struct mail_search_arg *arg;
610
arg = p_new(pool, struct mail_search_arg, 1);
611
arg->type = SEARCH_SEQSET;
613
return imap_uidset_parse(pool, box, uidset, &arg->value.seqset,
617
struct mail_search_arg *
618
imap_search_get_arg(struct client_command_context *cmd,
619
const char *set, bool uid)
621
struct mail_search_arg *search_arg = NULL;
626
ret = imap_search_get_msgset_arg(cmd, set, &search_arg, &error);
628
ret = imap_search_get_uidset_arg(cmd->pool,
629
cmd->client->mailbox, set,
630
&search_arg, &error);
633
client_send_command_error(cmd, error);
564
static int imap_search_deinit(struct imap_search_context *ctx)
568
mail_free(&ctx->mail);
569
if (mailbox_search_deinit(&ctx->search_ctx) < 0)
572
if (ret == 0 && !ctx->cmd->cancel)
573
imap_search_send_result(ctx);
576
if ((ctx->return_options & SEARCH_RETURN_SAVE) != 0)
577
array_clear(&ctx->cmd->client->search_saved_uidset);
580
(void)mailbox_transaction_commit(&ctx->trans);
583
timeout_remove(&ctx->to);
584
array_free(&ctx->result);
585
mail_search_args_deinit(ctx->sargs);
586
mail_search_args_unref(&ctx->sargs);
588
ctx->cmd->context = NULL;