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