/src/dovecot/src/lib-storage/index/index-search.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "ioloop.h" |
5 | | #include "array.h" |
6 | | #include "istream.h" |
7 | | #include "utc-offset.h" |
8 | | #include "str.h" |
9 | | #include "time-util.h" |
10 | | #include "unichar.h" |
11 | | #include "imap-match.h" |
12 | | #include "master-service.h" |
13 | | #include "message-address.h" |
14 | | #include "message-date.h" |
15 | | #include "message-search.h" |
16 | | #include "message-parser.h" |
17 | | #include "mail-index-modseq.h" |
18 | | #include "index-storage.h" |
19 | | #include "index-mail.h" |
20 | | #include "index-sort.h" |
21 | | #include "mail-search.h" |
22 | | #include "mailbox-search-result-private.h" |
23 | | #include "mailbox-recent-flags.h" |
24 | | #include "index-search-private.h" |
25 | | |
26 | | #include <ctype.h> |
27 | | |
28 | 0 | #define SEARCH_COST_DENTRY 3ULL |
29 | 0 | #define SEARCH_COST_ATTR 1ULL |
30 | 0 | #define SEARCH_COST_FILES_READ 25ULL |
31 | 0 | #define SEARCH_COST_KBYTE 15ULL |
32 | 0 | #define SEARCH_COST_CACHE 1ULL |
33 | | |
34 | 0 | #define SEARCH_MIN_NONBLOCK_USECS 200000 |
35 | 0 | #define SEARCH_MAX_NONBLOCK_USECS 250000 |
36 | 0 | #define SEARCH_INITIAL_MAX_COST 30000 |
37 | 0 | #define SEARCH_RECALC_MIN_USECS 50000 |
38 | | |
39 | | /* If interrupt signal is received and search doesn't finish in this many |
40 | | milliseconds, fail the search with MAIL_ERRSTR_INTERRUPTED. */ |
41 | 0 | #define SEARCH_INTERRUPT_DELAY_MSECS 2000 |
42 | | |
43 | | struct search_header_context { |
44 | | struct index_search_context *index_ctx; |
45 | | struct index_mail *imail; |
46 | | struct mail_search_arg *args; |
47 | | |
48 | | struct message_block decoded_block; |
49 | | bool decoded_block_set; |
50 | | |
51 | | struct message_header_line *hdr; |
52 | | |
53 | | bool parse_headers:1; |
54 | | bool custom_header:1; |
55 | | bool threading:1; |
56 | | }; |
57 | | |
58 | | struct search_body_context { |
59 | | struct index_search_context *index_ctx; |
60 | | struct istream *input; |
61 | | struct message_part *part; |
62 | | }; |
63 | | |
64 | | static void search_parse_msgset_args(unsigned int messages_count, |
65 | | struct mail_search_arg *args, |
66 | | uint32_t *seq1_r, uint32_t *seq2_r); |
67 | | |
68 | | static void ATTR_NULL(2) |
69 | | search_none(struct mail_search_arg *arg ATTR_UNUSED, void *ctx ATTR_UNUSED) |
70 | 0 | { |
71 | 0 | } |
72 | | |
73 | | static void search_set_failed(struct index_search_context *ctx) |
74 | 0 | { |
75 | 0 | if (ctx->failed) |
76 | 0 | return; |
77 | | |
78 | | /* remember the first failure */ |
79 | 0 | mail_storage_last_error_push(ctx->box->storage); |
80 | 0 | ctx->failed = TRUE; |
81 | 0 | } |
82 | | |
83 | | static void search_cur_mail_failed(struct index_search_context *ctx) |
84 | 0 | { |
85 | 0 | switch (mailbox_get_last_mail_error(ctx->cur_mail->box)) { |
86 | 0 | case MAIL_ERROR_EXPUNGED: |
87 | 0 | ctx->mail_ctx.seen_lost_data = TRUE; |
88 | 0 | break; |
89 | 0 | case MAIL_ERROR_LOOKUP_ABORTED: |
90 | | /* expected failure */ |
91 | 0 | break; |
92 | 0 | default: |
93 | 0 | search_set_failed(ctx); |
94 | 0 | break; |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | | static void search_init_arg(struct mail_search_arg *arg, |
99 | | struct index_search_context *ctx) |
100 | 0 | { |
101 | 0 | struct mailbox_metadata metadata; |
102 | 0 | bool match; |
103 | |
|
104 | 0 | switch (arg->type) { |
105 | 0 | case SEARCH_SEQSET: |
106 | 0 | ctx->have_seqsets = TRUE; |
107 | 0 | break; |
108 | 0 | case SEARCH_UIDSET: |
109 | 0 | case SEARCH_INTHREAD: |
110 | 0 | case SEARCH_FLAGS: |
111 | 0 | case SEARCH_KEYWORDS: |
112 | 0 | case SEARCH_MODSEQ: |
113 | 0 | if (arg->type == SEARCH_MODSEQ) |
114 | 0 | mail_index_modseq_enable(ctx->box->index); |
115 | 0 | ctx->have_index_args = TRUE; |
116 | 0 | break; |
117 | 0 | case SEARCH_MAILBOX_GUID: |
118 | 0 | if (mailbox_get_metadata(ctx->box, MAILBOX_METADATA_GUID, |
119 | 0 | &metadata) < 0) { |
120 | | /* result will be unknown */ |
121 | 0 | break; |
122 | 0 | } |
123 | | |
124 | 0 | match = strcmp(guid_128_to_string(metadata.guid), |
125 | 0 | arg->value.str) == 0; |
126 | 0 | if (match != arg->match_not) |
127 | 0 | arg->match_always = TRUE; |
128 | 0 | else { |
129 | 0 | arg->nonmatch_always = TRUE; |
130 | 0 | ctx->have_nonmatch_always = TRUE; |
131 | 0 | } |
132 | 0 | break; |
133 | 0 | case SEARCH_MAILBOX: |
134 | 0 | case SEARCH_MAILBOX_GLOB: |
135 | 0 | ctx->have_mailbox_args = TRUE; |
136 | 0 | break; |
137 | 0 | case SEARCH_ALL: |
138 | 0 | if (!arg->match_not) |
139 | 0 | arg->match_always = TRUE; |
140 | 0 | else { |
141 | 0 | arg->nonmatch_always = TRUE; |
142 | 0 | ctx->have_nonmatch_always = TRUE; |
143 | 0 | } |
144 | 0 | break; |
145 | 0 | default: |
146 | 0 | break; |
147 | 0 | } |
148 | 0 | } |
149 | | |
150 | | static void search_seqset_arg(struct mail_search_arg *arg, |
151 | | struct index_search_context *ctx) |
152 | 0 | { |
153 | 0 | if (arg->type == SEARCH_SEQSET) { |
154 | 0 | if (seq_range_exists(&arg->value.seqset, ctx->mail_ctx.seq)) |
155 | 0 | ARG_SET_RESULT(arg, 1); |
156 | 0 | else |
157 | 0 | ARG_SET_RESULT(arg, 0); |
158 | 0 | } |
159 | 0 | } |
160 | | |
161 | | static int search_arg_match_keywords(struct index_search_context *ctx, |
162 | | struct mail_search_arg *arg) |
163 | 0 | { |
164 | 0 | ARRAY_TYPE(keyword_indexes) keyword_indexes_arr; |
165 | 0 | const struct mail_keywords *search_kws = arg->initialized.keywords; |
166 | 0 | const unsigned int *keyword_indexes; |
167 | 0 | unsigned int i, j, count; |
168 | |
|
169 | 0 | i_assert(search_kws != NULL); |
170 | 0 | if (search_kws->count == 0) { |
171 | | /* invalid keyword - never matches */ |
172 | 0 | return 0; |
173 | 0 | } |
174 | | |
175 | 0 | t_array_init(&keyword_indexes_arr, 128); |
176 | 0 | mail_index_lookup_keywords(ctx->view, ctx->mail_ctx.seq, |
177 | 0 | &keyword_indexes_arr); |
178 | 0 | keyword_indexes = array_get(&keyword_indexes_arr, &count); |
179 | | |
180 | | /* there probably aren't many keywords, so O(n*m) for now */ |
181 | 0 | for (i = 0; i < search_kws->count; i++) { |
182 | 0 | for (j = 0; j < count; j++) { |
183 | 0 | if (search_kws->idx[i] == keyword_indexes[j]) |
184 | 0 | break; |
185 | 0 | } |
186 | 0 | if (j == count) |
187 | 0 | return 0; |
188 | 0 | } |
189 | 0 | return 1; |
190 | 0 | } |
191 | | |
192 | | static bool |
193 | | index_search_get_pvt(struct index_search_context *ctx, uint32_t uid) |
194 | 0 | { |
195 | 0 | index_transaction_init_pvt(ctx->mail_ctx.transaction); |
196 | |
|
197 | 0 | if (ctx->pvt_uid == uid) |
198 | 0 | return ctx->pvt_seq != 0; |
199 | 0 | ctx->pvt_uid = uid; |
200 | 0 | return mail_index_lookup_seq(ctx->mail_ctx.transaction->view_pvt, |
201 | 0 | uid, &ctx->pvt_seq); |
202 | 0 | } |
203 | | |
204 | | /* Returns >0 = matched, 0 = not matched, -1 = unknown */ |
205 | | static int search_arg_match_index(struct index_search_context *ctx, |
206 | | struct mail_search_arg *arg, |
207 | | const struct mail_index_record *rec) |
208 | 0 | { |
209 | 0 | enum mail_flags flags, pvt_flags_mask; |
210 | 0 | uint64_t modseq; |
211 | 0 | int ret; |
212 | |
|
213 | 0 | switch (arg->type) { |
214 | 0 | case SEARCH_UIDSET: |
215 | 0 | case SEARCH_INTHREAD: |
216 | 0 | return seq_range_exists(&arg->value.seqset, rec->uid) ? 1 : 0; |
217 | 0 | case SEARCH_FLAGS: |
218 | | /* recent flag shouldn't be set, but indexes from v1.0.x |
219 | | may contain it. */ |
220 | 0 | flags = rec->flags & ENUM_NEGATE(MAIL_RECENT); |
221 | 0 | if ((arg->value.flags & MAIL_RECENT) != 0 && |
222 | 0 | mailbox_recent_flags_have_uid(ctx->box, rec->uid)) |
223 | 0 | flags |= MAIL_RECENT; |
224 | 0 | if (ctx->box->view_pvt == NULL) { |
225 | | /* no private view (set by view syncing) -> |
226 | | no private flags */ |
227 | 0 | } else { |
228 | 0 | pvt_flags_mask = mailbox_get_private_flags_mask(ctx->box); |
229 | 0 | flags &= ENUM_NEGATE(pvt_flags_mask); |
230 | 0 | if (index_search_get_pvt(ctx, rec->uid)) { |
231 | 0 | rec = mail_index_lookup(ctx->mail_ctx.transaction->view_pvt, |
232 | 0 | ctx->pvt_seq); |
233 | 0 | flags |= rec->flags & pvt_flags_mask; |
234 | 0 | } |
235 | 0 | } |
236 | 0 | return (flags & arg->value.flags) == arg->value.flags ? 1 : 0; |
237 | 0 | case SEARCH_KEYWORDS: |
238 | 0 | T_BEGIN { |
239 | 0 | ret = search_arg_match_keywords(ctx, arg); |
240 | 0 | } T_END; |
241 | 0 | return ret; |
242 | 0 | case SEARCH_MODSEQ: { |
243 | 0 | modseq = mail_index_modseq_lookup(ctx->view, ctx->mail_ctx.seq); |
244 | 0 | return modseq >= arg->value.modseq->modseq ? 1 : 0; |
245 | 0 | } |
246 | 0 | default: |
247 | 0 | return -1; |
248 | 0 | } |
249 | 0 | } |
250 | | |
251 | | static void search_index_arg(struct mail_search_arg *arg, |
252 | | struct index_search_context *ctx) |
253 | 0 | { |
254 | 0 | const struct mail_index_record *rec; |
255 | |
|
256 | 0 | rec = mail_index_lookup(ctx->view, ctx->mail_ctx.seq); |
257 | 0 | switch (search_arg_match_index(ctx, arg, rec)) { |
258 | 0 | case -1: |
259 | | /* unknown */ |
260 | 0 | break; |
261 | 0 | case 0: |
262 | 0 | ARG_SET_RESULT(arg, 0); |
263 | 0 | break; |
264 | 0 | default: |
265 | 0 | ARG_SET_RESULT(arg, 1); |
266 | 0 | break; |
267 | 0 | } |
268 | 0 | } |
269 | | |
270 | | /* Returns >0 = matched, 0 = not matched, -1 = unknown */ |
271 | | static int search_arg_match_mailbox(struct index_search_context *ctx, |
272 | | struct mail_search_arg *arg) |
273 | 0 | { |
274 | 0 | struct mailbox *box = ctx->cur_mail->box; |
275 | 0 | const char *str; |
276 | |
|
277 | 0 | switch (arg->type) { |
278 | 0 | case SEARCH_MAILBOX: |
279 | | /* first try to match the mailbox name itself. this is |
280 | | important when using "mailbox virtual/foo" parameter foin |
281 | | doveadm's search query, otherwise we can never fetch |
282 | | anything with doveadm from virtual mailboxes because the |
283 | | mailbox parameter is compared to the mail's backend |
284 | | mailbox. */ |
285 | 0 | if (strcmp(box->vname, arg->value.str) == 0) |
286 | 0 | return 1; |
287 | 0 | if (mail_get_special(ctx->cur_mail, MAIL_FETCH_MAILBOX_NAME, |
288 | 0 | &str) < 0) { |
289 | 0 | search_cur_mail_failed(ctx); |
290 | 0 | return -1; |
291 | 0 | } |
292 | | |
293 | 0 | if (strcasecmp(str, "INBOX") == 0) |
294 | 0 | return strcasecmp(arg->value.str, "INBOX") == 0 ? 1 : 0; |
295 | 0 | return strcmp(str, arg->value.str) == 0 ? 1 : 0; |
296 | 0 | case SEARCH_MAILBOX_GLOB: |
297 | 0 | if (imap_match(arg->initialized.mailbox_glob, box->vname) == IMAP_MATCH_YES) |
298 | 0 | return 1; |
299 | 0 | if (mail_get_special(ctx->cur_mail, MAIL_FETCH_MAILBOX_NAME, |
300 | 0 | &str) < 0) { |
301 | 0 | search_cur_mail_failed(ctx); |
302 | 0 | return -1; |
303 | 0 | } |
304 | 0 | return imap_match(arg->initialized.mailbox_glob, str) == IMAP_MATCH_YES ? 1 : 0; |
305 | 0 | default: |
306 | 0 | return -1; |
307 | 0 | } |
308 | 0 | } |
309 | | |
310 | | static void search_mailbox_arg(struct mail_search_arg *arg, |
311 | | struct index_search_context *ctx) |
312 | 0 | { |
313 | 0 | switch (search_arg_match_mailbox(ctx, arg)) { |
314 | 0 | case -1: |
315 | | /* unknown */ |
316 | 0 | break; |
317 | 0 | case 0: |
318 | 0 | ARG_SET_RESULT(arg, 0); |
319 | 0 | break; |
320 | 0 | default: |
321 | 0 | ARG_SET_RESULT(arg, 1); |
322 | 0 | break; |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | | /* Returns >0 = matched, 0 = not matched, -1 = unknown */ |
327 | | static int search_arg_match_cached(struct index_search_context *ctx, |
328 | | struct mail_search_arg *arg) |
329 | 0 | { |
330 | 0 | const char *str; |
331 | 0 | struct tm *tm; |
332 | 0 | uoff_t virtual_size; |
333 | 0 | time_t date; |
334 | 0 | int tz_offset; |
335 | 0 | bool have_tz_offset; |
336 | 0 | int ret; |
337 | |
|
338 | 0 | switch (arg->type) { |
339 | | /* internal dates */ |
340 | 0 | case SEARCH_BEFORE: |
341 | 0 | case SEARCH_ON: |
342 | 0 | case SEARCH_SINCE: |
343 | 0 | have_tz_offset = FALSE; tz_offset = 0; date = (time_t)-1; |
344 | 0 | switch (arg->value.date_type) { |
345 | 0 | case MAIL_SEARCH_DATE_TYPE_SENT: |
346 | 0 | if (mail_get_date(ctx->cur_mail, &date, &tz_offset) < 0) { |
347 | 0 | search_cur_mail_failed(ctx); |
348 | 0 | return -1; |
349 | 0 | } |
350 | 0 | have_tz_offset = TRUE; |
351 | 0 | break; |
352 | 0 | case MAIL_SEARCH_DATE_TYPE_RECEIVED: |
353 | 0 | if (mail_get_received_date(ctx->cur_mail, &date) < 0) { |
354 | 0 | search_cur_mail_failed(ctx); |
355 | 0 | return -1; |
356 | 0 | } |
357 | 0 | break; |
358 | 0 | case MAIL_SEARCH_DATE_TYPE_SAVED: |
359 | 0 | if (mail_get_save_date(ctx->cur_mail, &date) < 0) { |
360 | 0 | search_cur_mail_failed(ctx); |
361 | 0 | return -1; |
362 | 0 | } |
363 | 0 | break; |
364 | 0 | } |
365 | | |
366 | 0 | if ((arg->value.search_flags & |
367 | 0 | MAIL_SEARCH_ARG_FLAG_UTC_TIMES) == 0) { |
368 | 0 | if (!have_tz_offset) { |
369 | 0 | tm = localtime(&date); |
370 | 0 | tz_offset = utc_offset(tm, date); |
371 | 0 | } |
372 | 0 | date += tz_offset * 60; |
373 | 0 | } |
374 | |
|
375 | 0 | switch (arg->type) { |
376 | 0 | case SEARCH_BEFORE: |
377 | 0 | return date < arg->value.time ? 1 : 0; |
378 | 0 | case SEARCH_ON: |
379 | 0 | return (date >= arg->value.time && |
380 | 0 | date < arg->value.time + 3600*24) ? 1 : 0; |
381 | 0 | case SEARCH_SINCE: |
382 | 0 | return date >= arg->value.time ? 1 : 0; |
383 | 0 | default: |
384 | 0 | i_unreached(); |
385 | 0 | } |
386 | | |
387 | | /* save date attribute */ |
388 | 0 | case SEARCH_SAVEDATESUPPORTED: |
389 | 0 | ret = mail_get_save_date(ctx->cur_mail, &date); |
390 | 0 | if (ret < 0) { |
391 | 0 | search_cur_mail_failed(ctx); |
392 | 0 | return -1; |
393 | 0 | } |
394 | 0 | return ret; |
395 | | |
396 | | /* sizes */ |
397 | 0 | case SEARCH_SMALLER: |
398 | 0 | case SEARCH_LARGER: |
399 | 0 | if (mail_get_virtual_size(ctx->cur_mail, &virtual_size) < 0) { |
400 | 0 | search_cur_mail_failed(ctx); |
401 | 0 | return -1; |
402 | 0 | } |
403 | | |
404 | 0 | if (arg->type == SEARCH_SMALLER) |
405 | 0 | return virtual_size < arg->value.size ? 1 : 0; |
406 | 0 | else |
407 | 0 | return virtual_size > arg->value.size ? 1 : 0; |
408 | | |
409 | 0 | case SEARCH_GUID: |
410 | 0 | if (mail_get_special(ctx->cur_mail, MAIL_FETCH_GUID, &str) < 0) { |
411 | 0 | search_cur_mail_failed(ctx); |
412 | 0 | return -1; |
413 | 0 | } |
414 | 0 | return strcmp(str, arg->value.str) == 0 ? 1 : 0; |
415 | 0 | case SEARCH_REAL_UID: { |
416 | 0 | struct mail *real_mail; |
417 | |
|
418 | 0 | if (mail_get_backend_mail(ctx->cur_mail, &real_mail) < 0) { |
419 | 0 | search_cur_mail_failed(ctx); |
420 | 0 | return -1; |
421 | 0 | } |
422 | 0 | return seq_range_exists(&arg->value.seqset, real_mail->uid) ? 1 : 0; |
423 | 0 | } |
424 | 0 | default: |
425 | 0 | return -1; |
426 | 0 | } |
427 | 0 | } |
428 | | |
429 | | static void search_cached_arg(struct mail_search_arg *arg, |
430 | | struct index_search_context *ctx) |
431 | 0 | { |
432 | 0 | switch (search_arg_match_cached(ctx, arg)) { |
433 | 0 | case -1: |
434 | | /* unknown */ |
435 | 0 | break; |
436 | 0 | case 0: |
437 | 0 | ARG_SET_RESULT(arg, 0); |
438 | 0 | break; |
439 | 0 | default: |
440 | 0 | ARG_SET_RESULT(arg, 1); |
441 | 0 | break; |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | static int search_sent(enum mail_search_arg_type type, time_t search_time, |
446 | | const unsigned char *sent_value, size_t sent_value_len) |
447 | 0 | { |
448 | 0 | time_t sent_time; |
449 | 0 | int timezone_offset; |
450 | |
|
451 | 0 | if (sent_value == NULL) |
452 | 0 | return 0; |
453 | | |
454 | | /* NOTE: RFC-3501 specifies that timezone is ignored |
455 | | in searches. sent_time is returned as UTC, so change it. */ |
456 | 0 | if (!message_date_parse(sent_value, sent_value_len, |
457 | 0 | &sent_time, &timezone_offset)) |
458 | 0 | return 0; |
459 | 0 | sent_time += timezone_offset * 60; |
460 | |
|
461 | 0 | switch (type) { |
462 | 0 | case SEARCH_BEFORE: |
463 | 0 | return sent_time < search_time ? 1 : 0; |
464 | 0 | case SEARCH_ON: |
465 | 0 | return (sent_time >= search_time && |
466 | 0 | sent_time < search_time + 3600*24) ? 1 : 0; |
467 | 0 | case SEARCH_SINCE: |
468 | 0 | return sent_time >= search_time ? 1 : 0; |
469 | 0 | default: |
470 | 0 | i_unreached(); |
471 | 0 | } |
472 | 0 | } |
473 | | |
474 | | static struct message_search_context * |
475 | | msg_search_arg_context(struct index_search_context *ctx, |
476 | | struct mail_search_arg *arg) |
477 | 0 | { |
478 | 0 | enum message_search_flags flags = 0; |
479 | |
|
480 | 0 | if (arg->context == NULL) T_BEGIN { |
481 | 0 | string_t *dtc = t_str_new(128); |
482 | |
|
483 | 0 | if (ctx->mail_ctx.normalizer(arg->value.str, |
484 | 0 | strlen(arg->value.str), dtc) < 0) |
485 | 0 | i_panic("search key not utf8: %s", arg->value.str); |
486 | | |
487 | 0 | if (arg->type == SEARCH_BODY) |
488 | 0 | flags |= MESSAGE_SEARCH_FLAG_SKIP_HEADERS; |
489 | | /* we don't get here if arg is "", but dtc can be "" if it |
490 | | only contains characters that we need to ignore. handle |
491 | | those searches by returning them as non-matched. */ |
492 | 0 | if (str_len(dtc) > 0) { |
493 | 0 | arg->context = |
494 | 0 | message_search_init(str_c(dtc), |
495 | 0 | ctx->mail_ctx.normalizer, |
496 | 0 | flags); |
497 | 0 | } |
498 | 0 | } T_END; |
499 | 0 | return arg->context; |
500 | 0 | } |
501 | | |
502 | | static void compress_lwsp(string_t *dest, const unsigned char *src, |
503 | | size_t src_len) |
504 | 0 | { |
505 | 0 | size_t i; |
506 | 0 | bool prev_lwsp = TRUE; |
507 | |
|
508 | 0 | for (i = 0; i < src_len; i++) { |
509 | 0 | if (IS_LWSP(src[i])) { |
510 | 0 | if (!prev_lwsp) { |
511 | 0 | prev_lwsp = TRUE; |
512 | 0 | str_append_c(dest, ' '); |
513 | 0 | } |
514 | 0 | } else { |
515 | 0 | prev_lwsp = FALSE; |
516 | 0 | str_append_c(dest, src[i]); |
517 | 0 | } |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | | static pool_t |
522 | | search_context_temp_pool(struct index_search_context *ctx, size_t size) |
523 | 0 | { |
524 | 0 | if (ctx->temp_pool != NULL) { |
525 | 0 | p_clear(ctx->temp_pool); |
526 | 0 | return ctx->temp_pool; |
527 | 0 | } |
528 | | |
529 | 0 | if (size <= 1024) |
530 | 0 | return pool_datastack_create(); |
531 | | /* This could just as well be allocated from data stack, but it |
532 | | makes it more difficult to track bad data_stack_grow events. |
533 | | Use a temporary memory pool instead. */ |
534 | 0 | ctx->temp_pool = pool_alloconly_create("search context temp", size); |
535 | 0 | return ctx->temp_pool; |
536 | 0 | } |
537 | | |
538 | | static void search_header_arg(struct mail_search_arg *arg, |
539 | | struct search_header_context *ctx) |
540 | 0 | { |
541 | 0 | struct message_search_context *msg_search_ctx; |
542 | 0 | struct message_block block; |
543 | 0 | struct message_header_line hdr; |
544 | 0 | int ret; |
545 | | |
546 | | /* first check that the field name matches to argument. */ |
547 | 0 | switch (arg->type) { |
548 | 0 | case SEARCH_BEFORE: |
549 | 0 | case SEARCH_ON: |
550 | 0 | case SEARCH_SINCE: |
551 | 0 | if (arg->value.date_type != MAIL_SEARCH_DATE_TYPE_SENT) |
552 | 0 | return; |
553 | | |
554 | | /* date is handled differently than others */ |
555 | 0 | if (strcasecmp(ctx->hdr->name, "Date") == 0) { |
556 | 0 | if (ctx->hdr->continues) { |
557 | 0 | ctx->hdr->use_full_value = TRUE; |
558 | 0 | return; |
559 | 0 | } |
560 | 0 | ret = search_sent(arg->type, arg->value.time, |
561 | 0 | ctx->hdr->full_value, |
562 | 0 | ctx->hdr->full_value_len); |
563 | 0 | ARG_SET_RESULT(arg, ret); |
564 | 0 | } |
565 | 0 | return; |
566 | | |
567 | 0 | case SEARCH_HEADER: |
568 | 0 | case SEARCH_HEADER_ADDRESS: |
569 | 0 | case SEARCH_HEADER_COMPRESS_LWSP: |
570 | 0 | ctx->custom_header = TRUE; |
571 | |
|
572 | 0 | if (strcasecmp(ctx->hdr->name, arg->hdr_field_name) != 0) |
573 | 0 | return; |
574 | 0 | break; |
575 | 0 | default: |
576 | 0 | return; |
577 | 0 | } |
578 | | |
579 | 0 | if (arg->value.str[0] == '\0') { |
580 | | /* we're just testing existence of the field. always matches. */ |
581 | 0 | ARG_SET_RESULT(arg, 1); |
582 | 0 | return; |
583 | 0 | } |
584 | | |
585 | 0 | if (ctx->hdr->continues) { |
586 | 0 | ctx->hdr->use_full_value = TRUE; |
587 | 0 | return; |
588 | 0 | } |
589 | | |
590 | 0 | i_zero(&block); |
591 | | |
592 | | /* We're searching only for values, so drop header name and middle |
593 | | parts. We use header searching so that MIME words will be decoded. */ |
594 | 0 | hdr = *ctx->hdr; |
595 | 0 | hdr.name = ""; hdr.name_len = 0; |
596 | 0 | hdr.middle_len = 0; |
597 | 0 | block.hdr = &hdr; |
598 | |
|
599 | 0 | msg_search_ctx = msg_search_arg_context(ctx->index_ctx, arg); |
600 | 0 | if (msg_search_ctx == NULL) |
601 | 0 | return; |
602 | | |
603 | 0 | if (!ctx->decoded_block_set) { T_BEGIN { |
604 | 0 | struct message_address *addr; |
605 | 0 | string_t *str; |
606 | |
|
607 | 0 | switch (arg->type) { |
608 | 0 | case SEARCH_HEADER: |
609 | | /* simple match */ |
610 | 0 | break; |
611 | 0 | case SEARCH_HEADER_ADDRESS: { |
612 | | /* we have to match against normalized address */ |
613 | 0 | pool_t pool = search_context_temp_pool(ctx->index_ctx, |
614 | 0 | ctx->hdr->full_value_len); |
615 | 0 | addr = message_address_parse(pool, |
616 | 0 | ctx->hdr->full_value, |
617 | 0 | ctx->hdr->full_value_len, |
618 | 0 | UINT_MAX, |
619 | 0 | MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING); |
620 | 0 | str = str_new(pool, ctx->hdr->value_len); |
621 | 0 | message_address_write(str, addr); |
622 | 0 | hdr.value = hdr.full_value = str_data(str); |
623 | 0 | hdr.value_len = hdr.full_value_len = str_len(str); |
624 | 0 | break; |
625 | 0 | } |
626 | 0 | case SEARCH_HEADER_COMPRESS_LWSP: { |
627 | | /* convert LWSP to single spaces */ |
628 | 0 | pool_t pool = search_context_temp_pool(ctx->index_ctx, |
629 | 0 | hdr.full_value_len); |
630 | 0 | str = str_new(pool, hdr.full_value_len); |
631 | 0 | compress_lwsp(str, hdr.full_value, hdr.full_value_len); |
632 | 0 | hdr.value = hdr.full_value = str_data(str); |
633 | 0 | hdr.value_len = hdr.full_value_len = str_len(str); |
634 | 0 | break; |
635 | 0 | } |
636 | 0 | default: |
637 | 0 | i_unreached(); |
638 | 0 | } |
639 | 0 | ret = message_search_more_get_decoded(msg_search_ctx, &block, |
640 | 0 | &ctx->decoded_block) ? 1 : 0; |
641 | 0 | ctx->decoded_block_set = TRUE; |
642 | 0 | } T_END; } else { |
643 | | /* this block was already decoded and saved by an earlier |
644 | | search arg. use the already-decoded block to avoid |
645 | | duplicating work. */ |
646 | 0 | ret = message_search_more_decoded(msg_search_ctx, |
647 | 0 | &ctx->decoded_block) ? 1 : 0; |
648 | 0 | } |
649 | | |
650 | | /* there may be multiple headers. don't mark this failed yet. */ |
651 | 0 | if (ret > 0) |
652 | 0 | ARG_SET_RESULT(arg, 1); |
653 | 0 | } |
654 | | |
655 | | static void search_header_unmatch(struct mail_search_arg *arg, |
656 | | struct search_header_context *ctx ATTR_UNUSED) |
657 | 0 | { |
658 | 0 | switch (arg->type) { |
659 | 0 | case SEARCH_BEFORE: |
660 | 0 | case SEARCH_ON: |
661 | 0 | case SEARCH_SINCE: |
662 | 0 | if (arg->value.date_type != MAIL_SEARCH_DATE_TYPE_SENT) |
663 | 0 | break; |
664 | | |
665 | 0 | if (arg->match_not) { |
666 | | /* date header not found, so we match only for |
667 | | NOT searches */ |
668 | 0 | ARG_SET_RESULT(arg, 0); |
669 | 0 | } |
670 | 0 | break; |
671 | 0 | case SEARCH_HEADER: |
672 | 0 | case SEARCH_HEADER_ADDRESS: |
673 | 0 | case SEARCH_HEADER_COMPRESS_LWSP: |
674 | 0 | ARG_SET_RESULT(arg, 0); |
675 | 0 | break; |
676 | 0 | default: |
677 | 0 | break; |
678 | 0 | } |
679 | 0 | } |
680 | | |
681 | | static void search_header(struct message_header_line *hdr, |
682 | | struct search_header_context *ctx) |
683 | 0 | { |
684 | 0 | if (ctx->parse_headers) |
685 | 0 | index_mail_parse_header(NULL, hdr, ctx->imail); |
686 | |
|
687 | 0 | if (hdr == NULL) { |
688 | | /* end of headers, mark all unknown SEARCH_HEADERs unmatched */ |
689 | 0 | (void)mail_search_args_foreach(ctx->args, search_header_unmatch, |
690 | 0 | ctx); |
691 | 0 | return; |
692 | 0 | } |
693 | | |
694 | 0 | if (hdr->eoh) |
695 | 0 | return; |
696 | | |
697 | 0 | if (ctx->custom_header || strcasecmp(hdr->name, "Date") == 0) { |
698 | 0 | ctx->hdr = hdr; |
699 | |
|
700 | 0 | ctx->decoded_block_set = FALSE; |
701 | 0 | ctx->custom_header = FALSE; |
702 | 0 | (void)mail_search_args_foreach(ctx->args, search_header_arg, ctx); |
703 | 0 | } |
704 | 0 | } |
705 | | |
706 | | static void search_body(struct mail_search_arg *arg, |
707 | | struct search_body_context *ctx) |
708 | 0 | { |
709 | 0 | struct message_search_context *msg_search_ctx; |
710 | 0 | const char *error; |
711 | 0 | int ret; |
712 | |
|
713 | 0 | switch (arg->type) { |
714 | 0 | case SEARCH_BODY: |
715 | 0 | case SEARCH_TEXT: |
716 | 0 | break; |
717 | 0 | default: |
718 | 0 | return; |
719 | 0 | } |
720 | | |
721 | 0 | msg_search_ctx = msg_search_arg_context(ctx->index_ctx, arg); |
722 | 0 | if (msg_search_ctx == NULL) { |
723 | 0 | ARG_SET_RESULT(arg, 0); |
724 | 0 | return; |
725 | 0 | } |
726 | | |
727 | 0 | i_stream_seek(ctx->input, 0); |
728 | 0 | ret = message_search_msg(msg_search_ctx, ctx->input, ctx->part, &error); |
729 | 0 | if (ret < 0 && ctx->input->stream_errno == 0) { |
730 | | /* try again without cached parts */ |
731 | 0 | index_mail_set_message_parts_corrupted(ctx->index_ctx->cur_mail, error); |
732 | |
|
733 | 0 | i_stream_seek(ctx->input, 0); |
734 | 0 | ret = message_search_msg(msg_search_ctx, ctx->input, NULL, &error); |
735 | 0 | i_assert(ret >= 0 || ctx->input->stream_errno != 0); |
736 | 0 | } |
737 | 0 | if (ctx->input->stream_errno != 0) { |
738 | 0 | mailbox_set_critical(ctx->index_ctx->box, |
739 | 0 | "read(%s) failed: %s", i_stream_get_name(ctx->input), |
740 | 0 | i_stream_get_error(ctx->input)); |
741 | 0 | } |
742 | |
|
743 | 0 | ARG_SET_RESULT(arg, ret); |
744 | 0 | } |
745 | | |
746 | | static int search_arg_match_text(struct mail_search_arg *args, |
747 | | struct index_search_context *ctx) |
748 | 0 | { |
749 | 0 | const enum message_header_parser_flags hdr_parser_flags = |
750 | 0 | MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE; |
751 | 0 | struct index_mail *imail = INDEX_MAIL(ctx->cur_mail); |
752 | 0 | struct mail *real_mail; |
753 | 0 | struct istream *input = NULL; |
754 | 0 | struct mailbox_header_lookup_ctx *headers_ctx; |
755 | 0 | struct search_header_context hdr_ctx; |
756 | 0 | struct search_body_context body_ctx; |
757 | 0 | const char *const *headers; |
758 | 0 | bool have_headers, have_body, failed = FALSE; |
759 | 0 | int ret; |
760 | | |
761 | | /* first check what we need to use */ |
762 | 0 | headers = mail_search_args_analyze(args, &have_headers, &have_body); |
763 | 0 | if (!have_headers && !have_body) |
764 | 0 | return -1; |
765 | | |
766 | 0 | i_zero(&hdr_ctx); |
767 | 0 | hdr_ctx.index_ctx = ctx; |
768 | | /* hdr_ctx.imail is different from imail for mails in |
769 | | virtual mailboxes */ |
770 | 0 | if (mail_get_backend_mail(ctx->cur_mail, &real_mail) < 0) { |
771 | 0 | search_cur_mail_failed(ctx); |
772 | 0 | return -1; |
773 | 0 | } |
774 | 0 | hdr_ctx.imail = INDEX_MAIL(real_mail); |
775 | 0 | hdr_ctx.custom_header = TRUE; |
776 | 0 | hdr_ctx.args = args; |
777 | |
|
778 | 0 | headers_ctx = headers == NULL ? NULL : |
779 | 0 | mailbox_header_lookup_init(ctx->box, headers); |
780 | 0 | if (headers != NULL && |
781 | 0 | (!have_body || |
782 | 0 | ctx->cur_mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER)) { |
783 | | /* try to look up the specified headers from cache */ |
784 | 0 | i_assert(*headers != NULL); |
785 | | |
786 | 0 | if (mail_get_header_stream(ctx->cur_mail, headers_ctx, |
787 | 0 | &input) < 0) { |
788 | 0 | search_cur_mail_failed(ctx); |
789 | 0 | failed = TRUE; |
790 | 0 | } else { |
791 | 0 | message_parse_header(input, NULL, hdr_parser_flags, |
792 | 0 | search_header, &hdr_ctx); |
793 | 0 | } |
794 | 0 | input = NULL; |
795 | 0 | } else if (have_headers) { |
796 | | /* we need to read the entire header */ |
797 | 0 | ret = have_body ? |
798 | 0 | mail_get_stream_because(ctx->cur_mail, NULL, NULL, "search", &input) : |
799 | 0 | mail_get_hdr_stream_because(ctx->cur_mail, NULL, "search", &input); |
800 | 0 | if (ret < 0) { |
801 | 0 | search_cur_mail_failed(ctx); |
802 | 0 | failed = TRUE; |
803 | 0 | } else { |
804 | | /* FIXME: The header parsing here is an optimization to |
805 | | avoid parsing the header twice: First when checking |
806 | | whether the search matches, and secondly when |
807 | | generating wanted fields. However, if we already |
808 | | know that we want to generate a BODYSTRUCTURE reply, |
809 | | index_mail_parse_header() must have a non-NULL part |
810 | | parameter. That's not easily possible at this point |
811 | | without larger code changes, so for now we'll just |
812 | | disable this optimization for that case. */ |
813 | 0 | hdr_ctx.parse_headers = |
814 | 0 | !hdr_ctx.imail->data.save_bodystructure_header && |
815 | 0 | index_mail_want_parse_headers(hdr_ctx.imail); |
816 | 0 | if (hdr_ctx.parse_headers) { |
817 | 0 | index_mail_parse_header_init(hdr_ctx.imail, |
818 | 0 | headers_ctx, TRUE); |
819 | 0 | } |
820 | 0 | message_parse_header(input, NULL, hdr_parser_flags, |
821 | 0 | search_header, &hdr_ctx); |
822 | 0 | if (input->stream_errno != 0) { |
823 | 0 | mailbox_set_critical(ctx->box, |
824 | 0 | "read(%s) failed: %s", |
825 | 0 | i_stream_get_name(input), |
826 | 0 | i_stream_get_error(input)); |
827 | 0 | failed = TRUE; |
828 | 0 | search_set_failed(ctx); |
829 | 0 | } |
830 | 0 | } |
831 | 0 | } |
832 | 0 | mailbox_header_lookup_unref(&headers_ctx); |
833 | |
|
834 | 0 | if (failed) { |
835 | | /* opening mail failed. maybe because of lookup_abort. |
836 | | update access_parts for prefetching */ |
837 | 0 | if (have_body) |
838 | 0 | imail->data.access_part |= READ_HDR | READ_BODY; |
839 | 0 | else |
840 | 0 | imail->data.access_part |= READ_HDR; |
841 | 0 | return -1; |
842 | 0 | } |
843 | | |
844 | 0 | if (have_headers) { |
845 | | /* see if the header search succeeded in finishing the search */ |
846 | 0 | ret = mail_search_args_foreach(args, search_none, NULL); |
847 | 0 | if (ret >= 0 || !have_body) |
848 | 0 | return ret; |
849 | 0 | } |
850 | | |
851 | 0 | i_assert(have_body); |
852 | | |
853 | 0 | if (ctx->cur_mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) { |
854 | 0 | imail->data.access_part |= READ_HDR | READ_BODY; |
855 | 0 | return -1; |
856 | 0 | } |
857 | | |
858 | 0 | if (input == NULL) { |
859 | | /* we didn't search headers. */ |
860 | 0 | struct message_size hdr_size; |
861 | |
|
862 | 0 | if (mail_get_stream_because(ctx->cur_mail, &hdr_size, NULL, "search", &input) < 0) { |
863 | 0 | search_cur_mail_failed(ctx); |
864 | 0 | return -1; |
865 | 0 | } |
866 | 0 | i_stream_seek(input, hdr_size.physical_size); |
867 | 0 | } |
868 | | |
869 | 0 | i_zero(&body_ctx); |
870 | 0 | body_ctx.index_ctx = ctx; |
871 | 0 | body_ctx.input = input; |
872 | | /* Get parts if they already exist in cache. If they don't, |
873 | | message-search will parse the mail automatically. */ |
874 | 0 | ctx->cur_mail->lookup_abort = MAIL_LOOKUP_ABORT_NOT_IN_CACHE; |
875 | 0 | (void)mail_get_parts(ctx->cur_mail, &body_ctx.part); |
876 | 0 | ctx->cur_mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER; |
877 | |
|
878 | 0 | return mail_search_args_foreach(args, search_body, &body_ctx); |
879 | 0 | } |
880 | | |
881 | | static bool |
882 | | search_msgset_fix_limits(unsigned int messages_count, |
883 | | ARRAY_TYPE(seq_range) *seqset, bool match_not) |
884 | 0 | { |
885 | 0 | struct seq_range *range; |
886 | 0 | unsigned int count; |
887 | |
|
888 | 0 | i_assert(messages_count > 0); |
889 | | |
890 | 0 | range = array_get_modifiable(seqset, &count); |
891 | 0 | if (count > 0) { |
892 | 0 | i_assert(range[0].seq1 != 0); |
893 | 0 | if (range[count-1].seq2 == (uint32_t)-1) { |
894 | | /* "*" used, make sure the last message is in the range |
895 | | (e.g. with count+1:* we still want to include it) */ |
896 | 0 | seq_range_array_add(seqset, messages_count); |
897 | 0 | } |
898 | | /* remove all nonexistent messages */ |
899 | 0 | seq_range_array_remove_range(seqset, messages_count + 1, |
900 | 0 | (uint32_t)-1); |
901 | 0 | } |
902 | 0 | if (!match_not) |
903 | 0 | return array_count(seqset) > 0; |
904 | 0 | else { |
905 | | /* if all messages are in the range, it can't match */ |
906 | 0 | range = array_get_modifiable(seqset, &count); |
907 | 0 | return count == 0 || range[0].seq1 != 1 || |
908 | 0 | range[count-1].seq2 != messages_count; |
909 | 0 | } |
910 | 0 | } |
911 | | |
912 | | static void |
913 | | search_msgset_fix(unsigned int messages_count, |
914 | | ARRAY_TYPE(seq_range) *seqset, |
915 | | uint32_t *seq1_r, uint32_t *seq2_r, bool match_not) |
916 | 0 | { |
917 | 0 | const struct seq_range *range; |
918 | 0 | unsigned int count; |
919 | 0 | uint32_t min_seq, max_seq; |
920 | |
|
921 | 0 | if (!search_msgset_fix_limits(messages_count, seqset, match_not)) { |
922 | 0 | *seq1_r = (uint32_t)-1; |
923 | 0 | *seq2_r = 0; |
924 | 0 | return; |
925 | 0 | } |
926 | | |
927 | 0 | range = array_get(seqset, &count); |
928 | 0 | if (!match_not) { |
929 | 0 | min_seq = range[0].seq1; |
930 | 0 | max_seq = range[count-1].seq2; |
931 | 0 | } else if (count == 0) { |
932 | | /* matches all messages */ |
933 | 0 | min_seq = 1; |
934 | 0 | max_seq = messages_count; |
935 | 0 | } else { |
936 | 0 | min_seq = range[0].seq1 > 1 ? 1 : range[0].seq2 + 1; |
937 | 0 | max_seq = range[count-1].seq2 < messages_count ? |
938 | 0 | messages_count : range[count-1].seq1 - 1; |
939 | 0 | if (min_seq > max_seq) { |
940 | 0 | *seq1_r = (uint32_t)-1; |
941 | 0 | *seq2_r = 0; |
942 | 0 | return; |
943 | 0 | } |
944 | 0 | } |
945 | | |
946 | 0 | if (*seq1_r < min_seq || *seq1_r == 0) |
947 | 0 | *seq1_r = min_seq; |
948 | 0 | if (*seq2_r > max_seq) |
949 | 0 | *seq2_r = max_seq; |
950 | 0 | } |
951 | | |
952 | | static void search_or_parse_msgset_args(unsigned int messages_count, |
953 | | struct mail_search_arg *args, |
954 | | uint32_t *seq1_r, uint32_t *seq2_r) |
955 | 0 | { |
956 | 0 | uint32_t seq1, seq2, min_seq1 = 0, max_seq2 = 0; |
957 | |
|
958 | 0 | for (; args != NULL; args = args->next) { |
959 | 0 | seq1 = 1; seq2 = messages_count; |
960 | |
|
961 | 0 | switch (args->type) { |
962 | 0 | case SEARCH_SUB: |
963 | 0 | i_assert(!args->match_not); |
964 | 0 | search_parse_msgset_args(messages_count, |
965 | 0 | args->value.subargs, |
966 | 0 | &seq1, &seq2); |
967 | 0 | break; |
968 | 0 | case SEARCH_OR: |
969 | 0 | i_assert(!args->match_not); |
970 | 0 | search_or_parse_msgset_args(messages_count, |
971 | 0 | args->value.subargs, |
972 | 0 | &seq1, &seq2); |
973 | 0 | break; |
974 | 0 | case SEARCH_SEQSET: |
975 | 0 | search_msgset_fix(messages_count, &args->value.seqset, |
976 | 0 | &seq1, &seq2, args->match_not); |
977 | 0 | break; |
978 | 0 | default: |
979 | 0 | break; |
980 | 0 | } |
981 | | |
982 | 0 | if (min_seq1 == 0) { |
983 | 0 | min_seq1 = seq1; |
984 | 0 | max_seq2 = seq2; |
985 | 0 | } else { |
986 | 0 | if (seq1 < min_seq1) |
987 | 0 | min_seq1 = seq1; |
988 | 0 | if (seq2 > max_seq2) |
989 | 0 | max_seq2 = seq2; |
990 | 0 | } |
991 | 0 | } |
992 | 0 | i_assert(min_seq1 != 0); |
993 | | |
994 | 0 | if (min_seq1 > *seq1_r) |
995 | 0 | *seq1_r = min_seq1; |
996 | 0 | if (max_seq2 < *seq2_r) |
997 | 0 | *seq2_r = max_seq2; |
998 | 0 | } |
999 | | |
1000 | | static void search_parse_msgset_args(unsigned int messages_count, |
1001 | | struct mail_search_arg *args, |
1002 | | uint32_t *seq1_r, uint32_t *seq2_r) |
1003 | 0 | { |
1004 | 0 | for (; args != NULL; args = args->next) { |
1005 | 0 | switch (args->type) { |
1006 | 0 | case SEARCH_SUB: |
1007 | 0 | i_assert(!args->match_not); |
1008 | 0 | search_parse_msgset_args(messages_count, |
1009 | 0 | args->value.subargs, |
1010 | 0 | seq1_r, seq2_r); |
1011 | 0 | break; |
1012 | 0 | case SEARCH_OR: |
1013 | | /* go through our children and use the widest seqset |
1014 | | range */ |
1015 | 0 | i_assert(!args->match_not); |
1016 | 0 | search_or_parse_msgset_args(messages_count, |
1017 | 0 | args->value.subargs, |
1018 | 0 | seq1_r, seq2_r); |
1019 | 0 | break; |
1020 | 0 | case SEARCH_SEQSET: |
1021 | 0 | search_msgset_fix(messages_count, &args->value.seqset, |
1022 | 0 | seq1_r, seq2_r, args->match_not); |
1023 | 0 | break; |
1024 | 0 | default: |
1025 | 0 | break; |
1026 | 0 | } |
1027 | 0 | } |
1028 | 0 | } |
1029 | | |
1030 | | static void search_limit_lowwater(struct index_search_context *ctx, |
1031 | | uint32_t uid_lowwater, uint32_t *first_seq) |
1032 | 0 | { |
1033 | 0 | uint32_t seq1, seq2; |
1034 | |
|
1035 | 0 | if (uid_lowwater == 0) |
1036 | 0 | return; |
1037 | | |
1038 | 0 | (void)mail_index_lookup_seq_range(ctx->view, uid_lowwater, (uint32_t)-1, |
1039 | 0 | &seq1, &seq2); |
1040 | 0 | if (*first_seq < seq1) |
1041 | 0 | *first_seq = seq1; |
1042 | 0 | } |
1043 | | |
1044 | | /* Returns TRUE if the view's header counters (e.g. |
1045 | | deleted_messages_count) may be older than the flags that searching |
1046 | | actually matches. */ |
1047 | | static bool search_view_have_stale_hdr_counters(struct mail_index_view *view) |
1048 | 0 | { |
1049 | 0 | const struct mail_index_modseq_header *view_modseq_hdr, *head_modseq_hdr; |
1050 | |
|
1051 | 0 | if (view->map == view->index->map) |
1052 | 0 | return FALSE; |
1053 | | |
1054 | | /* The view's map is older than the index's head map. If their |
1055 | | highest_modseqs are equal, there are no flag, keyword, append or |
1056 | | expunge changes between the maps, so the view's header counters |
1057 | | are still valid. Compare the maps' raw modseq headers - |
1058 | | mail_index_map_modseq_get_highest() could fall back to returning |
1059 | | the log head's modseq for both maps, hiding their difference. */ |
1060 | 0 | view_modseq_hdr = mail_index_map_get_modseq_header(view->map); |
1061 | 0 | head_modseq_hdr = mail_index_map_get_modseq_header(view->index->map); |
1062 | 0 | return view_modseq_hdr == NULL || head_modseq_hdr == NULL || |
1063 | 0 | view_modseq_hdr->highest_modseq == 0 || |
1064 | 0 | view_modseq_hdr->highest_modseq != head_modseq_hdr->highest_modseq; |
1065 | 0 | } |
1066 | | |
1067 | | static bool search_limit_by_hdr(struct index_search_context *ctx, |
1068 | | struct mail_search_arg *args, |
1069 | | uint32_t *seq1, uint32_t *seq2) |
1070 | 0 | { |
1071 | 0 | const struct mail_index_header *hdr; |
1072 | 0 | enum mail_flags pvt_flags_mask; |
1073 | 0 | uint64_t highest_modseq; |
1074 | |
|
1075 | 0 | if (search_view_have_stale_hdr_counters(ctx->view)) { |
1076 | | /* Flag matching uses the latest flags from the index's head |
1077 | | map (see view_lookup_full()), but the view's header still |
1078 | | has the counters from the last sync. For example with a |
1079 | | pipelined UID STORE +FLAGS \Deleted followed by UID EXPUNGE |
1080 | | the view's header could still have deleted_messages_count=0, |
1081 | | even though the messages are already marked as \Deleted in |
1082 | | the head map. Don't do any optimizations based on the stale |
1083 | | header counters in that case. */ |
1084 | 0 | return *seq1 <= *seq2; |
1085 | 0 | } |
1086 | | |
1087 | 0 | hdr = mail_index_get_header(ctx->view); |
1088 | | /* we can't trust that private view's header is fully up to date, |
1089 | | so do this optimization only for non-private flags */ |
1090 | 0 | pvt_flags_mask = ctx->box->view_pvt == NULL ? 0 : |
1091 | 0 | mailbox_get_private_flags_mask(ctx->box); |
1092 | |
|
1093 | 0 | for (; args != NULL; args = args->next) { |
1094 | 0 | switch (args->type) { |
1095 | 0 | case SEARCH_ALL: |
1096 | 0 | if (args->match_not) { |
1097 | | /* NOT ALL - pointless noop query */ |
1098 | 0 | return FALSE; |
1099 | 0 | } |
1100 | 0 | continue; |
1101 | 0 | case SEARCH_MODSEQ: |
1102 | | /* MODSEQ higher than current HIGHESTMODSEQ? */ |
1103 | 0 | highest_modseq = mail_index_modseq_get_highest(ctx->view); |
1104 | 0 | if (args->value.modseq->modseq > highest_modseq) |
1105 | 0 | return FALSE; |
1106 | 0 | continue; |
1107 | 0 | default: |
1108 | 0 | continue; |
1109 | 0 | case SEARCH_FLAGS: |
1110 | 0 | break; |
1111 | 0 | } |
1112 | 0 | if ((args->value.flags & MAIL_SEEN) != 0 && |
1113 | 0 | (pvt_flags_mask & MAIL_SEEN) == 0) { |
1114 | | /* SEEN with 0 seen? */ |
1115 | 0 | if (!args->match_not && hdr->seen_messages_count == 0) |
1116 | 0 | return FALSE; |
1117 | | |
1118 | 0 | if (hdr->seen_messages_count == hdr->messages_count) { |
1119 | | /* UNSEEN with all seen? */ |
1120 | 0 | if (args->match_not) |
1121 | 0 | return FALSE; |
1122 | 0 | } else if (args->match_not) { |
1123 | | /* UNSEEN with lowwater limiting */ |
1124 | 0 | search_limit_lowwater(ctx, |
1125 | 0 | hdr->first_unseen_uid_lowwater, seq1); |
1126 | 0 | } |
1127 | 0 | } |
1128 | 0 | if ((args->value.flags & MAIL_DELETED) != 0 && |
1129 | 0 | (pvt_flags_mask & MAIL_DELETED) == 0) { |
1130 | | /* DELETED with 0 deleted? */ |
1131 | 0 | if (!args->match_not && |
1132 | 0 | hdr->deleted_messages_count == 0) |
1133 | 0 | return FALSE; |
1134 | | |
1135 | 0 | if (hdr->deleted_messages_count == hdr->messages_count) { |
1136 | | /* UNDELETED with all deleted? */ |
1137 | 0 | if (args->match_not) |
1138 | 0 | return FALSE; |
1139 | 0 | } else if (!args->match_not) { |
1140 | | /* DELETED with lowwater limiting */ |
1141 | 0 | search_limit_lowwater(ctx, |
1142 | 0 | hdr->first_deleted_uid_lowwater, seq1); |
1143 | 0 | } |
1144 | 0 | } |
1145 | 0 | } |
1146 | | |
1147 | 0 | return *seq1 <= *seq2; |
1148 | 0 | } |
1149 | | |
1150 | | static void search_get_seqset(struct index_search_context *ctx, |
1151 | | unsigned int messages_count, |
1152 | | struct mail_search_arg *args) |
1153 | 0 | { |
1154 | 0 | if (messages_count == 0) { |
1155 | | /* no messages, don't check sequence ranges. although we could |
1156 | | give error message then for FETCH, we shouldn't do it for |
1157 | | UID FETCH. */ |
1158 | 0 | ctx->seq1 = 1; |
1159 | 0 | ctx->seq2 = 0; |
1160 | 0 | return; |
1161 | 0 | } |
1162 | | |
1163 | 0 | ctx->seq1 = 1; |
1164 | 0 | ctx->seq2 = messages_count; |
1165 | |
|
1166 | 0 | search_parse_msgset_args(messages_count, args, &ctx->seq1, &ctx->seq2); |
1167 | 0 | if (ctx->seq1 == 0) { |
1168 | 0 | ctx->seq1 = 1; |
1169 | 0 | ctx->seq2 = messages_count; |
1170 | 0 | } |
1171 | 0 | if (ctx->seq1 > ctx->seq2) { |
1172 | | /* no matches */ |
1173 | 0 | return; |
1174 | 0 | } |
1175 | | |
1176 | | /* See if this search query can never match based on data in index's |
1177 | | header. We'll scan only the root level args, which is usually |
1178 | | enough. */ |
1179 | 0 | if (!search_limit_by_hdr(ctx, args, &ctx->seq1, &ctx->seq2)) { |
1180 | | /* no matches */ |
1181 | 0 | ctx->seq1 = 1; |
1182 | 0 | ctx->seq2 = 0; |
1183 | 0 | } |
1184 | 0 | } |
1185 | | |
1186 | | static int search_build_subthread(struct mail_thread_iterate_context *iter, |
1187 | | ARRAY_TYPE(seq_range) *uids) |
1188 | 0 | { |
1189 | 0 | struct mail_thread_iterate_context *child_iter; |
1190 | 0 | const struct mail_thread_child_node *node; |
1191 | 0 | int ret = 0; |
1192 | |
|
1193 | 0 | while ((node = mail_thread_iterate_next(iter, &child_iter)) != NULL) { |
1194 | 0 | if (child_iter != NULL) { |
1195 | 0 | if (search_build_subthread(child_iter, uids) < 0) |
1196 | 0 | ret = -1; |
1197 | 0 | } |
1198 | 0 | seq_range_array_add(uids, node->uid); |
1199 | 0 | } |
1200 | 0 | if (mail_thread_iterate_deinit(&iter) < 0) |
1201 | 0 | ret = -1; |
1202 | 0 | return ret; |
1203 | 0 | } |
1204 | | |
1205 | | static int search_build_inthread_result(struct index_search_context *ctx, |
1206 | | struct mail_search_arg *arg) |
1207 | 0 | { |
1208 | 0 | struct mail_thread_iterate_context *iter, *child_iter; |
1209 | 0 | const struct mail_thread_child_node *node; |
1210 | 0 | const ARRAY_TYPE(seq_range) *search_uids; |
1211 | 0 | ARRAY_TYPE(seq_range) thread_uids; |
1212 | 0 | int ret = 0; |
1213 | | |
1214 | | /* mail_search_args_init() must have been called by now */ |
1215 | 0 | i_assert(arg->initialized.search_args != NULL); |
1216 | | |
1217 | 0 | p_array_init(&arg->value.seqset, ctx->mail_ctx.args->pool, 64); |
1218 | 0 | if (mailbox_search_result_build(ctx->mail_ctx.transaction, |
1219 | 0 | arg->initialized.search_args, |
1220 | 0 | MAILBOX_SEARCH_RESULT_FLAG_UPDATE | |
1221 | 0 | MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC, |
1222 | 0 | &arg->value.search_result) < 0) |
1223 | 0 | return -1; |
1224 | 0 | if (ctx->thread_ctx == NULL) { |
1225 | | /* failed earlier */ |
1226 | 0 | return -1; |
1227 | 0 | } |
1228 | | |
1229 | 0 | search_uids = mailbox_search_result_get(arg->value.search_result); |
1230 | 0 | if (array_count(search_uids) == 0) { |
1231 | | /* search found nothing - no threads can match */ |
1232 | 0 | return 0; |
1233 | 0 | } |
1234 | | |
1235 | 0 | t_array_init(&thread_uids, 128); |
1236 | 0 | iter = mail_thread_iterate_init(ctx->thread_ctx, |
1237 | 0 | arg->value.thread_type, FALSE); |
1238 | 0 | while ((node = mail_thread_iterate_next(iter, &child_iter)) != NULL) { |
1239 | 0 | seq_range_array_add(&thread_uids, node->uid); |
1240 | 0 | if (child_iter != NULL) { |
1241 | 0 | if (search_build_subthread(child_iter, |
1242 | 0 | &thread_uids) < 0) |
1243 | 0 | ret = -1; |
1244 | 0 | } |
1245 | 0 | if (seq_range_array_have_common(&thread_uids, search_uids)) { |
1246 | | /* yes, we want this thread */ |
1247 | 0 | seq_range_array_merge(&arg->value.seqset, &thread_uids); |
1248 | 0 | } |
1249 | 0 | array_clear(&thread_uids); |
1250 | 0 | } |
1251 | 0 | if (mail_thread_iterate_deinit(&iter) < 0) |
1252 | 0 | ret = -1; |
1253 | 0 | return ret; |
1254 | 0 | } |
1255 | | |
1256 | | static int search_build_inthreads(struct index_search_context *ctx, |
1257 | | struct mail_search_arg *arg) |
1258 | 0 | { |
1259 | 0 | int ret = 0; |
1260 | |
|
1261 | 0 | for (; arg != NULL; arg = arg->next) { |
1262 | 0 | switch (arg->type) { |
1263 | 0 | case SEARCH_OR: |
1264 | 0 | case SEARCH_SUB: |
1265 | 0 | if (search_build_inthreads(ctx, arg->value.subargs) < 0) |
1266 | 0 | ret = -1; |
1267 | 0 | break; |
1268 | 0 | case SEARCH_INTHREAD: |
1269 | | /* The INTHREAD's search key is searched separately |
1270 | | using the same thread context, so build the nested |
1271 | | INTHREADs' results first. */ |
1272 | 0 | if (search_build_inthreads(ctx, arg->value.subargs) < 0) |
1273 | 0 | ret = -1; |
1274 | 0 | if (search_build_inthread_result(ctx, arg) < 0) |
1275 | 0 | ret = -1; |
1276 | 0 | break; |
1277 | 0 | default: |
1278 | 0 | break; |
1279 | 0 | } |
1280 | 0 | } |
1281 | 0 | return ret; |
1282 | 0 | } |
1283 | | |
1284 | | static void |
1285 | | wanted_sort_fields_get(struct mailbox *box, |
1286 | | const enum mail_sort_type *sort_program, |
1287 | | struct mailbox_header_lookup_ctx *wanted_headers, |
1288 | | enum mail_fetch_field *wanted_fields_r, |
1289 | | struct mailbox_header_lookup_ctx **headers_ctx_r) |
1290 | 0 | { |
1291 | 0 | ARRAY_TYPE(const_string) headers; |
1292 | 0 | const char *header; |
1293 | 0 | unsigned int i; |
1294 | |
|
1295 | 0 | *wanted_fields_r = 0; |
1296 | 0 | *headers_ctx_r = NULL; |
1297 | |
|
1298 | 0 | t_array_init(&headers, 8); |
1299 | 0 | for (i = 0; sort_program[i] != MAIL_SORT_END; i++) { |
1300 | 0 | header = NULL; |
1301 | |
|
1302 | 0 | switch (sort_program[i] & MAIL_SORT_MASK) { |
1303 | 0 | case MAIL_SORT_ARRIVAL: |
1304 | 0 | *wanted_fields_r |= MAIL_FETCH_RECEIVED_DATE; |
1305 | 0 | break; |
1306 | 0 | case MAIL_SORT_CC: |
1307 | 0 | header = "Cc"; |
1308 | 0 | break; |
1309 | 0 | case MAIL_SORT_DATE: |
1310 | 0 | *wanted_fields_r |= MAIL_FETCH_DATE; |
1311 | 0 | break; |
1312 | 0 | case MAIL_SORT_FROM: |
1313 | 0 | header = "From"; |
1314 | 0 | break; |
1315 | 0 | case MAIL_SORT_SIZE: |
1316 | 0 | *wanted_fields_r |= MAIL_FETCH_VIRTUAL_SIZE; |
1317 | 0 | break; |
1318 | 0 | case MAIL_SORT_SUBJECT: |
1319 | 0 | header = "Subject"; |
1320 | 0 | break; |
1321 | 0 | case MAIL_SORT_TO: |
1322 | 0 | header = "To"; |
1323 | 0 | break; |
1324 | 0 | } |
1325 | 0 | if (header != NULL) |
1326 | 0 | array_push_back(&headers, &header); |
1327 | 0 | } |
1328 | | |
1329 | 0 | if (wanted_headers != NULL) { |
1330 | 0 | for (i = 0; wanted_headers->name[i] != NULL; i++) |
1331 | 0 | array_push_back(&headers, &wanted_headers->name[i]); |
1332 | 0 | } |
1333 | |
|
1334 | 0 | if (array_count(&headers) > 0) { |
1335 | 0 | array_append_zero(&headers); |
1336 | 0 | *headers_ctx_r = mailbox_header_lookup_init(box, |
1337 | 0 | array_front(&headers)); |
1338 | 0 | } |
1339 | 0 | } |
1340 | | |
1341 | | struct mail_search_context * |
1342 | | index_storage_search_init(struct mailbox_transaction_context *t, |
1343 | | struct mail_search_args *args, |
1344 | | const enum mail_sort_type *sort_program, |
1345 | | enum mail_fetch_field wanted_fields, |
1346 | | struct mailbox_header_lookup_ctx *wanted_headers) |
1347 | 0 | { |
1348 | 0 | struct index_search_context *ctx; |
1349 | 0 | struct mailbox_status status; |
1350 | |
|
1351 | 0 | ctx = i_new(struct index_search_context, 1); |
1352 | 0 | ctx->mail_ctx.transaction = t; |
1353 | 0 | ctx->mail_ctx.normalizer = t->box->storage->user->default_normalizer; |
1354 | 0 | ctx->box = t->box; |
1355 | 0 | ctx->view = t->view; |
1356 | 0 | ctx->mail_ctx.args = args; |
1357 | 0 | ctx->mail_ctx.sort_program = index_sort_program_init(t, sort_program); |
1358 | |
|
1359 | 0 | ctx->mail_ctx.max_mails = t->box->storage->set->mail_prefetch_count + 1; |
1360 | 0 | if (ctx->mail_ctx.max_mails == 0) |
1361 | 0 | ctx->mail_ctx.max_mails = UINT_MAX; |
1362 | 0 | ctx->next_time_check_cost = SEARCH_INITIAL_MAX_COST; |
1363 | 0 | i_gettimeofday(&ctx->last_nonblock_timeval); |
1364 | |
|
1365 | 0 | mailbox_get_open_status(t->box, STATUS_MESSAGES, &status); |
1366 | 0 | ctx->mail_ctx.progress_max = status.messages; |
1367 | |
|
1368 | 0 | i_array_init(&ctx->mail_ctx.results, 5); |
1369 | 0 | array_create(&ctx->mail_ctx.module_contexts, default_pool, |
1370 | 0 | sizeof(void *), 5); |
1371 | 0 | i_array_init(&ctx->mail_ctx.mails, ctx->mail_ctx.max_mails); |
1372 | |
|
1373 | 0 | mail_search_args_reset(ctx->mail_ctx.args->args, TRUE); |
1374 | 0 | if (args->have_inthreads) { |
1375 | 0 | if (mail_thread_init(t->box, NULL, &ctx->thread_ctx) < 0) |
1376 | 0 | search_set_failed(ctx); |
1377 | 0 | if (search_build_inthreads(ctx, args->args) < 0) |
1378 | 0 | search_set_failed(ctx); |
1379 | 0 | } |
1380 | |
|
1381 | 0 | if (sort_program != NULL) { |
1382 | 0 | wanted_sort_fields_get(ctx->box, sort_program, wanted_headers, |
1383 | 0 | &ctx->mail_ctx.wanted_fields, |
1384 | 0 | &ctx->mail_ctx.wanted_headers); |
1385 | 0 | } else if (wanted_headers != NULL) { |
1386 | 0 | ctx->mail_ctx.wanted_headers = wanted_headers; |
1387 | 0 | mailbox_header_lookup_ref(wanted_headers); |
1388 | 0 | } |
1389 | 0 | ctx->mail_ctx.wanted_fields |= wanted_fields; |
1390 | |
|
1391 | 0 | search_get_seqset(ctx, status.messages, args->args); |
1392 | 0 | (void)mail_search_args_foreach(args->args, search_init_arg, ctx); |
1393 | | |
1394 | | /* Need to reset results for match_always cases */ |
1395 | 0 | mail_search_args_reset(ctx->mail_ctx.args->args, FALSE); |
1396 | 0 | return &ctx->mail_ctx; |
1397 | 0 | } |
1398 | | |
1399 | | static void ATTR_NULL(2) |
1400 | | search_arg_deinit(struct mail_search_arg *arg, |
1401 | | struct index_search_context *ctx) |
1402 | 0 | { |
1403 | 0 | switch (arg->type) { |
1404 | 0 | case SEARCH_MIMEPART: |
1405 | 0 | index_search_mime_arg_deinit(arg, ctx); |
1406 | 0 | break; |
1407 | 0 | default: |
1408 | 0 | if (arg->context != NULL) { |
1409 | 0 | struct message_search_context *search_ctx = arg->context; |
1410 | 0 | message_search_deinit(&search_ctx); |
1411 | 0 | arg->context = NULL; |
1412 | 0 | } |
1413 | 0 | } |
1414 | 0 | } |
1415 | | |
1416 | | int index_storage_search_deinit(struct mail_search_context *_ctx) |
1417 | 0 | { |
1418 | 0 | struct index_search_context *ctx = (struct index_search_context *)_ctx; |
1419 | 0 | struct mail *mail; |
1420 | 0 | int ret; |
1421 | |
|
1422 | 0 | ret = ctx->failed ? -1 : 0; |
1423 | |
|
1424 | 0 | mail_search_args_reset(ctx->mail_ctx.args->args, FALSE); |
1425 | 0 | (void)mail_search_args_foreach(ctx->mail_ctx.args->args, |
1426 | 0 | search_arg_deinit, ctx); |
1427 | |
|
1428 | 0 | mailbox_header_lookup_unref(&ctx->mail_ctx.wanted_headers); |
1429 | 0 | if (ctx->mail_ctx.sort_program != NULL) { |
1430 | 0 | if (index_sort_program_deinit(&ctx->mail_ctx.sort_program) < 0) |
1431 | 0 | ret = -1; |
1432 | 0 | } |
1433 | 0 | if (ctx->thread_ctx != NULL) |
1434 | 0 | mail_thread_deinit(&ctx->thread_ctx); |
1435 | 0 | array_free(&ctx->mail_ctx.results); |
1436 | 0 | array_free(&ctx->mail_ctx.module_contexts); |
1437 | |
|
1438 | 0 | array_foreach_elem(&ctx->mail_ctx.mails, mail) { |
1439 | 0 | struct index_mail *imail = INDEX_MAIL(mail); |
1440 | |
|
1441 | 0 | imail->mail.search_mail = FALSE; |
1442 | 0 | mail_free(&mail); |
1443 | 0 | } |
1444 | |
|
1445 | 0 | if (ctx->failed) |
1446 | 0 | mail_storage_last_error_pop(ctx->box->storage); |
1447 | 0 | array_free(&ctx->mail_ctx.mails); |
1448 | 0 | pool_unref(&ctx->temp_pool); |
1449 | 0 | i_free(ctx); |
1450 | 0 | return ret; |
1451 | 0 | } |
1452 | | |
1453 | | static unsigned long long |
1454 | | search_get_cost(struct mailbox_transaction_context *trans) |
1455 | 0 | { |
1456 | 0 | return trans->stats.open_lookup_count * SEARCH_COST_DENTRY + |
1457 | 0 | trans->stats.stat_lookup_count * SEARCH_COST_DENTRY + |
1458 | 0 | trans->stats.fstat_lookup_count * SEARCH_COST_ATTR + |
1459 | 0 | trans->stats.cache_hit_count * SEARCH_COST_CACHE + |
1460 | 0 | trans->stats.files_read_count * SEARCH_COST_FILES_READ + |
1461 | 0 | (trans->stats.files_read_bytes/1024) * SEARCH_COST_KBYTE; |
1462 | 0 | } |
1463 | | |
1464 | | static int search_match_once(struct index_search_context *ctx) |
1465 | 0 | { |
1466 | 0 | int ret; |
1467 | |
|
1468 | 0 | ret = mail_search_args_foreach(ctx->mail_ctx.args->args, |
1469 | 0 | search_cached_arg, ctx); |
1470 | 0 | if (ret < 0) |
1471 | 0 | ret = search_arg_match_text(ctx->mail_ctx.args->args, ctx); |
1472 | 0 | if (ret < 0) |
1473 | 0 | ret = index_search_mime_arg_match(ctx->mail_ctx.args->args, ctx); |
1474 | 0 | return ret; |
1475 | 0 | } |
1476 | | |
1477 | | static bool search_arg_is_static(struct mail_search_arg *arg) |
1478 | 0 | { |
1479 | 0 | struct mail_search_arg *subarg; |
1480 | |
|
1481 | 0 | switch (arg->type) { |
1482 | 0 | case SEARCH_OR: |
1483 | 0 | case SEARCH_SUB: |
1484 | | /* they're static only if all subargs are static */ |
1485 | 0 | subarg = arg->value.subargs; |
1486 | 0 | for (; subarg != NULL; subarg = subarg->next) { |
1487 | 0 | if (!search_arg_is_static(subarg)) |
1488 | 0 | return FALSE; |
1489 | 0 | } |
1490 | 0 | return TRUE; |
1491 | 0 | case SEARCH_SEQSET: |
1492 | | /* changes between syncs, but we can't really handle this |
1493 | | currently. seqsets should be converted to uidsets first. */ |
1494 | 0 | case SEARCH_FLAGS: |
1495 | 0 | case SEARCH_KEYWORDS: |
1496 | 0 | case SEARCH_MODSEQ: |
1497 | 0 | case SEARCH_INTHREAD: |
1498 | 0 | break; |
1499 | 0 | case SEARCH_ALL: |
1500 | 0 | case SEARCH_UIDSET: |
1501 | 0 | case SEARCH_BEFORE: |
1502 | 0 | case SEARCH_ON: |
1503 | 0 | case SEARCH_SINCE: |
1504 | 0 | case SEARCH_SMALLER: |
1505 | 0 | case SEARCH_LARGER: |
1506 | 0 | case SEARCH_HEADER: |
1507 | 0 | case SEARCH_HEADER_ADDRESS: |
1508 | 0 | case SEARCH_HEADER_COMPRESS_LWSP: |
1509 | 0 | case SEARCH_BODY: |
1510 | 0 | case SEARCH_TEXT: |
1511 | 0 | case SEARCH_SAVEDATESUPPORTED: |
1512 | 0 | case SEARCH_GUID: |
1513 | 0 | case SEARCH_MAILBOX: |
1514 | 0 | case SEARCH_MAILBOX_GUID: |
1515 | 0 | case SEARCH_MAILBOX_GLOB: |
1516 | 0 | case SEARCH_REAL_UID: |
1517 | 0 | case SEARCH_MIMEPART: |
1518 | 0 | return TRUE; |
1519 | 0 | } |
1520 | 0 | return FALSE; |
1521 | 0 | } |
1522 | | |
1523 | | static void search_set_static_matches(struct mail_search_arg *arg) |
1524 | 0 | { |
1525 | 0 | for (; arg != NULL; arg = arg->next) { |
1526 | 0 | if (search_arg_is_static(arg)) |
1527 | 0 | arg->result = 1; |
1528 | 0 | } |
1529 | 0 | } |
1530 | | |
1531 | | static bool search_has_static_nonmatches(struct mail_search_arg *arg) |
1532 | 0 | { |
1533 | 0 | for (; arg != NULL; arg = arg->next) { |
1534 | 0 | if (arg->result == 0 && search_arg_is_static(arg)) |
1535 | 0 | return TRUE; |
1536 | 0 | } |
1537 | 0 | return FALSE; |
1538 | 0 | } |
1539 | | |
1540 | | static void search_match_finish(struct index_search_context *ctx, int match) |
1541 | 0 | { |
1542 | 0 | if (match == 0 && |
1543 | 0 | search_has_static_nonmatches(ctx->mail_ctx.args->args)) { |
1544 | | /* if there are saved search results remember |
1545 | | that this message never matches */ |
1546 | 0 | mailbox_search_results_never(&ctx->mail_ctx, |
1547 | 0 | ctx->cur_mail->uid); |
1548 | 0 | } |
1549 | 0 | } |
1550 | | |
1551 | | static int search_match_next(struct index_search_context *ctx) |
1552 | 0 | { |
1553 | 0 | static enum mail_lookup_abort cache_lookups[] = { |
1554 | 0 | MAIL_LOOKUP_ABORT_NOT_IN_CACHE, |
1555 | 0 | MAIL_LOOKUP_ABORT_READ_MAIL, |
1556 | 0 | MAIL_LOOKUP_ABORT_NEVER |
1557 | 0 | }; |
1558 | 0 | unsigned int i, n = N_ELEMENTS(cache_lookups); |
1559 | 0 | int ret = -1; |
1560 | |
|
1561 | 0 | if (ctx->have_mailbox_args) { |
1562 | | /* check that the mailbox name matches. |
1563 | | this makes sense only with virtual mailboxes. */ |
1564 | 0 | ret = mail_search_args_foreach(ctx->mail_ctx.args->args, |
1565 | 0 | search_mailbox_arg, ctx); |
1566 | 0 | } |
1567 | | |
1568 | | /* avoid doing extra work for as long as possible */ |
1569 | 0 | if (ctx->mail_ctx.max_mails > 1) { |
1570 | | /* we're doing prefetching. if we have to read the mail, |
1571 | | do a prefetch first and the final search later */ |
1572 | 0 | n--; |
1573 | 0 | } |
1574 | |
|
1575 | 0 | i_assert(ctx->cur_mail->lookup_abort == MAIL_LOOKUP_ABORT_NEVER); |
1576 | 0 | for (i = 0; i < n && ret < 0; i++) { |
1577 | 0 | ctx->cur_mail->lookup_abort = cache_lookups[i]; |
1578 | 0 | T_BEGIN { |
1579 | 0 | ret = search_match_once(ctx); |
1580 | 0 | } T_END; |
1581 | 0 | } |
1582 | 0 | ctx->cur_mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER; |
1583 | 0 | search_match_finish(ctx, ret); |
1584 | 0 | return ret; |
1585 | 0 | } |
1586 | | |
1587 | | static bool search_would_block(struct index_search_context *ctx) |
1588 | 0 | { |
1589 | 0 | struct timeval now; |
1590 | 0 | unsigned long long guess_cost; |
1591 | 0 | long long usecs; |
1592 | 0 | bool ret; |
1593 | |
|
1594 | 0 | if (ctx->cost < ctx->next_time_check_cost) |
1595 | 0 | return FALSE; |
1596 | | |
1597 | 0 | i_gettimeofday(&now); |
1598 | |
|
1599 | 0 | usecs = timeval_diff_usecs(&now, &ctx->last_nonblock_timeval); |
1600 | 0 | if (usecs < 0) { |
1601 | | /* clock moved backwards. */ |
1602 | 0 | ctx->last_nonblock_timeval = now; |
1603 | 0 | ctx->next_time_check_cost = SEARCH_INITIAL_MAX_COST; |
1604 | 0 | return TRUE; |
1605 | 0 | } else if (usecs < SEARCH_MIN_NONBLOCK_USECS) { |
1606 | | /* not finished yet. estimate the next time lookup */ |
1607 | 0 | ret = FALSE; |
1608 | 0 | } else { |
1609 | | /* done, or close enough anyway */ |
1610 | 0 | ctx->last_nonblock_timeval = now; |
1611 | 0 | ret = TRUE; |
1612 | 0 | } |
1613 | 0 | guess_cost = ctx->cost * |
1614 | 0 | (SEARCH_MAX_NONBLOCK_USECS / (double)usecs); |
1615 | 0 | if (usecs < SEARCH_RECALC_MIN_USECS) { |
1616 | | /* the estimate may not be very good since we spent |
1617 | | so little time doing this search. don't allow huge changes |
1618 | | to the guess, but allow anyway large enough so that we can |
1619 | | move to right direction. */ |
1620 | 0 | if (guess_cost > ctx->next_time_check_cost*3) |
1621 | 0 | guess_cost = ctx->next_time_check_cost*3; |
1622 | 0 | else if (guess_cost < ctx->next_time_check_cost/3) |
1623 | 0 | guess_cost = ctx->next_time_check_cost/3; |
1624 | 0 | } |
1625 | 0 | if (ret) |
1626 | 0 | ctx->cost = 0; |
1627 | 0 | ctx->next_time_check_cost = guess_cost; |
1628 | 0 | return ret; |
1629 | 0 | } |
1630 | | |
1631 | | int index_storage_search_next_match_mail(struct mail_search_context *_ctx, |
1632 | | struct mail *mail) |
1633 | 0 | { |
1634 | 0 | struct index_search_context *ctx = |
1635 | 0 | container_of(_ctx, struct index_search_context, mail_ctx); |
1636 | 0 | struct index_mail *imail = INDEX_MAIL(mail); |
1637 | 0 | int match; |
1638 | |
|
1639 | 0 | ctx->cur_mail = mail; |
1640 | | /* mail's access_type is SEARCH only while using it to process |
1641 | | the search query. afterwards the mail can still be accessed |
1642 | | for fetching. */ |
1643 | 0 | ctx->cur_mail->access_type = MAIL_ACCESS_TYPE_SEARCH; |
1644 | 0 | T_BEGIN { |
1645 | 0 | match = search_match_next(ctx); |
1646 | 0 | } T_END; |
1647 | 0 | ctx->cur_mail->access_type = MAIL_ACCESS_TYPE_DEFAULT; |
1648 | 0 | ctx->cur_mail = NULL; |
1649 | |
|
1650 | 0 | i_assert(imail->data.search_results == NULL); |
1651 | 0 | if (match < 0) { |
1652 | | /* result isn't known yet, do a prefetch and |
1653 | | finish later */ |
1654 | 0 | imail->data.search_results = |
1655 | 0 | buffer_create_dynamic(imail->mail.data_pool, 64); |
1656 | 0 | mail_search_args_result_serialize(_ctx->args, |
1657 | 0 | imail->data.search_results); |
1658 | 0 | } |
1659 | |
|
1660 | 0 | mail_search_args_reset(_ctx->args->args, FALSE); |
1661 | |
|
1662 | 0 | if (match != 0) { |
1663 | | /* either matched or result is still unknown. |
1664 | | anyway we're far enough now that we probably want |
1665 | | to update the access_parts. the only problem here is |
1666 | | if searching would want fewer access_parts than the |
1667 | | fetching part, but that's probably not a big problem |
1668 | | usually. */ |
1669 | 0 | index_mail_update_access_parts_pre(mail); |
1670 | 0 | return 1; |
1671 | 0 | } |
1672 | | |
1673 | | /* non-match */ |
1674 | 0 | if (_ctx->args->stop_on_nonmatch) |
1675 | 0 | return -1; |
1676 | 0 | return 0; |
1677 | 0 | } |
1678 | | |
1679 | | static int search_more_with_mail(struct index_search_context *ctx, |
1680 | | struct mail *mail) |
1681 | 0 | { |
1682 | 0 | struct mail_search_context *_ctx = &ctx->mail_ctx; |
1683 | 0 | struct mailbox *box = _ctx->transaction->box; |
1684 | 0 | unsigned long long cost1, cost2; |
1685 | 0 | int ret; |
1686 | |
|
1687 | 0 | if (search_would_block(ctx)) { |
1688 | | /* this lookup is useful when a large number of |
1689 | | messages match */ |
1690 | 0 | return 0; |
1691 | 0 | } |
1692 | | |
1693 | 0 | mail_search_args_reset(_ctx->args->args, FALSE); |
1694 | |
|
1695 | 0 | cost1 = search_get_cost(mail->transaction); |
1696 | 0 | ret = -1; |
1697 | 0 | for (;;) { |
1698 | 0 | bool more; |
1699 | 0 | T_BEGIN { |
1700 | 0 | more = box->v.search_next_update_seq(_ctx); |
1701 | 0 | } T_END; |
1702 | 0 | if (!more) |
1703 | 0 | break; |
1704 | 0 | mail_set_seq(mail, _ctx->seq); |
1705 | |
|
1706 | 0 | T_BEGIN { |
1707 | 0 | ret = box->v.search_next_match_mail(_ctx, mail); |
1708 | 0 | } T_END; |
1709 | 0 | if (ret != 0) |
1710 | 0 | break; |
1711 | | |
1712 | 0 | cost2 = search_get_cost(mail->transaction); |
1713 | 0 | ctx->cost += cost2 - cost1; |
1714 | 0 | cost1 = cost2; |
1715 | |
|
1716 | 0 | if (search_would_block(ctx)) |
1717 | 0 | break; |
1718 | 0 | ret = -1; |
1719 | 0 | } |
1720 | 0 | cost2 = search_get_cost(mail->transaction); |
1721 | 0 | ctx->cost += cost2 - cost1; |
1722 | 0 | return ret; |
1723 | 0 | } |
1724 | | |
1725 | | struct mail *index_search_get_mail(struct index_search_context *ctx) |
1726 | 0 | { |
1727 | 0 | struct index_mail *imail; |
1728 | 0 | struct mail *const *mails, *mail; |
1729 | 0 | unsigned int count; |
1730 | |
|
1731 | 0 | if (ctx->mail_ctx.unused_mail_idx == ctx->mail_ctx.max_mails) |
1732 | 0 | return NULL; |
1733 | | |
1734 | 0 | mails = array_get(&ctx->mail_ctx.mails, &count); |
1735 | 0 | if (ctx->mail_ctx.unused_mail_idx < count) |
1736 | 0 | return mails[ctx->mail_ctx.unused_mail_idx]; |
1737 | | |
1738 | 0 | mail = mail_alloc(ctx->mail_ctx.transaction, |
1739 | 0 | ctx->mail_ctx.wanted_fields, |
1740 | 0 | ctx->mail_ctx.wanted_headers); |
1741 | 0 | imail = INDEX_MAIL(mail); |
1742 | 0 | imail->mail.search_mail = TRUE; |
1743 | 0 | ctx->mail_ctx.transaction->stats_track = TRUE; |
1744 | |
|
1745 | 0 | array_push_back(&ctx->mail_ctx.mails, &mail); |
1746 | 0 | return mail; |
1747 | 0 | } |
1748 | | |
1749 | | static int search_more_with_prefetching(struct index_search_context *ctx, |
1750 | | struct mail **mail_r) |
1751 | 0 | { |
1752 | 0 | struct mail *mail, *const *mails; |
1753 | 0 | unsigned int count; |
1754 | 0 | int ret = 0; |
1755 | |
|
1756 | 0 | while ((mail = index_search_get_mail(ctx)) != NULL) { |
1757 | 0 | T_BEGIN { |
1758 | 0 | ret = search_more_with_mail(ctx, mail); |
1759 | 0 | } T_END; |
1760 | 0 | if (ret <= 0) |
1761 | 0 | break; |
1762 | | |
1763 | 0 | if (ctx->mail_ctx.sort_program != NULL) { |
1764 | | /* don't prefetch when using a sort program, |
1765 | | since the mails' access order will change */ |
1766 | 0 | i_assert(ctx->mail_ctx.unused_mail_idx == 0); |
1767 | 0 | *mail_r = mail; |
1768 | 0 | return 1; |
1769 | 0 | } |
1770 | 0 | if (mail_prefetch(mail) && ctx->mail_ctx.unused_mail_idx == 0) { |
1771 | | /* no prefetching done, return it immediately */ |
1772 | 0 | *mail_r = mail; |
1773 | 0 | return 1; |
1774 | 0 | } |
1775 | 0 | ctx->mail_ctx.unused_mail_idx++; |
1776 | 0 | } |
1777 | | |
1778 | 0 | if (mail != NULL) { |
1779 | 0 | if (ret == 0) { |
1780 | | /* wait */ |
1781 | 0 | return 0; |
1782 | 0 | } |
1783 | 0 | i_assert(ret < 0); |
1784 | 0 | if (ctx->mail_ctx.unused_mail_idx == 0) { |
1785 | | /* finished */ |
1786 | 0 | return -1; |
1787 | 0 | } |
1788 | 0 | } else { |
1789 | | /* prefetch buffer is full. */ |
1790 | 0 | } |
1791 | | |
1792 | | /* return the next message */ |
1793 | 0 | i_assert(ctx->mail_ctx.unused_mail_idx > 0); |
1794 | | |
1795 | 0 | mails = array_get(&ctx->mail_ctx.mails, &count); |
1796 | 0 | *mail_r = mails[0]; |
1797 | 0 | if (--ctx->mail_ctx.unused_mail_idx > 0) { |
1798 | 0 | array_pop_front(&ctx->mail_ctx.mails); |
1799 | 0 | array_push_back(&ctx->mail_ctx.mails, mail_r); |
1800 | 0 | } |
1801 | 0 | T_BEGIN { |
1802 | 0 | index_mail_update_access_parts_post(*mail_r); |
1803 | 0 | } T_END; |
1804 | 0 | return 1; |
1805 | 0 | } |
1806 | | |
1807 | | static bool search_finish_prefetch(struct index_search_context *ctx, |
1808 | | struct index_mail *imail) |
1809 | 0 | { |
1810 | 0 | int ret; |
1811 | |
|
1812 | 0 | i_assert(imail->mail.mail.lookup_abort == MAIL_LOOKUP_ABORT_NEVER); |
1813 | | |
1814 | 0 | ctx->cur_mail = &imail->mail.mail; |
1815 | 0 | ctx->cur_mail->access_type = MAIL_ACCESS_TYPE_SEARCH; |
1816 | 0 | mail_search_args_result_deserialize(ctx->mail_ctx.args, |
1817 | 0 | imail->data.search_results->data, |
1818 | 0 | imail->data.search_results->used); |
1819 | 0 | T_BEGIN { |
1820 | 0 | ret = search_match_once(ctx); |
1821 | 0 | search_match_finish(ctx, ret); |
1822 | 0 | } T_END; |
1823 | 0 | ctx->cur_mail->access_type = MAIL_ACCESS_TYPE_DEFAULT; |
1824 | 0 | ctx->cur_mail = NULL; |
1825 | 0 | return ret > 0; |
1826 | 0 | } |
1827 | | |
1828 | | static int search_more(struct index_search_context *ctx, |
1829 | | struct mail **mail_r) |
1830 | 0 | { |
1831 | 0 | struct index_mail *imail; |
1832 | 0 | int ret; |
1833 | |
|
1834 | 0 | while ((ret = search_more_with_prefetching(ctx, mail_r)) > 0) { |
1835 | 0 | imail = INDEX_MAIL(*mail_r); |
1836 | 0 | if (imail->data.search_results == NULL) |
1837 | 0 | break; |
1838 | | |
1839 | | /* prefetch running - searching wasn't finished yet */ |
1840 | 0 | if (search_finish_prefetch(ctx, imail)) |
1841 | 0 | break; |
1842 | | /* search finished as non-match */ |
1843 | 0 | if (ctx->mail_ctx.args->stop_on_nonmatch) { |
1844 | 0 | ret = -1; |
1845 | 0 | break; |
1846 | 0 | } |
1847 | 0 | mailbox_search_notify(ctx->box, &ctx->mail_ctx); |
1848 | 0 | } |
1849 | 0 | return ret; |
1850 | 0 | } |
1851 | | |
1852 | | static bool |
1853 | | index_storage_search_is_interrupted(struct index_search_context *ctx) |
1854 | 0 | { |
1855 | 0 | if (!master_service_is_killed(master_service)) |
1856 | 0 | return FALSE; |
1857 | | |
1858 | 0 | struct timeval tv_now; |
1859 | 0 | i_gettimeofday(&tv_now); |
1860 | |
|
1861 | 0 | if (ctx->interrupt_start_time.tv_sec == 0) { |
1862 | 0 | master_service_get_kill_time(master_service, |
1863 | 0 | &ctx->interrupt_start_time); |
1864 | 0 | i_assert(ctx->interrupt_start_time.tv_sec > 0); |
1865 | 0 | } else if (timeval_diff_msecs(&tv_now, &ctx->interrupt_start_time) >= |
1866 | 0 | SEARCH_INTERRUPT_DELAY_MSECS) { |
1867 | 0 | mail_storage_set_error(ctx->box->storage, |
1868 | 0 | MAIL_ERROR_INTERRUPTED, |
1869 | 0 | MAIL_ERRSTR_INTERRUPTED); |
1870 | 0 | search_set_failed(ctx); |
1871 | 0 | return TRUE; |
1872 | 0 | } |
1873 | 0 | return FALSE; |
1874 | 0 | } |
1875 | | |
1876 | | bool index_storage_search_next_nonblock(struct mail_search_context *_ctx, |
1877 | | struct mail **mail_r, bool *tryagain_r) |
1878 | 0 | { |
1879 | 0 | struct index_search_context *ctx = (struct index_search_context *)_ctx; |
1880 | 0 | struct mail *mail, *const *mailp; |
1881 | 0 | uint32_t seq; |
1882 | 0 | int ret; |
1883 | |
|
1884 | 0 | *tryagain_r = FALSE; |
1885 | |
|
1886 | 0 | if (index_storage_search_is_interrupted(ctx)) |
1887 | 0 | return FALSE; |
1888 | | |
1889 | 0 | if (_ctx->sort_program == NULL) { |
1890 | 0 | ret = search_more(ctx, &mail); |
1891 | 0 | if (ret == 0) { |
1892 | 0 | *tryagain_r = TRUE; |
1893 | 0 | return FALSE; |
1894 | 0 | } |
1895 | 0 | if (ret < 0) |
1896 | 0 | return FALSE; |
1897 | 0 | *mail_r = mail; |
1898 | 0 | return TRUE; |
1899 | 0 | } |
1900 | | |
1901 | 0 | if (!ctx->sorted) { |
1902 | 0 | while ((ret = search_more(ctx, &mail)) > 0) T_BEGIN { |
1903 | 0 | index_sort_list_add(_ctx->sort_program, mail); |
1904 | 0 | } T_END; |
1905 | | |
1906 | 0 | if (ret == 0) { |
1907 | 0 | *tryagain_r = TRUE; |
1908 | 0 | return FALSE; |
1909 | 0 | } |
1910 | | /* finished searching the messages. now sort them and start |
1911 | | returning the messages. */ |
1912 | 0 | ctx->sorted = TRUE; |
1913 | 0 | index_sort_list_finish(_ctx->sort_program); |
1914 | 0 | } |
1915 | | |
1916 | | /* everything searched at this point already. just returning |
1917 | | matches from sort list. FIXME: we could do prefetching here also. */ |
1918 | 0 | if (!index_sort_list_next(_ctx->sort_program, &seq)) |
1919 | 0 | return FALSE; |
1920 | | |
1921 | 0 | mailp = array_front(&ctx->mail_ctx.mails); |
1922 | 0 | mail_set_seq(*mailp, seq); |
1923 | 0 | index_mail_update_access_parts_pre(*mailp); |
1924 | 0 | index_mail_update_access_parts_post(*mailp); |
1925 | 0 | *mail_r = *mailp; |
1926 | 0 | return TRUE; |
1927 | 0 | } |
1928 | | |
1929 | | bool index_storage_search_match_index_args(struct mail_search_context *_ctx) |
1930 | 0 | { |
1931 | 0 | struct index_search_context *ctx = |
1932 | 0 | container_of(_ctx, struct index_search_context, mail_ctx); |
1933 | 0 | int ret; |
1934 | | |
1935 | | /* Match the args that can be looked up from the index for the |
1936 | | already set _ctx->seq. Args that need the mail itself are left |
1937 | | with an unknown result for the caller to handle. */ |
1938 | 0 | ret = mail_search_args_foreach(_ctx->args->args, search_seqset_arg, ctx); |
1939 | 0 | if (ret != 0) { |
1940 | 0 | ret = mail_search_args_foreach(_ctx->args->args, |
1941 | 0 | search_index_arg, ctx); |
1942 | 0 | } |
1943 | 0 | return ret != 0; |
1944 | 0 | } |
1945 | | |
1946 | | bool index_storage_search_next_update_seq(struct mail_search_context *_ctx) |
1947 | 0 | { |
1948 | 0 | struct index_search_context *ctx = (struct index_search_context *)_ctx; |
1949 | 0 | uint32_t uid; |
1950 | 0 | int ret; |
1951 | |
|
1952 | 0 | if (_ctx->seq == 0) { |
1953 | | /* first time */ |
1954 | 0 | _ctx->seq = ctx->seq1; |
1955 | 0 | } else { |
1956 | 0 | _ctx->seq++; |
1957 | 0 | } |
1958 | |
|
1959 | 0 | if (!ctx->have_seqsets && !ctx->have_index_args && |
1960 | 0 | !ctx->have_nonmatch_always && _ctx->update_result == NULL) { |
1961 | 0 | _ctx->progress_cur = _ctx->seq; |
1962 | 0 | return _ctx->seq <= ctx->seq2; |
1963 | 0 | } |
1964 | | |
1965 | 0 | ret = 0; |
1966 | 0 | while (_ctx->seq <= ctx->seq2) { |
1967 | | /* check if the sequence matches */ |
1968 | 0 | ret = mail_search_args_foreach(ctx->mail_ctx.args->args, |
1969 | 0 | search_seqset_arg, ctx); |
1970 | 0 | if (ret != 0 && ctx->have_index_args) { |
1971 | | /* check if flags/keywords match before anything else |
1972 | | is done. mail_set_seq() can be a bit slow. */ |
1973 | 0 | ret = mail_search_args_foreach(ctx->mail_ctx.args->args, |
1974 | 0 | search_index_arg, ctx); |
1975 | 0 | } |
1976 | 0 | if (ret != 0 && _ctx->update_result != NULL) { |
1977 | | /* see if this message never matches */ |
1978 | 0 | mail_index_lookup_uid(ctx->view, _ctx->seq, &uid); |
1979 | 0 | if (seq_range_exists(&_ctx->update_result->never_uids, |
1980 | 0 | uid)) |
1981 | 0 | ret = 0; |
1982 | 0 | } |
1983 | 0 | if (ret != 0) |
1984 | 0 | break; |
1985 | | |
1986 | | /* doesn't, try next one */ |
1987 | 0 | _ctx->seq++; |
1988 | 0 | mail_search_args_reset(ctx->mail_ctx.args->args, FALSE); |
1989 | 0 | } |
1990 | |
|
1991 | 0 | if (ret != 0 && _ctx->update_result != NULL) { |
1992 | 0 | mail_index_lookup_uid(ctx->view, _ctx->seq, &uid); |
1993 | 0 | if (seq_range_exists(&_ctx->update_result->uids, uid)) { |
1994 | | /* we already know that the static data |
1995 | | matches. mark it as such. */ |
1996 | 0 | search_set_static_matches(_ctx->args->args); |
1997 | 0 | } |
1998 | 0 | } |
1999 | 0 | ctx->mail_ctx.progress_cur = _ctx->seq; |
2000 | 0 | return ret != 0; |
2001 | 0 | } |