~ubuntu-branches/ubuntu/wily/dovecot/wily

« back to all changes in this revision

Viewing changes to src/imap/imap-search.c

  • Committer: Bazaar Package Importer
  • Author(s): CHuck Short, Chuck Short
  • Date: 2009-11-06 00:47:29 UTC
  • mfrom: (4.1.9 squeeze)
  • Revision ID: james.westby@ubuntu.com-20091106004729-i39n7v9e7d4h51f6
Tags: 1:1.2.6-1ubuntu1
* Merge from debian testing, remaining changes:
  Add new binary pkg dovecot-postfix that integrates postfix and dovecot
  automatically: (LP: #164837)
  + debian/control:
    - add new binary with short description
    - set Architecture all for dovecot-postfix (LP: #329878)
  + debian/dovecot-postfix.postinst:
    - create initial certificate symlinks to snakeoil.
    - set up postfix with postconf to:
      - use Maildir/ as the default mailbox.
      - use dovecot as the sasl authentication server.
      - use dovecot LDA (deliver).
      - use tls for smtp{d} services.
    - fix certificates paths in postfix' main.cf
    - add reject_unauth_destination to postfix' recipient restrictions
    - add reject_unknown_sender_domain to postfix' sender restrictions
    - rename configuration name on remove, delete on purge
    - restart dovecot after linking certificates
    - handle use case when postfix is unconfigurated
   + debian/dovecot-postfix.dirs: create backup directory for postfix's configuration
   + restart postfix and dovecot.
   + debian/dovecot-postfix.postrm:
     - remove all dovecot related configuration from postfix.
     - restart postfix and dovecot.
   + debian/dovecot-common.init:
     - check if /etc/dovecot/dovecot-postfix.conf exists and use it
       as the configuration file if so.
   + debian/patches/warning-ubuntu-postfix.dpatch
     - add warning about dovecot-postfix.conf in dovecot default 
       configuration file
   + debian/patches/dovecot-postfix.conf.diff:
     - Ubuntu server custom changes to the default dovecot configuration for
       better interfation with postfix
     - enable sieve plugin
   + debian/patches/dovecot-postfix.conf.diff:
     + Ubuntu server custom changes to the default dovecot configuration for
       better integration with postfix:
       - enable imap, pop3, imaps, pop3s and managesieve by default.
       - enable dovecot LDA (deliver).
       - enable SASL auth socket in postfix private directory.
   + debian/rules:
     - copy, patch and install dovecot-postfix.conf in /etc/dovecot/.
     - build architecure independent packages too
   + Use Snakeoil SSL certificates by default.
     - debian/control: Depend on ssl-cert.
     - debian/patches/ssl-cert-snakeoil.dpatch: Change default SSL cert
       paths to snakeoil.
     - debian/dovecot-common.postinst: Relax grep for SSL_* a bit.
   + Add autopkgtest to debian/tests/*.
   + Fast TearDown: Update the lsb init header to not stop in level 6.
   + Add ufw integration:
     - Created debian/dovecot-common.ufw.profile.
     - debian/rules:
       + install profile
     - debian/control:
       + Suggest ufw
   + debian/{control,rules}: enable PIE hardening.
   + dovecot-imapd, dovecot-pop3: Replaces dovecot-common (<< 1:1.1). LP: #254721
   + debian/control:
     - Update Vcs-* headers.
   + debian/rules:
     - Create emtpy stamp.h.in files in dovecot-sieve/ and dovecot-managesieve/
       if they're not there since empty files are not included in the diff.gz 
       file.
   + Add SMTP-AUTH support for Outlook (login auth mechanism)
   + Dropped:
     - debian/patches/security-CVE-2009-3235: Applied upstream.
     - debian/patches/fix-pop3-assertion.dpatch: Applied upstream.
     - dovecot-sieve and dovecot-managesieve: Use the debian patches instead.

  [Chuck Short]
  - Updated dovecot-sieve to 0.1.13.
  - Updated dovecot-managesieve to 0.11.9.

Show diffs side-by-side

added added

removed removed

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