1 /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "array.h"
6 #include "str.h"
7 #include "imap-date.h"
8 #include "imap-seqset.h"
9 #include "imap-utf7.h"
10 #include "imap-util.h"
11 #include "mail-search-register.h"
12 #include "mail-search-parser.h"
13 #include "mail-search-build.h"
14 #include "mail-search-build.h"
15 #include "mail-search-mime-build.h"
16 
17 struct mail_search_register *mail_search_register_imap;
18 
19 static struct mail_search_arg *
imap_search_fallback(struct mail_search_build_context * ctx,const char * key)20 imap_search_fallback(struct mail_search_build_context *ctx,
21 		     const char *key)
22 {
23 	struct mail_search_arg *sarg;
24 
25 	if (*key == '*' || (*key >= '0' && *key <= '9')) {
26 		/* <message-set> */
27 		sarg = mail_search_build_new(ctx, SEARCH_SEQSET);
28 		p_array_init(&sarg->value.seqset, ctx->pool, 16);
29 		if (imap_seq_set_parse(key, &sarg->value.seqset) < 0) {
30 			ctx->_error = "Invalid messageset";
31 			return NULL;
32 		}
33 		return sarg;
34 	}
35 	ctx->_error = p_strconcat(ctx->pool, "Unknown argument ", key, NULL);
36 	return NULL;
37 }
38 
39 static struct mail_search_arg *
imap_search_not(struct mail_search_build_context * ctx)40 imap_search_not(struct mail_search_build_context *ctx)
41 {
42 	struct mail_search_arg *sarg;
43 
44 	if (mail_search_build_key(ctx, ctx->parent, &sarg) < 0)
45 		return NULL;
46 
47 	sarg->match_not = !sarg->match_not;
48 	return sarg;
49 }
50 
51 static struct mail_search_arg *
imap_search_or(struct mail_search_build_context * ctx)52 imap_search_or(struct mail_search_build_context *ctx)
53 {
54 	struct mail_search_arg *sarg, **subargs;
55 
56 	/* <search-key1> <search-key2> */
57 	sarg = mail_search_build_new(ctx, SEARCH_OR);
58 
59 	subargs = &sarg->value.subargs;
60 	do {
61 		if (mail_search_build_key(ctx, sarg, subargs) < 0)
62 			return NULL;
63 		subargs = &(*subargs)->next;
64 
65 		/* <key> OR <key> OR ... <key> - put them all
66 		   under one SEARCH_OR list. */
67 	} while (mail_search_parse_skip_next(ctx->parser, "OR"));
68 
69 	if (mail_search_build_key(ctx, sarg, subargs) < 0)
70 		return NULL;
71 	return sarg;
72 }
73 
74 #define CALLBACK_STR(_func, _type) \
75 static struct mail_search_arg *\
76 imap_search_##_func(struct mail_search_build_context *ctx) \
77 { \
78 	return mail_search_build_str(ctx, _type); \
79 }
80 static struct mail_search_arg *
imap_search_all(struct mail_search_build_context * ctx)81 imap_search_all(struct mail_search_build_context *ctx)
82 {
83 	return mail_search_build_new(ctx, SEARCH_ALL);
84 }
85 
86 static struct mail_search_arg *
imap_search_uid(struct mail_search_build_context * ctx)87 imap_search_uid(struct mail_search_build_context *ctx)
88 {
89 	struct mail_search_arg *sarg;
90 
91 	/* <message set> */
92 	sarg = mail_search_build_str(ctx, SEARCH_UIDSET);
93 	if (sarg == NULL)
94 		return NULL;
95 
96 	p_array_init(&sarg->value.seqset, ctx->pool, 16);
97 	if (strcmp(sarg->value.str, "$") == 0) {
98 		/* SEARCHRES: delay initialization */
99 	} else {
100 		if (imap_seq_set_parse(sarg->value.str,
101 				       &sarg->value.seqset) < 0) {
102 			ctx->_error = "Invalid UID messageset";
103 			return NULL;
104 		}
105 	}
106 	return sarg;
107 }
108 
109 #define CALLBACK_FLAG(_func, _flag, _not) \
110 static struct mail_search_arg *\
111 imap_search_##_func(struct mail_search_build_context *ctx) \
112 { \
113 	struct mail_search_arg *sarg; \
114 	sarg = mail_search_build_new(ctx, SEARCH_FLAGS); \
115 	sarg->value.flags = _flag; \
116 	sarg->match_not = _not; \
117 	return sarg; \
118 }
CALLBACK_FLAG(answered,MAIL_ANSWERED,FALSE)119 CALLBACK_FLAG(answered, MAIL_ANSWERED, FALSE)
120 CALLBACK_FLAG(unanswered, MAIL_ANSWERED, TRUE)
121 CALLBACK_FLAG(deleted, MAIL_DELETED, FALSE)
122 CALLBACK_FLAG(undeleted, MAIL_DELETED, TRUE)
123 CALLBACK_FLAG(draft, MAIL_DRAFT, FALSE)
124 CALLBACK_FLAG(undraft, MAIL_DRAFT, TRUE)
125 CALLBACK_FLAG(flagged, MAIL_FLAGGED, FALSE)
126 CALLBACK_FLAG(unflagged, MAIL_FLAGGED, TRUE)
127 CALLBACK_FLAG(seen, MAIL_SEEN, FALSE)
128 CALLBACK_FLAG(unseen, MAIL_SEEN, TRUE)
129 CALLBACK_FLAG(recent, MAIL_RECENT, FALSE)
130 CALLBACK_FLAG(old, MAIL_RECENT, TRUE)
131 
132 static struct mail_search_arg *
133 imap_search_new(struct mail_search_build_context *ctx)
134 {
135 	struct mail_search_arg *sarg;
136 
137 	/* NEW == (RECENT UNSEEN) */
138 	sarg = mail_search_build_new(ctx, SEARCH_SUB);
139 	sarg->value.subargs = imap_search_recent(ctx);
140 	sarg->value.subargs->next = imap_search_unseen(ctx);
141 	return sarg;
142 }
143 
CALLBACK_STR(keyword,SEARCH_KEYWORDS)144 CALLBACK_STR(keyword, SEARCH_KEYWORDS)
145 
146 static struct mail_search_arg *
147 imap_search_unkeyword(struct mail_search_build_context *ctx)
148 {
149 	struct mail_search_arg *sarg;
150 
151 	sarg = imap_search_keyword(ctx);
152 	if (sarg != NULL)
153 		sarg->match_not = TRUE;
154 	return sarg;
155 }
156 
157 static struct mail_search_arg *
arg_new_date(struct mail_search_build_context * ctx,enum mail_search_arg_type type,enum mail_search_date_type date_type)158 arg_new_date(struct mail_search_build_context *ctx,
159 	     enum mail_search_arg_type type,
160 	     enum mail_search_date_type date_type)
161 {
162 	struct mail_search_arg *sarg;
163 	const char *value;
164 
165 	sarg = mail_search_build_new(ctx, type);
166 	if (mail_search_parse_string(ctx->parser, &value) < 0)
167 		return NULL;
168 	if (!imap_parse_date(value, &sarg->value.time)) {
169 		ctx->_error = "Invalid search date parameter";
170 		return NULL;
171 	}
172 	sarg->value.date_type = date_type;
173 	return sarg;
174 }
175 
176 #define CALLBACK_DATE(_func, _type, _date_type) \
177 static struct mail_search_arg *\
178 imap_search_##_func(struct mail_search_build_context *ctx) \
179 { \
180 	return arg_new_date(ctx, _type, _date_type); \
181 }
CALLBACK_DATE(before,SEARCH_BEFORE,MAIL_SEARCH_DATE_TYPE_RECEIVED)182 CALLBACK_DATE(before, SEARCH_BEFORE, MAIL_SEARCH_DATE_TYPE_RECEIVED)
183 CALLBACK_DATE(on, SEARCH_ON, MAIL_SEARCH_DATE_TYPE_RECEIVED)
184 CALLBACK_DATE(since, SEARCH_SINCE, MAIL_SEARCH_DATE_TYPE_RECEIVED)
185 
186 CALLBACK_DATE(sentbefore, SEARCH_BEFORE, MAIL_SEARCH_DATE_TYPE_SENT)
187 CALLBACK_DATE(senton, SEARCH_ON, MAIL_SEARCH_DATE_TYPE_SENT)
188 CALLBACK_DATE(sentsince, SEARCH_SINCE, MAIL_SEARCH_DATE_TYPE_SENT)
189 
190 CALLBACK_DATE(savedbefore, SEARCH_BEFORE, MAIL_SEARCH_DATE_TYPE_SAVED)
191 CALLBACK_DATE(savedon, SEARCH_ON, MAIL_SEARCH_DATE_TYPE_SAVED)
192 CALLBACK_DATE(savedsince, SEARCH_SINCE, MAIL_SEARCH_DATE_TYPE_SAVED)
193 
194 CALLBACK_DATE(x_savedbefore, SEARCH_BEFORE, MAIL_SEARCH_DATE_TYPE_SAVED)
195 CALLBACK_DATE(x_savedon, SEARCH_ON, MAIL_SEARCH_DATE_TYPE_SAVED)
196 CALLBACK_DATE(x_savedsince, SEARCH_SINCE, MAIL_SEARCH_DATE_TYPE_SAVED)
197 
198 static struct mail_search_arg *
199 imap_search_savedatesupported(struct mail_search_build_context *ctx)
200 {
201 	return mail_search_build_new(ctx, SEARCH_SAVEDATESUPPORTED);
202 }
203 
204 static struct mail_search_arg *
arg_new_size(struct mail_search_build_context * ctx,enum mail_search_arg_type type)205 arg_new_size(struct mail_search_build_context *ctx,
206 	     enum mail_search_arg_type type)
207 {
208 	struct mail_search_arg *sarg;
209 	const char *value;
210 
211 	sarg = mail_search_build_new(ctx, type);
212 	if (mail_search_parse_string(ctx->parser, &value) < 0)
213 		return NULL;
214 
215 	if (str_to_uoff(value, &sarg->value.size) < 0) {
216 		ctx->_error = "Invalid search size parameter";
217 		return NULL;
218 	}
219 	return sarg;
220 }
221 
222 static struct mail_search_arg *
imap_search_larger(struct mail_search_build_context * ctx)223 imap_search_larger(struct mail_search_build_context *ctx)
224 {
225 	return arg_new_size(ctx, SEARCH_LARGER);
226 }
227 
228 static struct mail_search_arg *
imap_search_smaller(struct mail_search_build_context * ctx)229 imap_search_smaller(struct mail_search_build_context *ctx)
230 {
231 	return arg_new_size(ctx, SEARCH_SMALLER);
232 }
233 
234 static struct mail_search_arg *
arg_new_header(struct mail_search_build_context * ctx,enum mail_search_arg_type type,const char * hdr_name)235 arg_new_header(struct mail_search_build_context *ctx,
236 	       enum mail_search_arg_type type, const char *hdr_name)
237 {
238 	struct mail_search_arg *sarg;
239 	const char *value;
240 
241 	sarg = mail_search_build_new(ctx, type);
242 	if (mail_search_parse_string(ctx->parser, &value) < 0)
243 		return NULL;
244 
245 	if (mail_search_build_get_utf8(ctx, value, &sarg->value.str) < 0)
246 		return NULL;
247 
248 	sarg->hdr_field_name = p_strdup(ctx->pool, hdr_name);
249 	return sarg;
250 }
251 
252 #define CALLBACK_HDR(_name, _type) \
253 static struct mail_search_arg *\
254 imap_search_##_name(struct mail_search_build_context *ctx) \
255 { \
256 	return arg_new_header(ctx, _type, #_name); \
257 }
CALLBACK_HDR(bcc,SEARCH_HEADER_ADDRESS)258 CALLBACK_HDR(bcc, SEARCH_HEADER_ADDRESS)
259 CALLBACK_HDR(cc, SEARCH_HEADER_ADDRESS)
260 CALLBACK_HDR(from, SEARCH_HEADER_ADDRESS)
261 CALLBACK_HDR(to, SEARCH_HEADER_ADDRESS)
262 CALLBACK_HDR(subject, SEARCH_HEADER_COMPRESS_LWSP)
263 
264 static struct mail_search_arg *
265 imap_search_header(struct mail_search_build_context *ctx)
266 {
267 	const char *hdr_name;
268 
269 	/* <hdr-name> <string> */
270 	if (mail_search_parse_string(ctx->parser, &hdr_name) < 0)
271 		return NULL;
272 	if (mail_search_build_get_utf8(ctx, hdr_name, &hdr_name) < 0)
273 		return NULL;
274 
275 	return arg_new_header(ctx, SEARCH_HEADER, t_str_ucase(hdr_name));
276 }
277 
278 static struct mail_search_arg *
arg_new_body(struct mail_search_build_context * ctx,enum mail_search_arg_type type)279 arg_new_body(struct mail_search_build_context *ctx,
280 	     enum mail_search_arg_type type)
281 {
282 	struct mail_search_arg *sarg;
283 
284 	sarg = mail_search_build_str(ctx, type);
285 	if (sarg == NULL)
286 		return NULL;
287 
288 	if (mail_search_build_get_utf8(ctx, sarg->value.str,
289 				       &sarg->value.str) < 0)
290 		return NULL;
291 	return sarg;
292 }
293 
294 #define CALLBACK_BODY(_func, _type) \
295 static struct mail_search_arg *\
296 imap_search_##_func(struct mail_search_build_context *ctx) \
297 { \
298 	return arg_new_body(ctx, _type); \
299 }
CALLBACK_BODY(body,SEARCH_BODY)300 CALLBACK_BODY(body, SEARCH_BODY)
301 CALLBACK_BODY(text, SEARCH_TEXT)
302 
303 static struct mail_search_arg *
304 arg_new_interval(struct mail_search_build_context *ctx,
305 		 enum mail_search_arg_type type)
306 {
307 	struct mail_search_arg *sarg;
308 	const char *value;
309 	uint32_t interval;
310 
311 	sarg = mail_search_build_new(ctx, type);
312 	if (mail_search_parse_string(ctx->parser, &value) < 0)
313 		return NULL;
314 
315 	if (str_to_uint32(value, &interval) < 0 || interval == 0) {
316 		ctx->_error = "Invalid search interval parameter";
317 		return NULL;
318 	}
319 	sarg->value.search_flags = MAIL_SEARCH_ARG_FLAG_UTC_TIMES;
320 	sarg->value.time = ioloop_time - interval;
321 	sarg->value.date_type = MAIL_SEARCH_DATE_TYPE_RECEIVED;
322 	return sarg;
323 }
324 
325 static struct mail_search_arg *
imap_search_older(struct mail_search_build_context * ctx)326 imap_search_older(struct mail_search_build_context *ctx)
327 {
328 	struct mail_search_arg *sarg;
329 
330 	sarg = arg_new_interval(ctx, SEARCH_BEFORE);
331 	if (sarg == NULL)
332 		return NULL;
333 
334 	/* we need to match also equal, but SEARCH_BEFORE compares with "<" */
335 	sarg->value.time++;
336 	return sarg;
337 }
338 
339 static struct mail_search_arg *
imap_search_younger(struct mail_search_build_context * ctx)340 imap_search_younger(struct mail_search_build_context *ctx)
341 {
342 	return arg_new_interval(ctx, SEARCH_SINCE);
343 }
344 
345 static int
arg_modseq_set_type(struct mail_search_build_context * ctx,struct mail_search_modseq * modseq,const char * name)346 arg_modseq_set_type(struct mail_search_build_context *ctx,
347 		    struct mail_search_modseq *modseq, const char *name)
348 {
349 	if (strcasecmp(name, "all") == 0)
350 		modseq->type = MAIL_SEARCH_MODSEQ_TYPE_ANY;
351 	else if (strcasecmp(name, "priv") == 0)
352 		modseq->type = MAIL_SEARCH_MODSEQ_TYPE_PRIVATE;
353 	else if (strcasecmp(name, "shared") == 0)
354 		modseq->type = MAIL_SEARCH_MODSEQ_TYPE_SHARED;
355 	else {
356 		ctx->_error = "Invalid MODSEQ type";
357 		return -1;
358 	}
359 	return 0;
360 }
361 
362 static int
arg_modseq_set_ext(struct mail_search_build_context * ctx,struct mail_search_arg * sarg,const char * name)363 arg_modseq_set_ext(struct mail_search_build_context *ctx,
364 		   struct mail_search_arg *sarg, const char *name)
365 {
366 	const char *value;
367 
368 	name = t_str_lcase(name);
369 	if (!str_begins(name, "/flags/"))
370 		return 0;
371 	name += 7;
372 
373 	/* set name */
374 	if (*name == '\\') {
375 		/* system flag */
376 		sarg->value.flags = imap_parse_system_flag(name);
377 		if (sarg->value.flags == 0 ||
378 		    sarg->value.flags == MAIL_RECENT) {
379 			ctx->_error = "Invalid MODSEQ system flag";
380 			return -1;
381 		}
382 	} else {
383 		sarg->value.str = p_strdup(ctx->pool, name);
384 	}
385 
386 	/* set type */
387 	if (mail_search_parse_string(ctx->parser, &value) < 0)
388 		return -1;
389 	if (arg_modseq_set_type(ctx, sarg->value.modseq, value) < 0)
390 		return -1;
391 	return 1;
392 }
393 
394 static struct mail_search_arg *
imap_search_modseq(struct mail_search_build_context * ctx)395 imap_search_modseq(struct mail_search_build_context *ctx)
396 {
397 	struct mail_search_arg *sarg;
398 	const char *value;
399 	int ret;
400 
401 	/* [<name> <type>] <modseq> */
402 	sarg = mail_search_build_new(ctx, SEARCH_MODSEQ);
403 	sarg->value.modseq = p_new(ctx->pool, struct mail_search_modseq, 1);
404 
405 	if (mail_search_parse_string(ctx->parser, &value) < 0)
406 		return NULL;
407 
408 	if ((ret = arg_modseq_set_ext(ctx, sarg, value)) < 0)
409 		return NULL;
410 	if (ret > 0) {
411 		/* extension data used */
412 		if (mail_search_parse_string(ctx->parser, &value) < 0)
413 			return NULL;
414 	}
415 
416 	if (str_to_uint64(value, &sarg->value.modseq->modseq) < 0) {
417 		ctx->_error = "Invalid MODSEQ value";
418 		return NULL;
419 	}
420 	return sarg;
421 }
422 
423 static struct mail_search_arg *
imap_search_last_result(struct mail_search_build_context * ctx)424 imap_search_last_result(struct mail_search_build_context *ctx)
425 {
426 	struct mail_search_arg *sarg;
427 
428 	/* SEARCHRES: delay initialization */
429 	sarg = mail_search_build_new(ctx, SEARCH_UIDSET);
430 	sarg->value.str = "$";
431 	p_array_init(&sarg->value.seqset, ctx->pool, 16);
432 	return sarg;
433 }
434 
mail_search_arg_set_fuzzy(struct mail_search_arg * sarg)435 static void mail_search_arg_set_fuzzy(struct mail_search_arg *sarg)
436 {
437 	for (; sarg != NULL; sarg = sarg->next) {
438 		sarg->fuzzy = TRUE;
439 		switch (sarg->type) {
440 		case SEARCH_OR:
441 		case SEARCH_SUB:
442 		case SEARCH_INTHREAD:
443 			mail_search_arg_set_fuzzy(sarg->value.subargs);
444 			break;
445 		default:
446 			break;
447 		}
448 	}
449 }
450 
451 static struct mail_search_arg *
imap_search_fuzzy(struct mail_search_build_context * ctx)452 imap_search_fuzzy(struct mail_search_build_context *ctx)
453 {
454 	struct mail_search_arg *sarg;
455 
456 	if (mail_search_build_key(ctx, ctx->parent, &sarg) < 0)
457 		return NULL;
458 	i_assert(sarg->next == NULL);
459 
460 	mail_search_arg_set_fuzzy(sarg);
461 	return sarg;
462 }
463 
464 static struct mail_search_arg *
imap_search_mimepart(struct mail_search_build_context * ctx)465 imap_search_mimepart(struct mail_search_build_context *ctx)
466 {
467 	struct mail_search_arg *sarg;
468 
469 	sarg = mail_search_build_new(ctx, SEARCH_MIMEPART);
470 	if (mail_search_mime_build(ctx, &sarg->value.mime_part) < 0)
471 		return NULL;
472 	return sarg;
473 }
474 
475 static struct mail_search_arg *
imap_search_inthread(struct mail_search_build_context * ctx)476 imap_search_inthread(struct mail_search_build_context *ctx)
477 {
478 	struct mail_search_arg *sarg;
479 
480 	/* <algorithm> <search key> */
481 	enum mail_thread_type thread_type;
482 	const char *algorithm;
483 
484 	if (mail_search_parse_string(ctx->parser, &algorithm) < 0)
485 		return NULL;
486 	if (!mail_thread_type_parse(algorithm, &thread_type)) {
487 		ctx->_error = "Unknown thread algorithm";
488 		return NULL;
489 	}
490 
491 	sarg = mail_search_build_new(ctx, SEARCH_INTHREAD);
492 	sarg->value.thread_type = thread_type;
493 	if (mail_search_build_key(ctx, sarg, &sarg->value.subargs) < 0)
494 		return NULL;
495 	return sarg;
496 }
497 
CALLBACK_STR(x_guid,SEARCH_GUID)498 CALLBACK_STR(x_guid, SEARCH_GUID)
499 
500 static struct mail_search_arg *
501 imap_search_x_mailbox(struct mail_search_build_context *ctx)
502 {
503 	struct mail_search_arg *sarg;
504 	string_t *utf8_name;
505 
506 	sarg = mail_search_build_str(ctx, SEARCH_MAILBOX_GLOB);
507 	if (sarg == NULL)
508 		return NULL;
509 
510 	utf8_name = t_str_new(strlen(sarg->value.str));
511 	if (imap_utf7_to_utf8(sarg->value.str, utf8_name) < 0) {
512 		ctx->_error = "X-MAILBOX name not mUTF-7";
513 		return NULL;
514 	}
515 	sarg->value.str = p_strdup(ctx->pool, str_c(utf8_name));
516 	return sarg;
517 }
518 
519 static struct mail_search_arg *
imap_search_x_real_uid(struct mail_search_build_context * ctx)520 imap_search_x_real_uid(struct mail_search_build_context *ctx)
521 {
522 	struct mail_search_arg *sarg;
523 
524 	/* <message set> */
525 	sarg = mail_search_build_str(ctx, SEARCH_REAL_UID);
526 	if (sarg == NULL)
527 		return NULL;
528 
529 	p_array_init(&sarg->value.seqset, ctx->pool, 16);
530 	if (imap_seq_set_parse(sarg->value.str,
531 			       &sarg->value.seqset) < 0) {
532 		ctx->_error = "Invalid X-REAL-UID messageset";
533 		return NULL;
534 	}
535 	return sarg;
536 }
537 
538 static const struct mail_search_register_arg imap_register_args[] = {
539 	/* argument set operations */
540 	{ "NOT", imap_search_not },
541 	{ "OR", imap_search_or },
542 
543 	/* message sets */
544 	{ "ALL", imap_search_all },
545 	{ "UID", imap_search_uid },
546 
547 	/* flags */
548 	{ "ANSWERED", imap_search_answered },
549 	{ "UNANSWERED", imap_search_unanswered },
550 	{ "DELETED", imap_search_deleted },
551 	{ "UNDELETED", imap_search_undeleted },
552 	{ "DRAFT", imap_search_draft },
553 	{ "UNDRAFT", imap_search_undraft },
554 	{ "FLAGGED", imap_search_flagged },
555 	{ "UNFLAGGED", imap_search_unflagged },
556 	{ "SEEN", imap_search_seen },
557 	{ "UNSEEN", imap_search_unseen },
558 	{ "RECENT", imap_search_recent },
559 	{ "OLD", imap_search_old },
560 	{ "NEW", imap_search_new },
561 
562 	/* keywords */
563 	{ "KEYWORD", imap_search_keyword },
564 	{ "UNKEYWORD", imap_search_unkeyword },
565 
566 	/* dates */
567 	{ "BEFORE", imap_search_before },
568 	{ "ON", imap_search_on },
569 	{ "SINCE", imap_search_since },
570 	{ "SENTBEFORE", imap_search_sentbefore },
571 	{ "SENTON", imap_search_senton },
572 	{ "SENTSINCE", imap_search_sentsince },
573 	{ "SAVEDBEFORE", imap_search_savedbefore },
574 	{ "SAVEDON", imap_search_savedon },
575 	{ "SAVEDSINCE", imap_search_savedsince },
576 	{ "SAVEDATESUPPORTED", imap_search_savedatesupported },
577 	/* FIXME: remove these in v2.4: */
578 	{ "X-SAVEDBEFORE", imap_search_x_savedbefore },
579 	{ "X-SAVEDON", imap_search_x_savedon },
580 	{ "X-SAVEDSINCE", imap_search_x_savedsince },
581 
582 	/* sizes */
583 	{ "LARGER", imap_search_larger },
584 	{ "SMALLER", imap_search_smaller },
585 
586 	/* headers */
587 	{ "BCC", imap_search_bcc },
588 	{ "CC", imap_search_cc },
589 	{ "FROM", imap_search_from },
590 	{ "TO", imap_search_to },
591 	{ "SUBJECT", imap_search_subject },
592 	{ "HEADER", imap_search_header },
593 
594 	/* body */
595 	{ "BODY", imap_search_body },
596 	{ "TEXT", imap_search_text },
597 
598 	/* WITHIN extension: */
599 	{ "OLDER", imap_search_older },
600 	{ "YOUNGER", imap_search_younger },
601 
602 	/* CONDSTORE extension: */
603 	{ "MODSEQ", imap_search_modseq },
604 
605 	/* SEARCHRES extension: */
606 	{ "$", imap_search_last_result },
607 
608 	/* FUZZY extension: */
609 	{ "FUZZY", imap_search_fuzzy },
610 
611 	/* SEARCH=MIMEPART extension: */
612 	{ "MIMEPART", imap_search_mimepart },
613 
614 	/* Other Dovecot extensions: */
615 	{ "INTHREAD", imap_search_inthread },
616 	{ "X-GUID", imap_search_x_guid },
617 	{ "X-MAILBOX", imap_search_x_mailbox },
618 	{ "X-REAL-UID", imap_search_x_real_uid }
619 };
620 
mail_search_register_init_imap(void)621 static struct mail_search_register *mail_search_register_init_imap(void)
622 {
623 	struct mail_search_register *reg;
624 
625 	reg = mail_search_register_init();
626 	mail_search_register_add(reg, imap_register_args,
627 				 N_ELEMENTS(imap_register_args));
628 	mail_search_register_fallback(reg, imap_search_fallback);
629 	return reg;
630 }
631 
632 struct mail_search_register *
mail_search_register_get_imap(void)633 mail_search_register_get_imap(void)
634 {
635 	if (mail_search_register_imap == NULL)
636 		mail_search_register_imap = mail_search_register_init_imap();
637 	return mail_search_register_imap;
638 }
639