1 /* Copyright (c) 2016-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-parser.h"
12 #include "mail-search-mime-register.h"
13 #include "mail-search-mime-build.h"
14 #include "mail-search-mime.h"
15
16 struct mail_search_mime_register {
17 ARRAY(struct mail_search_mime_register_arg) args;
18
19 bool args_sorted:1;
20 };
21
22 struct mail_search_mime_register *mail_search_mime_register = NULL;
23
24 static void
25 mail_search_register_add_default(void);
26
27 /*
28 * Register
29 */
30
31 static struct mail_search_mime_register *
mail_search_mime_register_init(void)32 mail_search_mime_register_init(void)
33 {
34 struct mail_search_mime_register *reg =
35 mail_search_mime_register;
36 if (reg == NULL) {
37 reg = i_new(struct mail_search_mime_register, 1);
38 i_array_init(®->args, 64);
39
40 mail_search_mime_register = reg;
41 mail_search_register_add_default();
42 }
43
44 return reg;
45 }
46
mail_search_mime_register_deinit(void)47 void mail_search_mime_register_deinit(void)
48 {
49 struct mail_search_mime_register *reg =
50 mail_search_mime_register;
51
52 mail_search_mime_register = NULL;
53 if (reg == NULL)
54 return;
55
56 array_free(®->args);
57 i_free(reg);
58 }
59
mail_search_mime_register_add(const struct mail_search_mime_register_arg * arg,unsigned int count)60 void mail_search_mime_register_add(
61 const struct mail_search_mime_register_arg *arg,
62 unsigned int count)
63 {
64 struct mail_search_mime_register *reg =
65 mail_search_mime_register_init();
66
67 array_append(®->args, arg, count);
68 reg->args_sorted = FALSE;
69 }
70
71 static int
mail_search_mime_register_arg_cmp(const struct mail_search_mime_register_arg * arg1,const struct mail_search_mime_register_arg * arg2)72 mail_search_mime_register_arg_cmp(
73 const struct mail_search_mime_register_arg *arg1,
74 const struct mail_search_mime_register_arg *arg2)
75 {
76 return strcmp(arg1->key, arg2->key);
77 }
78
79 const struct mail_search_mime_register_arg *
mail_search_mime_register_get(unsigned int * count_r)80 mail_search_mime_register_get(unsigned int *count_r)
81 {
82 struct mail_search_mime_register *reg =
83 mail_search_mime_register_init();
84
85 if (!reg->args_sorted) {
86 array_sort(®->args, mail_search_mime_register_arg_cmp);
87 reg->args_sorted = TRUE;
88 }
89
90 return array_get(®->args, count_r);
91 }
92
93 const struct mail_search_mime_register_arg *
mail_search_mime_register_find(const char * key)94 mail_search_mime_register_find(const char *key)
95 {
96 struct mail_search_mime_register_arg arg;
97 struct mail_search_mime_register *reg =
98 mail_search_mime_register_init();
99
100 if (!reg->args_sorted) {
101 array_sort(®->args, mail_search_mime_register_arg_cmp);
102 reg->args_sorted = TRUE;
103 }
104
105 arg.key = key;
106 return array_bsearch(®->args, &arg, mail_search_mime_register_arg_cmp);
107 }
108
109 /*
110 * Default MIMEPART args
111 */
112
113 static struct mail_search_mime_arg *
mail_search_mime_not(struct mail_search_mime_build_context * ctx)114 mail_search_mime_not(struct mail_search_mime_build_context *ctx)
115 {
116 struct mail_search_mime_arg *smarg;
117
118 if (mail_search_mime_build_key(ctx, ctx->parent, &smarg) < 0)
119 return NULL;
120
121 smarg->match_not = !smarg->match_not;
122 return smarg;
123 }
124
125 static struct mail_search_mime_arg *
mail_search_mime_or(struct mail_search_mime_build_context * ctx)126 mail_search_mime_or(struct mail_search_mime_build_context *ctx)
127 {
128 struct mail_search_mime_arg *smarg, **subargs;
129
130 /* <search-key1> <search-key2> */
131 smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_OR);
132
133 subargs = &smarg->value.subargs;
134 do {
135 if (mail_search_mime_build_key(ctx, smarg, subargs) < 0)
136 return NULL;
137 subargs = &(*subargs)->next;
138
139 /* <key> OR <key> OR ... <key> - put them all
140 under one SEARCH_MIME_OR list. */
141 } while (mail_search_parse_skip_next(ctx->ctx->parser, "OR"));
142
143 if (mail_search_mime_build_key(ctx, smarg, subargs) < 0)
144 return NULL;
145 return smarg;
146 }
147
148 #define CALLBACK_STR(_func, _type) \
149 static struct mail_search_mime_arg *\
150 mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
151 { \
152 return mail_search_mime_build_str(ctx, _type); \
153 }
154
155 static struct mail_search_mime_arg *
arg_new_date(struct mail_search_mime_build_context * ctx,enum mail_search_mime_arg_type type)156 arg_new_date(struct mail_search_mime_build_context *ctx,
157 enum mail_search_mime_arg_type type)
158 {
159 struct mail_search_mime_arg *smarg;
160 const char *value;
161
162 smarg = mail_search_mime_build_new(ctx, type);
163 if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
164 return NULL;
165 if (!imap_parse_date(value, &smarg->value.time)) {
166 ctx->ctx->_error = "Invalid search date parameter";
167 return NULL;
168 }
169 return smarg;
170 }
171
172 #define CALLBACK_DATE(_func, _type) \
173 static struct mail_search_mime_arg *\
174 mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
175 { \
176 return arg_new_date(ctx, _type); \
177 }
CALLBACK_DATE(sentbefore,SEARCH_MIME_SENTBEFORE)178 CALLBACK_DATE(sentbefore, SEARCH_MIME_SENTBEFORE)
179 CALLBACK_DATE(senton, SEARCH_MIME_SENTON)
180 CALLBACK_DATE(sentsince, SEARCH_MIME_SENTSINCE)
181
182 static struct mail_search_mime_arg *
183 mail_search_mime_size(struct mail_search_mime_build_context *ctx)
184 {
185 struct mail_search_mime_arg *smarg;
186 enum mail_search_mime_arg_type type;
187 const char *key, *value;
188 uoff_t size;
189
190 if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
191 ctx->ctx->_error = "Invalid MIMEPART SIZE key type";
192 return NULL;
193 }
194
195 key = t_str_ucase(key);
196 if (strcmp(key, "LARGER") == 0)
197 type = SEARCH_MIME_SIZE_LARGER;
198 else if (strcmp(key, "SMALLER") == 0)
199 type = SEARCH_MIME_SIZE_SMALLER;
200 else {
201 type = SEARCH_MIME_SIZE_EQUAL;
202 value = key;
203 }
204
205 if (type != SEARCH_MIME_SIZE_EQUAL &&
206 mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
207 ctx->ctx->_error = "Invalid MIMEPART SIZE value";
208 return NULL;
209 }
210
211 if (str_to_uoff(value, &size) < 0) {
212 ctx->ctx->_error = "Invalid MIMEPART SIZE value";
213 return NULL;
214 }
215
216 smarg = mail_search_mime_build_new(ctx, type);
217 smarg->value.size = size;
218 return smarg;
219 }
220
CALLBACK_STR(description,SEARCH_MIME_DESCRIPTION)221 CALLBACK_STR(description, SEARCH_MIME_DESCRIPTION)
222 CALLBACK_STR(encoding, SEARCH_MIME_ENCODING)
223 CALLBACK_STR(id, SEARCH_MIME_ID)
224 CALLBACK_STR(language, SEARCH_MIME_LANGUAGE)
225 CALLBACK_STR(location, SEARCH_MIME_LOCATION)
226 CALLBACK_STR(md5, SEARCH_MIME_MD5)
227
228 CALLBACK_STR(type, SEARCH_MIME_TYPE)
229 CALLBACK_STR(subtype, SEARCH_MIME_SUBTYPE)
230
231 CALLBACK_STR(bcc, SEARCH_MIME_BCC)
232 CALLBACK_STR(cc, SEARCH_MIME_CC)
233 CALLBACK_STR(from, SEARCH_MIME_FROM)
234 CALLBACK_STR(in_reply_to, SEARCH_MIME_IN_REPLY_TO)
235 CALLBACK_STR(message_id, SEARCH_MIME_MESSAGE_ID)
236 CALLBACK_STR(reply_to, SEARCH_MIME_REPLY_TO)
237 CALLBACK_STR(sender, SEARCH_MIME_SENDER)
238 CALLBACK_STR(subject, SEARCH_MIME_SUBJECT)
239 CALLBACK_STR(to, SEARCH_MIME_TO)
240
241 static struct mail_search_mime_arg *
242 arg_new_field(struct mail_search_mime_build_context *ctx,
243 enum mail_search_mime_arg_type type)
244 {
245 struct mail_search_mime_arg *smarg;
246 const char *field_name, *value;
247
248 /* <field-name> <string> */
249 if (mail_search_parse_string(ctx->ctx->parser, &field_name) < 0)
250 return NULL;
251 if (mail_search_build_get_utf8(ctx->ctx, field_name, &field_name) < 0)
252 return NULL;
253 if (mail_search_parse_string(ctx->ctx->parser, &value) < 0)
254 return NULL;
255 if (mail_search_build_get_utf8(ctx->ctx, value, &value) < 0)
256 return NULL;
257
258 smarg = mail_search_mime_build_new(ctx, type);
259 smarg->field_name = str_ucase(p_strdup(ctx->ctx->pool, field_name));
260 smarg->value.str = value;
261
262 return smarg;
263 }
264
265 static struct mail_search_mime_arg *
mail_search_mime_param(struct mail_search_mime_build_context * ctx)266 mail_search_mime_param(struct mail_search_mime_build_context *ctx)
267 {
268 return arg_new_field
269 (ctx, SEARCH_MIME_PARAM);
270 }
271
272 static struct mail_search_mime_arg *
mail_search_mime_header(struct mail_search_mime_build_context * ctx)273 mail_search_mime_header(struct mail_search_mime_build_context *ctx)
274 {
275 return arg_new_field
276 (ctx, SEARCH_MIME_HEADER);
277 }
278
279 static struct mail_search_mime_arg *
arg_new_body(struct mail_search_mime_build_context * ctx,enum mail_search_mime_arg_type type)280 arg_new_body(struct mail_search_mime_build_context *ctx,
281 enum mail_search_mime_arg_type type)
282 {
283 struct mail_search_mime_arg *smarg;
284
285 smarg = mail_search_mime_build_str(ctx, type);
286 if (smarg == NULL)
287 return NULL;
288
289 if (mail_search_build_get_utf8(ctx->ctx, smarg->value.str,
290 &smarg->value.str) < 0)
291 return NULL;
292 return smarg;
293 }
294
295 #define CALLBACK_BODY(_func, _type) \
296 static struct mail_search_mime_arg *\
297 mail_search_mime_##_func(struct mail_search_mime_build_context *ctx) \
298 { \
299 return arg_new_body(ctx, _type); \
300 }
CALLBACK_BODY(body,SEARCH_MIME_BODY)301 CALLBACK_BODY(body, SEARCH_MIME_BODY)
302 CALLBACK_BODY(text, SEARCH_MIME_TEXT)
303
304 static struct mail_search_mime_arg *
305 mail_search_mime_disposition(struct mail_search_mime_build_context *ctx)
306 {
307 struct mail_search_mime_arg *smarg;
308 const char *key, *value;
309
310 if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
311 ctx->ctx->_error = "Invalid MIMEPART DISPOSITION key type";
312 return NULL;
313 }
314
315 key = t_str_ucase(key);
316 if (strcmp(key, "TYPE") == 0) {
317 if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
318 ctx->ctx->_error = "Invalid MIMEPART DISPOSITION TYPE value";
319 return NULL;
320 }
321 smarg = mail_search_mime_build_new
322 (ctx, SEARCH_MIME_DISPOSITION_TYPE);
323 smarg->value.str = p_strdup(ctx->ctx->pool, value);
324 return smarg;
325 } else if (strcmp(key, "PARAM") == 0) {
326 return arg_new_field
327 (ctx, SEARCH_MIME_DISPOSITION_PARAM);
328 }
329
330 ctx->ctx->_error = "Invalid MIMEPART DISPOSITION key type";
331 return NULL;
332 }
333
334 static struct mail_search_mime_arg *
mail_search_mime_depth(struct mail_search_mime_build_context * ctx)335 mail_search_mime_depth(struct mail_search_mime_build_context *ctx)
336 {
337 struct mail_search_mime_arg *smarg;
338 enum mail_search_mime_arg_type type;
339 const char *key, *value;
340 unsigned int depth;
341
342 if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
343 ctx->ctx->_error = "Invalid MIMEPART DEPTH key";
344 return NULL;
345 }
346
347 key = t_str_ucase(key);
348 if (strcmp(key, "MIN") == 0)
349 type = SEARCH_MIME_DEPTH_MIN;
350 else if (strcmp(key, "MAX") == 0)
351 type = SEARCH_MIME_DEPTH_MAX;
352 else {
353 type = SEARCH_MIME_DEPTH_EQUAL;
354 value = key;
355 }
356
357 if (type != SEARCH_MIME_DEPTH_EQUAL &&
358 mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
359 ctx->ctx->_error = "Invalid MIMEPART DEPTH value";
360 return NULL;
361 }
362
363 if (str_to_uint(value, &depth) < 0) {
364 ctx->ctx->_error = "Invalid MIMEPART DEPTH level";
365 return NULL;
366 }
367
368 smarg = mail_search_mime_build_new(ctx, type);
369 smarg->value.number = depth;
370 return smarg;
371 }
372
373 static struct mail_search_mime_arg *
mail_search_mime_index(struct mail_search_mime_build_context * ctx)374 mail_search_mime_index(struct mail_search_mime_build_context *ctx)
375 {
376 struct mail_search_mime_arg *smarg;
377 const char *value;
378 unsigned int index;
379
380 if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
381 ctx->ctx->_error = "Invalid MIMEPART INDEX value";
382 return NULL;
383 }
384
385 if (str_to_uint(value, &index) < 0) {
386 ctx->ctx->_error = "Invalid MIMEPART INDEX number";
387 return NULL;
388 }
389
390 smarg = mail_search_mime_build_new
391 (ctx, SEARCH_MIME_INDEX);
392 smarg->value.number = index;
393 return smarg;
394 }
395
396 static struct mail_search_mime_arg *
mail_search_mime_filename(struct mail_search_mime_build_context * ctx)397 mail_search_mime_filename(struct mail_search_mime_build_context *ctx)
398 {
399 struct mail_search_mime_arg *smarg;
400 enum mail_search_mime_arg_type type;
401 const char *key, *value;
402
403 if (mail_search_parse_key(ctx->ctx->parser, &key) <= 0) {
404 ctx->ctx->_error = "Invalid MIMEPART FILENAME match type";
405 return NULL;
406 }
407
408 key = t_str_ucase(key);
409 if (strcmp(key, "IS") == 0)
410 type = SEARCH_MIME_FILENAME_IS;
411 else if (strcmp(key, "CONTAINS") == 0)
412 type = SEARCH_MIME_FILENAME_CONTAINS;
413 else if (strcmp(key, "BEGINS") == 0)
414 type = SEARCH_MIME_FILENAME_BEGINS;
415 else if (strcmp(key, "ENDS") == 0)
416 type = SEARCH_MIME_FILENAME_ENDS;
417 else {
418 ctx->ctx->_error = "Invalid MIMEPART FILENAME match type";
419 return NULL;
420 }
421
422 if (mail_search_parse_string(ctx->ctx->parser, &value) < 0) {
423 ctx->ctx->_error = "Invalid MIMEPART FILENAME string value";
424 return NULL;
425 }
426
427 if (mail_search_build_get_utf8(ctx->ctx, value, &value) < 0) {
428 ctx->ctx->_error = "Invalid MIMEPART FILENAME stromg value";
429 return NULL;
430 }
431
432 smarg = mail_search_mime_build_new(ctx, type);
433 smarg->value.str = value;
434 return smarg;
435 }
436
437 static struct mail_search_mime_arg *
mail_search_mime_parent(struct mail_search_mime_build_context * ctx)438 mail_search_mime_parent(struct mail_search_mime_build_context *ctx)
439 {
440 struct mail_search_mime_arg *smarg, *subargs;
441
442 smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_PARENT);
443 if (mail_search_mime_build_key(ctx, smarg, &subargs) < 0)
444 return NULL;
445 if (subargs == smarg)
446 smarg->value.subargs = NULL;
447 else if (subargs->type == SEARCH_MIME_SUB)
448 smarg->value.subargs = subargs->value.subargs;
449 else
450 smarg->value.subargs = subargs;
451 return smarg;
452 }
453
454 static struct mail_search_mime_arg *
mail_search_mime_child(struct mail_search_mime_build_context * ctx)455 mail_search_mime_child(struct mail_search_mime_build_context *ctx)
456 {
457 struct mail_search_mime_arg *smarg, *subargs;
458
459 smarg = mail_search_mime_build_new(ctx, SEARCH_MIME_CHILD);
460 if (mail_search_mime_build_key(ctx, smarg, &subargs) < 0)
461 return NULL;
462 if (subargs == smarg)
463 smarg->value.subargs = NULL;
464 else if (subargs->type == SEARCH_MIME_SUB)
465 smarg->value.subargs = subargs->value.subargs;
466 else
467 smarg->value.subargs = subargs;
468 return smarg;
469 }
470
471 static struct mail_search_mime_arg *
mail_search_mime_exists(struct mail_search_mime_build_context * ctx)472 mail_search_mime_exists(struct mail_search_mime_build_context *ctx)
473 {
474 if (ctx->parent == NULL ||
475 (ctx->parent->type != SEARCH_MIME_PARENT &&
476 ctx->parent->type != SEARCH_MIME_CHILD)) {
477 ctx->ctx->_error = "EXISTS key can only be used with PARENT or CHILD";
478 return NULL;
479 }
480 return ctx->parent;
481 }
482
483 static const struct mail_search_mime_register_arg
484 mime_register_args[] = {
485 /* argument set operations */
486 { "NOT", mail_search_mime_not },
487 { "OR", mail_search_mime_or },
488
489 /* dates */
490 { "SENTBEFORE", mail_search_mime_sentbefore },
491 { "SENTON", mail_search_mime_senton },
492 { "SENTSINCE", mail_search_mime_sentsince },
493
494 /* size */
495 { "SIZE", mail_search_mime_size },
496
497 /* part properties */
498 { "DESCRIPTION", mail_search_mime_description },
499 { "DISPOSITION", mail_search_mime_disposition },
500 { "ENCODING", mail_search_mime_encoding },
501 { "ID", mail_search_mime_id },
502 { "LANGUAGE", mail_search_mime_language },
503 { "LOCATION", mail_search_mime_location },
504 { "MD5", mail_search_mime_md5 },
505
506 /* content-type */
507 { "TYPE", mail_search_mime_type },
508 { "SUBTYPE", mail_search_mime_subtype },
509 { "PARAM", mail_search_mime_param },
510
511 /* headers */
512 { "HEADER", mail_search_mime_header },
513
514 /* message */
515 { "BCC", mail_search_mime_bcc },
516 { "CC", mail_search_mime_cc },
517 { "FROM", mail_search_mime_from },
518 { "IN-REPLY-TO", mail_search_mime_in_reply_to },
519 { "MESSAGE-ID", mail_search_mime_message_id },
520 { "REPLY-TO", mail_search_mime_reply_to },
521 { "SENDER", mail_search_mime_sender },
522 { "SUBJECT", mail_search_mime_subject },
523 { "TO", mail_search_mime_to },
524
525 /* body */
526 { "BODY", mail_search_mime_body },
527 { "TEXT", mail_search_mime_text },
528
529 /* position */
530 { "DEPTH", mail_search_mime_depth },
531 { "INDEX", mail_search_mime_index },
532
533 /* relations */
534 { "PARENT", mail_search_mime_parent },
535 { "CHILD", mail_search_mime_child },
536 { "EXISTS", mail_search_mime_exists },
537
538 /* filename */
539 { "FILENAME", mail_search_mime_filename },
540 };
541
542 static void
mail_search_register_add_default(void)543 mail_search_register_add_default(void)
544 {
545 mail_search_mime_register_add(mime_register_args,
546 N_ELEMENTS(mime_register_args));
547 }
548