Avoid C++ keywords in headers.
[platform/upstream/evolution-data-server.git] / camel / camel-folder-search.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  *  Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /* This is a helper class for folders to implement the search function.
23    It implements enough to do basic searches on folders that can provide
24    an in-memory summary and a body index. */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 /* POSIX requires <sys/types.h> be included before <regex.h> */
31 #include <sys/types.h>
32
33 #include <ctype.h>
34 #include <regex.h>
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <glib.h>
39 #include <glib/gi18n-lib.h>
40
41 #include "camel-exception.h"
42 #include "camel-folder-search.h"
43 #include "camel-folder-thread.h"
44 #include "camel-iconv.h"
45 #include "camel-medium.h"
46 #include "camel-mime-message.h"
47 #include "camel-multipart.h"
48 #include "camel-search-private.h"
49 #include "camel-stream-mem.h"
50 #include "camel-db.h"
51 #include "camel-debug.h"
52 #include "camel-store.h"
53 #include "camel-vee-folder.h"
54 #include "camel-string-utils.h"
55 #include "camel-search-sql.h"
56 #include "camel-search-sql-sexp.h"
57
58 #define d(x)
59 #define r(x)
60 #define dd(x) if (camel_debug("search")) x
61
62 struct _CamelFolderSearchPrivate {
63         CamelException *ex;
64
65         CamelFolderThread *threads;
66         GHashTable *threads_hash;
67 };
68
69 #define _PRIVATE(o) (((CamelFolderSearch *)(o))->priv)
70
71 static ESExpResult *search_not(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
72
73 static ESExpResult *search_header_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
74 static ESExpResult *search_header_matches(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
75 static ESExpResult *search_header_starts_with(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
76 static ESExpResult *search_header_ends_with(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
77 static ESExpResult *search_header_exists(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
78 static ESExpResult *search_header_soundex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
79 static ESExpResult *search_header_regex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
80 static ESExpResult *search_header_full_regex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
81 static ESExpResult *search_match_all(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search);
82 static ESExpResult *search_match_threads(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *s);
83 static ESExpResult *search_body_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
84 static ESExpResult *search_body_regex(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
85 static ESExpResult *search_user_flag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
86 static ESExpResult *search_user_tag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
87 static ESExpResult *search_system_flag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
88 static ESExpResult *search_get_sent_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
89 static ESExpResult *search_get_received_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
90 static ESExpResult *search_get_current_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
91 static ESExpResult *search_get_size(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
92 static ESExpResult *search_uid(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
93 static ESExpResult *search_message_location(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s);
94
95 static ESExpResult *search_dummy(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search);
96
97 static void camel_folder_search_class_init (CamelFolderSearchClass *klass);
98 static void camel_folder_search_init       (CamelFolderSearch *obj);
99 static void camel_folder_search_finalize   (CamelObject *obj);
100
101 static gint read_uid_callback (gpointer  ref, gint ncol, gchar ** cols, gchar **name);
102
103 static CamelObjectClass *camel_folder_search_parent;
104
105 static void
106 camel_folder_search_class_init (CamelFolderSearchClass *klass)
107 {
108         camel_folder_search_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
109
110         klass->not = search_not;
111
112         klass->match_all = search_match_all;
113         klass->match_threads = search_match_threads;
114         klass->body_contains = search_body_contains;
115         klass->body_regex = search_body_regex;
116         klass->header_contains = search_header_contains;
117         klass->header_matches = search_header_matches;
118         klass->header_starts_with = search_header_starts_with;
119         klass->header_ends_with = search_header_ends_with;
120         klass->header_exists = search_header_exists;
121         klass->header_soundex = search_header_soundex;
122         klass->header_regex = search_header_regex;
123         klass->header_full_regex = search_header_full_regex;
124         klass->user_tag = search_user_tag;
125         klass->user_flag = search_user_flag;
126         klass->system_flag = search_system_flag;
127         klass->get_sent_date = search_get_sent_date;
128         klass->get_received_date = search_get_received_date;
129         klass->get_current_date = search_get_current_date;
130         klass->get_size = search_get_size;
131         klass->uid = search_uid;
132         klass->message_location = search_message_location;
133 }
134
135 static void
136 camel_folder_search_init (CamelFolderSearch *obj)
137 {
138         struct _CamelFolderSearchPrivate *p;
139
140         p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
141
142         obj->sexp = e_sexp_new();
143 }
144
145 static void
146 camel_folder_search_finalize (CamelObject *obj)
147 {
148         CamelFolderSearch *search = (CamelFolderSearch *)obj;
149         struct _CamelFolderSearchPrivate *p = _PRIVATE(obj);
150
151         if (search->sexp)
152                 e_sexp_unref(search->sexp);
153
154         g_free(search->last_search);
155         g_free(p);
156 }
157
158 CamelType
159 camel_folder_search_get_type (void)
160 {
161         static CamelType type = CAMEL_INVALID_TYPE;
162
163         if (type == CAMEL_INVALID_TYPE) {
164                 type = camel_type_register (camel_object_get_type (), "CamelFolderSearch",
165                                             sizeof (CamelFolderSearch),
166                                             sizeof (CamelFolderSearchClass),
167                                             (CamelObjectClassInitFunc) camel_folder_search_class_init,
168                                             NULL,
169                                             (CamelObjectInitFunc) camel_folder_search_init,
170                                             (CamelObjectFinalizeFunc) camel_folder_search_finalize);
171         }
172
173         return type;
174 }
175
176 #ifdef offsetof
177 #define CAMEL_STRUCT_OFFSET(type, field)        ((gint) offsetof (type, field))
178 #else
179 #define CAMEL_STRUCT_OFFSET(type, field)        ((gint) ((gchar *) &((type *) 0)->field))
180 #endif
181
182 static struct {
183         const gchar *name;
184         gint offset;
185         gint flags;             /* 0x02 = immediate, 0x01 = always enter */
186 } builtins[] = {
187         /* these have default implementations in e-sexp */
188         { "and", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, and), 2 },
189         { "or", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, or), 2 },
190         /* we need to override this one though to implement an 'array not' */
191         { "not", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, not), 0 },
192         { "<", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, lt), 2 },
193         { ">", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, gt), 2 },
194         { "=", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, eq), 2 },
195
196         /* these we have to use our own default if there is none */
197         /* they should all be defined in the language? so it parses, or should they not?? */
198         { "match-all", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_all), 3 },
199         { "match-threads", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_threads), 3 },
200         { "body-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, body_contains), 1 },
201         { "body-regex",  CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, body_regex), 1  },
202         { "header-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_contains), 1 },
203         { "header-matches", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_matches), 1 },
204         { "header-starts-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_starts_with), 1 },
205         { "header-ends-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_ends_with), 1 },
206         { "header-exists", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_exists), 1 },
207         { "header-soundex", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_soundex), 1 },
208         { "header-regex", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_regex), 1 },
209         { "header-full-regex", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_full_regex), 1 },
210         { "user-tag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_tag), 1 },
211         { "user-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_flag), 1 },
212         { "system-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, system_flag), 1 },
213         { "get-sent-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_sent_date), 1 },
214         { "get-received-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_received_date), 1 },
215         { "get-current-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_current_date), 1 },
216         { "get-size", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_size), 1 },
217         { "uid", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, uid), 1 },
218         { "message-location", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, message_location), 1 },
219 };
220
221 void
222 camel_folder_search_construct (CamelFolderSearch *search)
223 {
224         gint i;
225         CamelFolderSearchClass *klass = (CamelFolderSearchClass *)CAMEL_OBJECT_GET_CLASS(search);
226
227         for (i = 0; i < G_N_ELEMENTS (builtins); i++) {
228                 gpointer func;
229                 /* c is sure messy sometimes */
230                 func = *((gpointer *)(((gchar *)klass)+builtins[i].offset));
231                 if (func == NULL && builtins[i].flags&1) {
232                         g_warning("Search class doesn't implement '%s' method: %s", builtins[i].name, camel_type_to_name(CAMEL_OBJECT_GET_CLASS(search)));
233                         func = (gpointer)search_dummy;
234                 }
235                 if (func != NULL) {
236                         if (builtins[i].flags&2) {
237                                 e_sexp_add_ifunction(search->sexp, 0, builtins[i].name, (ESExpIFunc *)func, search);
238                         } else {
239                                 e_sexp_add_function(search->sexp, 0, builtins[i].name, (ESExpFunc *)func, search);
240                         }
241                 }
242         }
243 }
244
245 /**
246  * camel_folder_search_new:
247  *
248  * Create a new CamelFolderSearch object.
249  *
250  * A CamelFolderSearch is a subclassable, extensible s-exp
251  * evaluator which enforces a particular set of s-expressions.
252  * Particular methods may be overriden by an implementation to
253  * implement a search for any sort of backend.
254  *
255  * Returns: A new CamelFolderSearch widget.
256  **/
257 CamelFolderSearch *
258 camel_folder_search_new (void)
259 {
260         CamelFolderSearch *new = CAMEL_FOLDER_SEARCH (camel_object_new (camel_folder_search_get_type ()));
261
262         camel_folder_search_construct(new);
263         return new;
264 }
265
266 /**
267  * camel_folder_search_set_folder:
268  * @search:
269  * @folder: A folder.
270  *
271  * Set the folder attribute of the search.  This is currently unused, but
272  * could be used to perform a slow-search when indexes and so forth are not
273  * available.  Or for use by subclasses.
274  **/
275 void
276 camel_folder_search_set_folder(CamelFolderSearch *search, CamelFolder *folder)
277 {
278         search->folder = folder;
279 }
280
281 /**
282  * camel_folder_search_set_summary:
283  * @search:
284  * @summary: An array of CamelMessageInfo pointers.
285  *
286  * Set the array of summary objects representing the span of the search.
287  *
288  * If this is not set, then a subclass must provide the functions
289  * for searching headers and for the match-all operator.
290  **/
291 void
292 camel_folder_search_set_summary(CamelFolderSearch *search, GPtrArray *summary)
293 {
294         search->summary = summary;
295 }
296
297 /**
298  * camel_folder_search_set_body_index:
299  * @search:
300  * @index:
301  *
302  * Set the index representing the contents of all messages
303  * in this folder.  If this is not set, then the folder implementation
304  * should sub-class the CamelFolderSearch and provide its own
305  * body-contains function.
306  **/
307 void
308 camel_folder_search_set_body_index(CamelFolderSearch *search, CamelIndex *index)
309 {
310         if (search->body_index)
311                 camel_object_unref((CamelObject *)search->body_index);
312         search->body_index = index;
313         if (index)
314                 camel_object_ref((CamelObject *)index);
315 }
316
317 /**
318  * camel_folder_search_execute_expression:
319  * @search:
320  * @expr:
321  * @ex:
322  *
323  * Execute the search expression @expr, returning an array of
324  * all matches as a GPtrArray of uid's of matching messages.
325  *
326  * Note that any settings such as set_body_index(), set_folder(),
327  * and so on are reset to #NULL once the search has completed.
328  *
329  * TODO: The interface should probably return summary items instead
330  * (since they are much more useful to any client).
331  *
332  * Returns: A GPtrArray of strings of all matching messages.
333  * This must only be freed by camel_folder_search_free_result.
334  **/
335 GPtrArray *
336 camel_folder_search_execute_expression(CamelFolderSearch *search, const gchar *expr, CamelException *ex)
337 {
338         ESExpResult *r;
339         GPtrArray *matches;
340         gint i;
341         GHashTable *results;
342         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
343
344         p->ex = ex;
345
346         /* only re-parse if the search has changed */
347         if (search->last_search == NULL
348             || strcmp(search->last_search, expr)) {
349                 e_sexp_input_text(search->sexp, expr, strlen(expr));
350                 if (e_sexp_parse(search->sexp) == -1) {
351                         camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
352                         return NULL;
353                 }
354
355                 g_free(search->last_search);
356                 search->last_search = g_strdup(expr);
357         }
358         r = e_sexp_eval(search->sexp);
359         if (r == NULL) {
360                 if (!camel_exception_is_set(ex))
361                         camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
362                 return NULL;
363         }
364
365         matches = g_ptr_array_new();
366
367         /* now create a folder summary to return?? */
368         if (r->type == ESEXP_RES_ARRAY_PTR) {
369                 d(printf("got result ...\n"));
370                 if (search->summary) {
371                         /* reorder result in summary order */
372                         results = g_hash_table_new(g_str_hash, g_str_equal);
373                         for (i=0;i<r->value.ptrarray->len;i++) {
374                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
375                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
376                         }
377                         for (i=0;i<search->summary->len;i++) {
378                                 gchar *uid = g_ptr_array_index(search->summary, i);
379                                 if (g_hash_table_lookup(results, uid)) {
380                                         g_ptr_array_add(matches, (gpointer) camel_pstring_strdup(uid));
381                                 }
382                         }
383                         g_hash_table_destroy(results);
384                 } else {
385                         for (i=0;i<r->value.ptrarray->len;i++) {
386                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
387                                 g_ptr_array_add(matches, (gpointer) camel_pstring_strdup(g_ptr_array_index(r->value.ptrarray, i)));
388                         }
389                 }
390         } else {
391                 g_warning("Search returned an invalid result type");
392         }
393
394         e_sexp_result_free(search->sexp, r);
395
396         if (p->threads)
397                 camel_folder_thread_messages_unref(p->threads);
398         if (p->threads_hash)
399                 g_hash_table_destroy(p->threads_hash);
400
401         p->threads = NULL;
402         p->threads_hash = NULL;
403         search->folder = NULL;
404         search->summary = NULL;
405         search->current = NULL;
406         search->body_index = NULL;
407
408         return matches;
409 }
410
411 /**
412  * camel_folder_search_count:
413  * @search:
414  * @expr:
415  * @uids: to search against, NULL for all uid's.
416  * @ex:
417  *
418  * Run a search.  Search must have had Folder already set on it, and
419  * it must implement summaries.
420  *
421  * Returns: Number of messages that match the query.
422  *
423  * Since: 2.26
424  **/
425
426 guint32
427 camel_folder_search_count(CamelFolderSearch *search, const gchar *expr, CamelException *ex)
428 {
429         ESExpResult *r;
430         GPtrArray *summary_set;
431         gint i;
432         CamelDB *cdb;
433         gchar *sql_query, *tmp, *tmp1;
434         GHashTable *results;
435         guint32 count = 0;
436
437         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
438
439         g_assert(search->folder);
440
441         p->ex = ex;
442
443         /* We route body-contains search and thread based search through memory and not via db. */
444         if (strstr((const gchar *) expr, "body-contains") || strstr((const gchar *) expr, "match-threads")) {
445                 /* setup our search list only contains those we're interested in */
446                 search->summary = camel_folder_get_summary(search->folder);
447
448                 summary_set = search->summary;
449
450                 /* only re-parse if the search has changed */
451                 if (search->last_search == NULL
452                     || strcmp(search->last_search, expr)) {
453                         e_sexp_input_text(search->sexp, expr, strlen(expr));
454                         if (e_sexp_parse(search->sexp) == -1) {
455                                 camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
456                                 goto fail;
457                         }
458
459                         g_free(search->last_search);
460                         search->last_search = g_strdup(expr);
461                 }
462                 r = e_sexp_eval(search->sexp);
463                 if (r == NULL) {
464                         if (!camel_exception_is_set(ex))
465                                 camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
466                         goto fail;
467                 }
468
469                 /* now create a folder summary to return?? */
470                 if (r->type == ESEXP_RES_ARRAY_PTR) {
471                         d(printf("got result\n"));
472
473                         /* reorder result in summary order */
474                         results = g_hash_table_new(g_str_hash, g_str_equal);
475                         for (i=0;i<r->value.ptrarray->len;i++) {
476                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
477                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
478                         }
479
480                         for (i=0;i<summary_set->len;i++) {
481                                 gchar *uid = g_ptr_array_index(summary_set, i);
482                                 if (g_hash_table_lookup(results, uid))
483                                         count++;
484                         }
485                         g_hash_table_destroy(results);
486                 }
487
488                 e_sexp_result_free(search->sexp, r);
489
490         } else {
491                 /* Sync the db, so that we search the db for changes */
492                 camel_folder_summary_save_to_db (search->folder->summary, ex);
493
494                 dd(printf ("sexp is : [%s]\n", expr));
495                 if (g_getenv("SQL_SEARCH_OLD"))
496                         sql_query = camel_sexp_to_sql (expr);
497                 else
498                         sql_query = camel_sexp_to_sql_sexp (expr);
499                 tmp1 = camel_db_sqlize_string(search->folder->full_name);
500                 tmp = g_strdup_printf ("SELECT COUNT (*) FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query?sql_query:"");
501                 camel_db_free_sqlized_string (tmp1);
502                 g_free (sql_query);
503                 dd(printf("Equivalent sql %s\n", tmp));
504
505                 cdb = (CamelDB *) (search->folder->parent_store->cdb_r);
506                 camel_db_count_message_info  (cdb, tmp, &count, ex);
507                 if (ex && camel_exception_is_set(ex)) {
508                         const gchar *exception = camel_exception_get_description (ex);
509                         if (strncmp(exception, "no such table", 13) == 0) {
510                                 d(g_warning ("Error during searching %s: %s\n", tmp, exception));
511                                 camel_exception_clear (ex); /* Suppress no such table */
512                         }
513                 }
514                 g_free (tmp);
515
516         }
517
518 fail:
519         /* these might be allocated by match-threads */
520         if (p->threads)
521                 camel_folder_thread_messages_unref(p->threads);
522         if (p->threads_hash)
523                 g_hash_table_destroy(p->threads_hash);
524         if (search->summary_set)
525                 g_ptr_array_free(search->summary_set, TRUE);
526         if (search->summary)
527                 camel_folder_free_summary(search->folder, search->summary);
528
529         p->threads = NULL;
530         p->threads_hash = NULL;
531         search->folder = NULL;
532         search->summary = NULL;
533         search->summary_set = NULL;
534         search->current = NULL;
535         search->body_index = NULL;
536
537         return count;
538 }
539
540 static gboolean
541 do_search_in_memory (const gchar *expr)
542 {
543         /* if the expression contains any of these tokens, then perform a memory search, instead of the SQL one */
544         const gchar *in_memory_tokens[] = { "body-contains", "body-regex", "match-threads", "message-location", "header-soundex", "header-regex", "header-full-regex", "header-contains", NULL };
545         gint i;
546
547         if (!expr)
548                 return FALSE;
549
550         for (i = 0; in_memory_tokens [i]; i++) {
551                 if (strstr (expr, in_memory_tokens [i]))
552                         return TRUE;
553         }
554
555         return FALSE;
556 }
557
558 /**
559  * camel_folder_search_search:
560  * @search:
561  * @expr:
562  * @uids: to search against, NULL for all uid's.
563  * @ex:
564  *
565  * Run a search.  Search must have had Folder already set on it, and
566  * it must implement summaries.
567  *
568  * Returns:
569  **/
570 GPtrArray *
571 camel_folder_search_search(CamelFolderSearch *search, const gchar *expr, GPtrArray *uids, CamelException *ex)
572 {
573         ESExpResult *r;
574         GPtrArray *matches = NULL, *summary_set;
575         gint i;
576         CamelDB *cdb;
577         gchar *sql_query, *tmp, *tmp1;
578         GHashTable *results;
579
580         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
581
582         g_assert(search->folder);
583
584         p->ex = ex;
585
586         /* We route body-contains / thread based search and uid search through memory and not via db. */
587         if (uids || do_search_in_memory (expr)) {
588                 /* setup our search list only contains those we're interested in */
589                 search->summary = camel_folder_get_summary(search->folder);
590
591                 if (uids) {
592                         GHashTable *uids_hash = g_hash_table_new(g_str_hash, g_str_equal);
593
594                         summary_set = search->summary_set = g_ptr_array_new();
595                         for (i=0;i<uids->len;i++)
596                                 g_hash_table_insert(uids_hash, uids->pdata[i], uids->pdata[i]);
597                         for (i=0;i<search->summary->len;i++)
598                                 if (g_hash_table_lookup(uids_hash, search->summary->pdata[i]))
599                                         g_ptr_array_add(search->summary_set, search->summary->pdata[i]);
600                         g_hash_table_destroy(uids_hash);
601                 } else {
602                         summary_set = search->summary;
603                 }
604
605                 /* only re-parse if the search has changed */
606                 if (search->last_search == NULL
607                     || strcmp(search->last_search, expr)) {
608                         e_sexp_input_text(search->sexp, expr, strlen(expr));
609                         if (e_sexp_parse(search->sexp) == -1) {
610                                 camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
611                                 goto fail;
612                         }
613
614                         g_free(search->last_search);
615                         search->last_search = g_strdup(expr);
616                 }
617                 r = e_sexp_eval(search->sexp);
618                 if (r == NULL) {
619                         if (!camel_exception_is_set(ex))
620                                 camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
621                         goto fail;
622                 }
623
624                 matches = g_ptr_array_new();
625
626                 /* now create a folder summary to return?? */
627                 if (r->type == ESEXP_RES_ARRAY_PTR) {
628                         d(printf("got result\n"));
629
630                         /* reorder result in summary order */
631                         results = g_hash_table_new(g_str_hash, g_str_equal);
632                         for (i=0;i<r->value.ptrarray->len;i++) {
633                                 d(printf("adding match: %s\n", (gchar *)g_ptr_array_index(r->value.ptrarray, i)));
634                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
635                         }
636
637                         for (i=0;i<summary_set->len;i++) {
638                                 gchar *uid = g_ptr_array_index(summary_set, i);
639                                 if (g_hash_table_lookup(results, uid))
640                                         g_ptr_array_add(matches, (gpointer) camel_pstring_strdup(uid));
641                         }
642                         g_hash_table_destroy(results);
643                 }
644
645                 e_sexp_result_free(search->sexp, r);
646
647         } else {
648                 /* Sync the db, so that we search the db for changes */
649                 camel_folder_summary_save_to_db (search->folder->summary, ex);
650
651                 dd(printf ("sexp is : [%s]\n", expr));
652                 if (g_getenv("SQL_SEARCH_OLD"))
653                         sql_query = camel_sexp_to_sql (expr);
654                 else
655                         sql_query = camel_sexp_to_sql_sexp (expr);
656                 tmp1 = camel_db_sqlize_string(search->folder->full_name);
657                 tmp = g_strdup_printf ("SELECT uid FROM %s %s %s", tmp1, sql_query ? "WHERE":"", sql_query?sql_query:"");
658                 camel_db_free_sqlized_string (tmp1);
659                 g_free (sql_query);
660                 dd(printf("Equivalent sql %s\n", tmp));
661
662                 matches = g_ptr_array_new();
663                 cdb = (CamelDB *) (search->folder->parent_store->cdb_r);
664                 camel_db_select (cdb, tmp, (CamelDBSelectCB) read_uid_callback, matches, ex);
665                 if (ex && camel_exception_is_set(ex)) {
666                         const gchar *exception = camel_exception_get_description (ex);
667                         if (strncmp(exception, "no such table", 13) == 0) {
668                                 d(g_warning ("Error during searching %s: %s\n", tmp, exception));
669                                 camel_exception_clear (ex); /* Suppress no such table */
670                         }
671                 }
672                 g_free (tmp);
673
674         }
675
676 fail:
677         /* these might be allocated by match-threads */
678         if (p->threads)
679                 camel_folder_thread_messages_unref(p->threads);
680         if (p->threads_hash)
681                 g_hash_table_destroy(p->threads_hash);
682         if (search->summary_set)
683                 g_ptr_array_free(search->summary_set, TRUE);
684         if (search->summary)
685                 camel_folder_free_summary(search->folder, search->summary);
686
687         p->threads = NULL;
688         p->threads_hash = NULL;
689         search->folder = NULL;
690         search->summary = NULL;
691         search->summary_set = NULL;
692         search->current = NULL;
693         search->body_index = NULL;
694
695         return matches;
696 }
697
698 void camel_folder_search_free_result(CamelFolderSearch *search, GPtrArray *result)
699 {
700         g_ptr_array_foreach (result, (GFunc) camel_pstring_free, NULL);
701         g_ptr_array_free(result, TRUE);
702 }
703
704 /* dummy function, returns false always, or an empty match array */
705 static ESExpResult *
706 search_dummy(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
707 {
708         ESExpResult *r;
709
710         if (search->current == NULL) {
711                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
712                 r->value.boolean = FALSE;
713         } else {
714                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
715                 r->value.ptrarray = g_ptr_array_new();
716         }
717
718         return r;
719 }
720
721 /* impelemnt an 'array not', i.e. everything in the summary, not in the supplied array */
722 static ESExpResult *
723 search_not(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
724 {
725         ESExpResult *r;
726         gint i;
727
728         if (argc>0) {
729                 if (argv[0]->type == ESEXP_RES_ARRAY_PTR) {
730                         GPtrArray *v = argv[0]->value.ptrarray;
731                         const gchar *uid;
732
733                         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
734                         r->value.ptrarray = g_ptr_array_new();
735
736                         /* not against a single message?*/
737                         if (search->current) {
738                                 gint found = FALSE;
739
740                                 uid = camel_message_info_uid(search->current);
741                                 for (i=0;!found && i<v->len;i++) {
742                                         if (strcmp(uid, v->pdata[i]) == 0)
743                                                 found = TRUE;
744                                 }
745
746                                 if (!found)
747                                         g_ptr_array_add(r->value.ptrarray, (gchar *)uid);
748                         } else if (search->summary == NULL) {
749                                 g_warning("No summary set, 'not' against an array requires a summary");
750                         } else {
751                                 /* 'not' against the whole summary */
752                                 GHashTable *have = g_hash_table_new(g_str_hash, g_str_equal);
753                                 gchar **s;
754                                 gchar **m;
755
756                                 s = (gchar **)v->pdata;
757                                 for (i=0;i<v->len;i++)
758                                         g_hash_table_insert(have, s[i], s[i]);
759
760                                 v = search->summary_set?search->summary_set:search->summary;
761                                 m = (gchar **)v->pdata;
762                                 for (i=0;i<v->len;i++) {
763                                         gchar *uid = m[i];
764
765                                         if (g_hash_table_lookup(have, uid) == NULL)
766                                                 g_ptr_array_add(r->value.ptrarray, uid);
767                                 }
768                                 g_hash_table_destroy(have);
769                         }
770                 } else {
771                         gint res = TRUE;
772
773                         if (argv[0]->type == ESEXP_RES_BOOL)
774                                 res = !argv[0]->value.boolean;
775
776                         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
777                         r->value.boolean = res;
778                 }
779         } else {
780                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
781                 r->value.boolean = TRUE;
782         }
783
784         return r;
785 }
786
787 static ESExpResult *
788 search_match_all(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
789 {
790         gint i;
791         ESExpResult *r, *r1;
792         gchar *error_msg;
793         GPtrArray *v;
794
795         if (argc>1) {
796                 g_warning("match-all only takes a single argument, other arguments ignored");
797         }
798
799         /* we are only matching a single message?  or already inside a match-all? */
800         if (search->current) {
801                 d(printf("matching against 1 message: %s\n", camel_message_info_subject(search->current)));
802
803                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
804                 r->value.boolean = FALSE;
805
806                 if (argc>0) {
807                         r1 = e_sexp_term_eval(f, argv[0]);
808                         if (r1->type == ESEXP_RES_BOOL) {
809                                 r->value.boolean = r1->value.boolean;
810                         } else {
811                                 g_warning("invalid syntax, matches require a single bool result");
812                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
813                                 e_sexp_fatal_error(f, error_msg);
814                                 g_free(error_msg);
815                         }
816                         e_sexp_result_free(f, r1);
817                 } else {
818                         r->value.boolean = TRUE;
819                 }
820                 return r;
821         }
822
823         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
824         r->value.ptrarray = g_ptr_array_new();
825
826         if (search->summary == NULL) {
827                 /* TODO: make it work - e.g. use the folder and so forth for a slower search */
828                 g_warning("No summary supplied, match-all doesn't work with no summary");
829                 g_assert(0);
830                 return r;
831         }
832
833         v = search->summary_set?search->summary_set:search->summary;
834
835         if (v->len > g_hash_table_size (search->folder->summary->loaded_infos) && !CAMEL_IS_VEE_FOLDER (search->folder)) {
836                 camel_folder_summary_reload_from_db (search->folder->summary, search->priv->ex);
837         }
838
839         for (i=0;i<v->len;i++) {
840                 const gchar *uid;
841
842                 search->current = camel_folder_summary_uid (search->folder->summary, v->pdata[i]);
843                 if (!search->current)
844                         continue;
845                 uid = camel_message_info_uid(search->current);
846
847                 if (argc>0) {
848                         r1 = e_sexp_term_eval(f, argv[0]);
849                         if (r1->type == ESEXP_RES_BOOL) {
850                                 if (r1->value.boolean)
851                                         g_ptr_array_add(r->value.ptrarray, (gchar *)uid);
852                         } else {
853                                 g_warning("invalid syntax, matches require a single bool result");
854                                 error_msg = g_strdup_printf(_("(%s) requires a single bool result"), "match-all");
855                                 e_sexp_fatal_error(f, error_msg);
856                                 g_free(error_msg);
857                         }
858                         e_sexp_result_free(f, r1);
859                 } else {
860                         g_ptr_array_add(r->value.ptrarray, (gchar *)uid);
861                 }
862                 camel_message_info_free (search->current);
863         }
864         search->current = NULL;
865         return r;
866 }
867
868 static void
869 fill_thread_table(struct _CamelFolderThreadNode *root, GHashTable *id_hash)
870 {
871         while (root) {
872                 g_hash_table_insert(id_hash, (gchar *)camel_message_info_uid(root->message), root);
873                 if (root->child)
874                         fill_thread_table(root->child, id_hash);
875                 root = root->next;
876         }
877 }
878
879 static void
880 add_thread_results(struct _CamelFolderThreadNode *root, GHashTable *result_hash)
881 {
882         while (root) {
883                 g_hash_table_insert(result_hash, (gchar *)camel_message_info_uid(root->message), GINT_TO_POINTER (1));
884                 if (root->child)
885                         add_thread_results(root->child, result_hash);
886                 root = root->next;
887         }
888 }
889
890 static void
891 add_results(gchar *uid, gpointer dummy, GPtrArray *result)
892 {
893         g_ptr_array_add(result, uid);
894 }
895
896 static ESExpResult *
897 search_match_threads(struct _ESExp *f, gint argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
898 {
899         ESExpResult *r;
900         struct _CamelFolderSearchPrivate *p = search->priv;
901         gint i, type;
902         GHashTable *results;
903         gchar *error_msg;
904
905         /* not supported in match-all */
906         if (search->current) {
907                 error_msg = g_strdup_printf(_("(%s) not allowed inside %s"), "match-threads", "match-all");
908                 e_sexp_fatal_error(f, error_msg);
909                 g_free(error_msg);
910         }
911
912         if (argc == 0) {
913                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
914                 e_sexp_fatal_error(f, error_msg);
915                 g_free(error_msg);
916         }
917
918         r = e_sexp_term_eval(f, argv[0]);
919         if (r->type != ESEXP_RES_STRING) {
920                 error_msg = g_strdup_printf(_("(%s) requires a match type string"), "match-threads");
921                 e_sexp_fatal_error(f, error_msg);
922                 g_free(error_msg);
923         }
924
925         type = 0;
926         if (!strcmp(r->value.string, "none"))
927                 type = 0;
928         else if (!strcmp(r->value.string, "all"))
929                 type = 1;
930         else if (!strcmp(r->value.string, "replies"))
931                 type = 2;
932         else if (!strcmp(r->value.string, "replies_parents"))
933                 type = 3;
934         else if (!strcmp(r->value.string, "single"))
935                 type = 4;
936         e_sexp_result_free(f, r);
937
938         /* behave as (begin does */
939         r = NULL;
940         for (i=1;i<argc;i++) {
941                 if (r)
942                         e_sexp_result_free(f, r);
943                 r = e_sexp_term_eval(f, argv[i]);
944         }
945
946         if (r == NULL || r->type != ESEXP_RES_ARRAY_PTR) {
947                 error_msg = g_strdup_printf(_("(%s) expects an array result"), "match-threads");
948                 e_sexp_fatal_error(f, error_msg);
949                 g_free(error_msg);
950         }
951
952         if (type == 0)
953                 return r;
954
955         if (search->folder == NULL) {
956                 error_msg = g_strdup_printf(_("(%s) requires the folder set"), "match-threads");
957                 e_sexp_fatal_error(f, error_msg);
958                 g_free(error_msg);
959         }
960
961         /* cache this, so we only have to re-calculate once per search at most */
962         if (p->threads == NULL) {
963                 p->threads = camel_folder_thread_messages_new(search->folder, NULL, TRUE);
964                 p->threads_hash = g_hash_table_new(g_str_hash, g_str_equal);
965
966                 fill_thread_table(p->threads->tree, p->threads_hash);
967         }
968
969         results = g_hash_table_new(g_str_hash, g_str_equal);
970         for (i=0;i<r->value.ptrarray->len;i++) {
971                 struct _CamelFolderThreadNode *node, *scan;
972
973                 if (type != 4)
974                         g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER(1));
975
976                 node = g_hash_table_lookup(p->threads_hash, (gchar *)g_ptr_array_index(r->value.ptrarray, i));
977                 if (node == NULL) /* this shouldn't happen but why cry over spilt milk */
978                         continue;
979
980                 /* select messages in thread according to search criteria */
981                 if (type == 4) {
982                         if (node->child == NULL && node->parent == NULL)
983                                 g_hash_table_insert(results, (gchar *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
984                 } else {
985                         if (type == 3) {
986                                 scan = node;
987                                 while (scan && scan->parent) {
988                                         scan = scan->parent;
989                                         g_hash_table_insert(results, (gchar *)camel_message_info_uid(scan->message), GINT_TO_POINTER(1));
990                                 }
991                         } else if (type == 1) {
992                                 while (node && node->parent)
993                                         node = node->parent;
994                         }
995                         g_hash_table_insert(results, (gchar *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
996                         if (node->child)
997                                 add_thread_results(node->child, results);
998                 }
999         }
1000         e_sexp_result_free(f, r);
1001
1002         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1003         r->value.ptrarray = g_ptr_array_new();
1004
1005         g_hash_table_foreach(results, (GHFunc)add_results, r->value.ptrarray);
1006         g_hash_table_destroy(results);
1007
1008         return r;
1009 }
1010
1011 static CamelMimeMessage *
1012 get_current_message (CamelFolderSearch *search)
1013 {
1014         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1015         CamelMimeMessage *res;
1016
1017         if (!search || !search->folder || !search->current)
1018                 return NULL;
1019
1020         res = camel_folder_get_message (search->folder, search->current->uid, &x);
1021
1022         if (!res)
1023                 camel_exception_clear (&x);
1024
1025         return res;
1026 }
1027
1028 static ESExpResult *
1029 check_header (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search, camel_search_match_t how)
1030 {
1031         ESExpResult *r;
1032         gint truth = FALSE;
1033
1034         r(printf("executing check-header %d\n", how));
1035
1036         /* are we inside a match-all? */
1037         if (search->current && argc>1
1038             && argv[0]->type == ESEXP_RES_STRING) {
1039                 gchar *headername;
1040                 const gchar *header = NULL, *charset = NULL;
1041                 gchar strbuf[32];
1042                 gint i, j;
1043                 camel_search_t type = CAMEL_SEARCH_TYPE_ASIS;
1044                 struct _camel_search_words *words;
1045                 CamelMimeMessage *message = NULL;
1046                 struct _camel_header_raw *raw_header;
1047
1048                 /* only a subset of headers are supported .. */
1049                 headername = argv[0]->value.string;
1050                 if (!g_ascii_strcasecmp(headername, "subject")) {
1051                         header = camel_message_info_subject(search->current);
1052                 } else if (!g_ascii_strcasecmp(headername, "date")) {
1053                         /* FIXME: not a very useful form of the date */
1054                         sprintf(strbuf, "%d", (gint)camel_message_info_date_sent(search->current));
1055                         header = strbuf;
1056                 } else if (!g_ascii_strcasecmp(headername, "from")) {
1057                         header = camel_message_info_from(search->current);
1058                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1059                 } else if (!g_ascii_strcasecmp(headername, "to")) {
1060                         header = camel_message_info_to(search->current);
1061                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1062                 } else if (!g_ascii_strcasecmp(headername, "cc")) {
1063                         header = camel_message_info_cc(search->current);
1064                         type = CAMEL_SEARCH_TYPE_ADDRESS;
1065                 } else if (!g_ascii_strcasecmp(headername, "x-camel-mlist")) {
1066                         header = camel_message_info_mlist(search->current);
1067                         type = CAMEL_SEARCH_TYPE_MLIST;
1068                 } else {
1069                         message = get_current_message (search);
1070                         if (message) {
1071                                 CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
1072
1073                                 if (ct) {
1074                                         charset = camel_content_type_param (ct, "charset");
1075                                         charset = camel_iconv_charset_name (charset);
1076                                 }
1077                         }
1078                 }
1079
1080                 if (header == NULL)
1081                         header = "";
1082
1083                 /* performs an OR of all words */
1084                 for (i=1;i<argc && !truth;i++) {
1085                         if (argv[i]->type == ESEXP_RES_STRING) {
1086                                 if (argv[i]->value.string[0] == 0) {
1087                                         truth = TRUE;
1088                                 } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) {
1089                                         /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */
1090                                         words = camel_search_words_split((const guchar *) argv[i]->value.string);
1091                                         truth = TRUE;
1092                                         for (j=0;j<words->len && truth;j++) {
1093                                                 if (message) {
1094                                                         for (raw_header = ((CamelMimePart *)message)->headers; raw_header; raw_header = raw_header->next) {
1095                                                                 if (!g_ascii_strcasecmp (raw_header->name, headername)) {
1096                                                                         if (camel_search_header_match (raw_header->value, words->words[j]->word, how, type, charset))
1097                                                                                 break;;
1098                                                                 }
1099                                                         }
1100
1101                                                         truth = raw_header != NULL;
1102                                                 } else
1103                                                         truth = camel_search_header_match(header, words->words[j]->word, how, type, charset);
1104                                         }
1105                                         camel_search_words_free(words);
1106                                 } else {
1107                                         if (message) {
1108                                                 for (raw_header = ((CamelMimePart *)message)->headers; raw_header && !truth; raw_header = raw_header->next) {
1109                                                         if (!g_ascii_strcasecmp (raw_header->name, headername)) {
1110                                                                 truth = camel_search_header_match(raw_header->value, argv[i]->value.string, how, type, charset);
1111                                                         }
1112                                                 }
1113                                         } else
1114                                                 truth = camel_search_header_match(header, argv[i]->value.string, how, type, charset);
1115                                 }
1116                         }
1117                 }
1118
1119                 if (message)
1120                         camel_object_unref (message);
1121         }
1122         /* TODO: else, find all matches */
1123
1124         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1125         r->value.boolean = truth;
1126
1127         return r;
1128 }
1129
1130 /*
1131 static void
1132 l_printf(gchar *node)
1133 {
1134 printf("%s\t", node);
1135 }
1136 */
1137
1138 static ESExpResult *
1139 search_header_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1140 {
1141         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS);
1142 }
1143
1144 static ESExpResult *
1145 search_header_matches(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1146 {
1147         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT);
1148 }
1149
1150 static ESExpResult *
1151 search_header_starts_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1152 {
1153         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS);
1154 }
1155
1156 static ESExpResult *
1157 search_header_ends_with (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1158 {
1159         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS);
1160 }
1161
1162 static ESExpResult *
1163 search_header_exists (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1164 {
1165         ESExpResult *r;
1166
1167         r(printf ("executing header-exists\n"));
1168
1169         if (search->current) {
1170                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1171                 if (argc == 1 && argv[0]->type == ESEXP_RES_STRING)
1172                         r->value.boolean = camel_medium_get_header(CAMEL_MEDIUM(search->current), argv[0]->value.string) != NULL;
1173
1174         } else {
1175                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1176                 r->value.ptrarray = g_ptr_array_new();
1177         }
1178
1179         return r;
1180 }
1181
1182 static ESExpResult *
1183 search_header_soundex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1184 {
1185         return check_header (f, argc, argv, search, CAMEL_SEARCH_MATCH_SOUNDEX);
1186 }
1187
1188 static ESExpResult *
1189 search_header_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1190 {
1191         ESExpResult *r;
1192         CamelMimeMessage *msg;
1193
1194         msg = get_current_message (search);
1195
1196         if (msg) {
1197                 regex_t pattern;
1198                 const gchar *contents;
1199
1200                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1201
1202                 if (argc > 1 && argv[0]->type == ESEXP_RES_STRING
1203                     && (contents = camel_medium_get_header (CAMEL_MEDIUM (msg), argv[0]->value.string))
1204                     && camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE, argc-1, argv+1, search->priv->ex) == 0) {
1205                         r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
1206                         regfree (&pattern);
1207                 } else
1208                         r->value.boolean = FALSE;
1209
1210                 camel_object_unref (msg);
1211         } else {
1212                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1213                 r->value.ptrarray = g_ptr_array_new();
1214         }
1215
1216         return r;
1217 }
1218
1219 static gchar *
1220 get_full_header (CamelMimeMessage *message)
1221 {
1222         CamelMimePart *mp = CAMEL_MIME_PART (message);
1223         GString *str = g_string_new ("");
1224         struct _camel_header_raw *h;
1225
1226         for (h = mp->headers; h; h = h->next) {
1227                 if (h->value != NULL) {
1228                         g_string_append (str, h->name);
1229                         if (isspace (h->value[0]))
1230                                 g_string_append (str, ":");
1231                         else
1232                                 g_string_append (str, ": ");
1233                         g_string_append (str, h->value);
1234                         g_string_append_c (str, '\n');
1235                 }
1236         }
1237
1238         return g_string_free (str, FALSE);
1239 }
1240
1241 static ESExpResult *
1242 search_header_full_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1243 {
1244         ESExpResult *r;
1245         CamelMimeMessage *msg;
1246
1247         msg = get_current_message (search);
1248
1249         if (msg) {
1250                 regex_t pattern;
1251
1252                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1253
1254                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->ex) == 0) {
1255                         gchar *contents;
1256
1257                         contents = get_full_header (msg);
1258                         r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
1259
1260                         g_free (contents);
1261                         regfree (&pattern);
1262                 } else
1263                         r->value.boolean = FALSE;
1264
1265                 camel_object_unref (msg);
1266         } else {
1267                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1268                 r->value.ptrarray = g_ptr_array_new();
1269         }
1270
1271         return r;
1272 }
1273
1274 /* this is just to OR results together */
1275 struct IterData {
1276         gint count;
1277         GPtrArray *uids;
1278 };
1279
1280 /* or, store all unique values */
1281 static void
1282 htor(gchar *key, gint value, struct IterData *iter_data)
1283 {
1284         g_ptr_array_add(iter_data->uids, key);
1285 }
1286
1287 /* and, only store duplicates */
1288 static void
1289 htand(gchar *key, gint value, struct IterData *iter_data)
1290 {
1291         if (value == iter_data->count)
1292                 g_ptr_array_add(iter_data->uids, key);
1293 }
1294
1295 static gint
1296 match_message_index(CamelIndex *idx, const gchar *uid, const gchar *match, CamelException *ex)
1297 {
1298         CamelIndexCursor *wc, *nc;
1299         const gchar *word, *name;
1300         gint truth = FALSE;
1301
1302         wc = camel_index_words(idx);
1303         if (wc) {
1304                 while (!truth && (word = camel_index_cursor_next(wc))) {
1305                         if (camel_ustrstrcase(word,match) != NULL) {
1306                                 /* perf: could have the wc cursor return the name cursor */
1307                                 nc = camel_index_find(idx, word);
1308                                 if (nc) {
1309                                         while (!truth && (name = camel_index_cursor_next(nc)))
1310                                                 truth = strcmp(name, uid) == 0;
1311                                         camel_object_unref((CamelObject *)nc);
1312                                 }
1313                         }
1314                 }
1315                 camel_object_unref((CamelObject *)wc);
1316         }
1317
1318         return truth;
1319 }
1320
1321 /*
1322  "one two" "three" "four five"
1323
1324   one and two
1325 or
1326   three
1327 or
1328   four and five
1329 */
1330
1331 /* returns messages which contain all words listed in words */
1332 static GPtrArray *
1333 match_words_index(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
1334 {
1335         GPtrArray *result = g_ptr_array_new();
1336         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1337         struct IterData lambdafoo;
1338         CamelIndexCursor *wc, *nc;
1339         const gchar *word, *name;
1340         gint i;
1341
1342         /* we can have a maximum of 32 words, as we use it as the AND mask */
1343
1344         wc = camel_index_words(search->body_index);
1345         if (wc) {
1346                 while ((word = camel_index_cursor_next(wc))) {
1347                         for (i=0;i<words->len;i++) {
1348                                 if (camel_ustrstrcase(word, words->words[i]->word) != NULL) {
1349                                         /* perf: could have the wc cursor return the name cursor */
1350                                         nc = camel_index_find(search->body_index, word);
1351                                         if (nc) {
1352                                                 while ((name = camel_index_cursor_next(nc))) {
1353                                                                 gint mask;
1354
1355                                                                 mask = (GPOINTER_TO_INT(g_hash_table_lookup(ht, name))) | (1<<i);
1356                                                                 g_hash_table_insert(ht, (gchar *) camel_pstring_peek(name), GINT_TO_POINTER(mask));
1357                                                 }
1358                                                 camel_object_unref((CamelObject *)nc);
1359                                         }
1360                                 }
1361                         }
1362                 }
1363                 camel_object_unref((CamelObject *)wc);
1364
1365                 lambdafoo.uids = result;
1366                 lambdafoo.count = (1<<words->len) - 1;
1367                 g_hash_table_foreach(ht, (GHFunc)htand, &lambdafoo);
1368                 g_hash_table_destroy(ht);
1369         }
1370
1371         return result;
1372 }
1373
1374 static gboolean
1375 match_words_1message (CamelDataWrapper *object, struct _camel_search_words *words, guint32 *mask)
1376 {
1377         CamelDataWrapper *containee;
1378         gint truth = FALSE;
1379         gint parts, i;
1380
1381         containee = camel_medium_get_content_object (CAMEL_MEDIUM (object));
1382
1383         if (containee == NULL)
1384                 return FALSE;
1385
1386         /* using the object types is more accurate than using the mime/types */
1387         if (CAMEL_IS_MULTIPART (containee)) {
1388                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
1389                 for (i = 0; i < parts && truth == FALSE; i++) {
1390                         CamelDataWrapper *part = (CamelDataWrapper *)camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
1391                         if (part)
1392                                 truth = match_words_1message(part, words, mask);
1393                 }
1394         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
1395                 /* for messages we only look at its contents */
1396                 truth = match_words_1message((CamelDataWrapper *)containee, words, mask);
1397         } else if (camel_content_type_is(CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) {
1398                 /* for all other text parts, we look inside, otherwise we dont care */
1399                 CamelStreamMem *mem = (CamelStreamMem *)camel_stream_mem_new ();
1400
1401                 /* FIXME: The match should be part of a stream op */
1402                 camel_data_wrapper_decode_to_stream (containee, CAMEL_STREAM (mem));
1403                 camel_stream_write (CAMEL_STREAM (mem), "", 1);
1404                 for (i=0;i<words->len;i++) {
1405                         /* FIXME: This is horridly slow, and should use a real search algorithm */
1406                         if (camel_ustrstrcase((const gchar *) mem->buffer->data, words->words[i]->word) != NULL) {
1407                                 *mask |= (1<<i);
1408                                 /* shortcut a match */
1409                                 if (*mask == (1<<(words->len))-1)
1410                                         return TRUE;
1411                         }
1412                 }
1413
1414                 camel_object_unref (mem);
1415         }
1416
1417         return truth;
1418 }
1419
1420 static gboolean
1421 match_words_message(CamelFolder *folder, const gchar *uid, struct _camel_search_words *words, CamelException *ex)
1422 {
1423         guint32 mask;
1424         CamelMimeMessage *msg;
1425         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1426         gint truth;
1427
1428         msg = camel_folder_get_message(folder, uid, &x);
1429         if (msg) {
1430                 mask = 0;
1431                 truth = match_words_1message((CamelDataWrapper *)msg, words, &mask);
1432                 camel_object_unref((CamelObject *)msg);
1433         } else {
1434                 camel_exception_clear(&x);
1435                 truth = FALSE;
1436         }
1437
1438         return truth;
1439 }
1440
1441 static GPtrArray *
1442 match_words_messages(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
1443 {
1444         gint i;
1445         GPtrArray *matches = g_ptr_array_new();
1446
1447         if (search->body_index) {
1448                 GPtrArray *indexed;
1449                 struct _camel_search_words *simple;
1450
1451                 simple = camel_search_words_simple(words);
1452                 indexed = match_words_index(search, simple, ex);
1453                 camel_search_words_free(simple);
1454
1455                 for (i=0;i<indexed->len;i++) {
1456                         const gchar *uid = g_ptr_array_index(indexed, i);
1457
1458                         if (match_words_message(search->folder, uid, words, ex))
1459                                 g_ptr_array_add(matches, (gchar *)uid);
1460                 }
1461
1462                 g_ptr_array_free(indexed, TRUE);
1463         } else {
1464                 GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1465
1466                 for (i=0;i<v->len;i++) {
1467                         gchar *uid  = g_ptr_array_index(v, i);
1468
1469                         if (match_words_message(search->folder, uid, words, ex))
1470                                 g_ptr_array_add(matches, (gchar *)uid);
1471                 }
1472         }
1473
1474         return matches;
1475 }
1476
1477 static ESExpResult *
1478 search_body_contains(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1479 {
1480         gint i, j;
1481         CamelException *ex = search->priv->ex;
1482         struct _camel_search_words *words;
1483         ESExpResult *r;
1484         struct IterData lambdafoo;
1485
1486         if (search->current) {
1487                 gint truth = FALSE;
1488
1489                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1490                         truth = TRUE;
1491                 } else {
1492                         for (i=0;i<argc && !truth;i++) {
1493                                 if (argv[i]->type == ESEXP_RES_STRING) {
1494                                         words = camel_search_words_split((const guchar *) argv[i]->value.string);
1495                                         truth = TRUE;
1496                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1497                                                 for (j=0;j<words->len && truth;j++)
1498                                                         truth = match_message_index(search->body_index, camel_message_info_uid(search->current), words->words[j]->word, ex);
1499                                         } else {
1500                                                 /* TODO: cache current message incase of multiple body search terms */
1501                                                 truth = match_words_message(search->folder, camel_message_info_uid(search->current), words, ex);
1502                                         }
1503                                         camel_search_words_free(words);
1504                                 }
1505                         }
1506                 }
1507                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1508                 r->value.boolean = truth;
1509         } else {
1510                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1511                 r->value.ptrarray = g_ptr_array_new();
1512
1513                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1514                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1515
1516                         for (i=0;i<v->len;i++) {
1517                                 gchar *uid = g_ptr_array_index(v, i);
1518
1519                                 g_ptr_array_add(r->value.ptrarray, uid);
1520                         }
1521                 } else {
1522                         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1523                         GPtrArray *matches;
1524
1525                         for (i=0;i<argc;i++) {
1526                                 if (argv[i]->type == ESEXP_RES_STRING) {
1527                                         words = camel_search_words_split((const guchar *) argv[i]->value.string);
1528                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1529                                                 matches = match_words_index(search, words, ex);
1530                                         } else {
1531                                                 matches = match_words_messages(search, words, ex);
1532                                         }
1533                                         for (j=0;j<matches->len;j++) {
1534                                                 g_hash_table_insert(ht, matches->pdata[j], matches->pdata[j]);
1535                                         }
1536                                         g_ptr_array_free(matches, TRUE);
1537                                         camel_search_words_free(words);
1538                                 }
1539                         }
1540                         lambdafoo.uids = r->value.ptrarray;
1541                         g_hash_table_foreach(ht, (GHFunc)htor, &lambdafoo);
1542                         g_hash_table_destroy(ht);
1543                 }
1544         }
1545
1546         return r;
1547 }
1548
1549 static ESExpResult *
1550 search_body_regex (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1551 {
1552         ESExpResult *r;
1553         CamelMimeMessage *msg = get_current_message (search);
1554
1555         if (msg) {
1556                 regex_t pattern;
1557
1558                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1559
1560                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->ex) == 0) {
1561                         r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) msg, &pattern);
1562                         regfree (&pattern);
1563                 } else
1564                         r->value.boolean = FALSE;
1565
1566                 camel_object_unref (msg);
1567         } else {
1568                 regex_t pattern;
1569
1570                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1571                 r->value.ptrarray = g_ptr_array_new ();
1572
1573                 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE, argc, argv, search->priv->ex) == 0) {
1574                         gint i;
1575                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1576                         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1577                         CamelMimeMessage *message;
1578
1579                         for (i = 0; i < v->len; i++) {
1580                                 gchar *uid = g_ptr_array_index(v, i);
1581
1582                                 message = camel_folder_get_message (search->folder, uid, &x);
1583                                 if (message) {
1584                                         if (camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern)) {
1585                                                 g_ptr_array_add (r->value.ptrarray, uid);
1586                                         }
1587
1588                                         camel_object_unref ((CamelObject *)message);
1589                                 } else {
1590                                         camel_exception_clear (&x);
1591                                 }
1592                         }
1593
1594                         regfree (&pattern);
1595                 }
1596         }
1597
1598         return r;
1599 }
1600
1601 static ESExpResult *
1602 search_user_flag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1603 {
1604         ESExpResult *r;
1605         gint i;
1606
1607         r(printf("executing user-flag\n"));
1608
1609         /* are we inside a match-all? */
1610         if (search->current) {
1611                 gint truth = FALSE;
1612                 /* performs an OR of all words */
1613                 for (i=0;i<argc && !truth;i++) {
1614                         if (argv[i]->type == ESEXP_RES_STRING
1615                             && camel_message_info_user_flag(search->current, argv[i]->value.string)) {
1616                                 truth = TRUE;
1617                                 break;
1618                         }
1619                 }
1620                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1621                 r->value.boolean = truth;
1622         } else {
1623                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1624                 r->value.ptrarray = g_ptr_array_new();
1625         }
1626
1627         return r;
1628 }
1629
1630 static ESExpResult *
1631 search_system_flag (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1632 {
1633         ESExpResult *r;
1634
1635         r(printf ("executing system-flag\n"));
1636
1637         if (search->current) {
1638                 gboolean truth = FALSE;
1639
1640                 if (argc == 1)
1641                         truth = camel_system_flag_get (camel_message_info_flags(search->current), argv[0]->value.string);
1642
1643                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1644                 r->value.boolean = truth;
1645         } else {
1646                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1647                 r->value.ptrarray = g_ptr_array_new ();
1648         }
1649
1650         return r;
1651 }
1652
1653 static ESExpResult *
1654 search_user_tag(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1655 {
1656         const gchar *value = NULL;
1657         ESExpResult *r;
1658
1659         r(printf("executing user-tag\n"));
1660
1661         if (search->current && argc == 1)
1662                 value = camel_message_info_user_tag(search->current, argv[0]->value.string);
1663
1664         r = e_sexp_result_new(f, ESEXP_RES_STRING);
1665         r->value.string = g_strdup (value ? value : "");
1666
1667         return r;
1668 }
1669
1670 static ESExpResult *
1671 search_get_sent_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1672 {
1673         ESExpResult *r;
1674
1675         r(printf("executing get-sent-date\n"));
1676
1677         /* are we inside a match-all? */
1678         if (s->current) {
1679                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1680
1681                 r->value.number = camel_message_info_date_sent(s->current);
1682         } else {
1683                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1684                 r->value.ptrarray = g_ptr_array_new ();
1685         }
1686
1687         return r;
1688 }
1689
1690 static ESExpResult *
1691 search_get_received_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1692 {
1693         ESExpResult *r;
1694
1695         r(printf("executing get-received-date\n"));
1696
1697         /* are we inside a match-all? */
1698         if (s->current) {
1699                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1700
1701                 r->value.number = camel_message_info_date_received(s->current);
1702         } else {
1703                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1704                 r->value.ptrarray = g_ptr_array_new ();
1705         }
1706
1707         return r;
1708 }
1709
1710 static ESExpResult *
1711 search_get_current_date(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1712 {
1713         ESExpResult *r;
1714
1715         r(printf("executing get-current-date\n"));
1716
1717         r = e_sexp_result_new(f, ESEXP_RES_INT);
1718         r->value.number = time (NULL);
1719         return r;
1720 }
1721
1722 static ESExpResult *
1723 search_get_size (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1724 {
1725         ESExpResult *r;
1726
1727         r(printf("executing get-size\n"));
1728
1729         /* are we inside a match-all? */
1730         if (s->current) {
1731                 r = e_sexp_result_new (f, ESEXP_RES_INT);
1732                 r->value.number = camel_message_info_size(s->current) / 1024;
1733         } else {
1734                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1735                 r->value.ptrarray = g_ptr_array_new ();
1736         }
1737
1738         return r;
1739 }
1740
1741 static ESExpResult *
1742 search_uid(struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1743 {
1744         ESExpResult *r;
1745         gint i;
1746
1747         r(printf("executing uid\n"));
1748
1749         /* are we inside a match-all? */
1750         if (search->current) {
1751                 gint truth = FALSE;
1752                 const gchar *uid = camel_message_info_uid(search->current);
1753
1754                 /* performs an OR of all words */
1755                 for (i=0;i<argc && !truth;i++) {
1756                         if (argv[i]->type == ESEXP_RES_STRING
1757                             && !strcmp(uid, argv[i]->value.string)) {
1758                                 truth = TRUE;
1759                                 break;
1760                         }
1761                 }
1762                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1763                 r->value.boolean = truth;
1764         } else {
1765                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1766                 r->value.ptrarray = g_ptr_array_new();
1767                 for (i=0;i<argc;i++) {
1768                         if (argv[i]->type == ESEXP_RES_STRING)
1769                                 g_ptr_array_add(r->value.ptrarray, argv[i]->value.string);
1770                 }
1771         }
1772
1773         return r;
1774 }
1775
1776 static gint
1777 read_uid_callback (gpointer  ref, gint ncol, gchar ** cols, gchar **name)
1778 {
1779         GPtrArray *matches;
1780
1781         matches = (GPtrArray *) ref;
1782
1783         g_ptr_array_add (matches, (gpointer) camel_pstring_strdup (cols [0]));
1784         return 0;
1785 }
1786
1787 static ESExpResult *
1788 search_message_location (struct _ESExp *f, gint argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1789 {
1790         ESExpResult *r;
1791         gboolean same = FALSE;
1792
1793         if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
1794                 if (argv[0]->value.string && search->folder && search->folder->parent_store && camel_folder_get_full_name (search->folder)) {
1795                         CamelFolderInfo *fi = camel_store_get_folder_info (search->folder->parent_store, camel_folder_get_full_name (search->folder), 0, NULL);
1796                         if (fi) {
1797                                 same = g_str_equal (fi->uri ? fi->uri : "", argv[0]->value.string);
1798
1799                                 camel_store_free_folder_info (search->folder->parent_store, fi);
1800                         }
1801                 }
1802         }
1803
1804         if (search->current) {
1805                 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
1806                 r->value.boolean = same ? TRUE : FALSE;
1807         } else {
1808                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1809                 r->value.ptrarray = g_ptr_array_new ();
1810
1811                 if (same) {
1812                         /* all matches */
1813                         gint i;
1814                         GPtrArray *v = search->summary_set ? search->summary_set : search->summary;
1815
1816                         for (i = 0; i < v->len; i++) {
1817                                 gchar *uid = g_ptr_array_index (v, i);
1818
1819                                 g_ptr_array_add (r->value.ptrarray, uid);
1820                         }
1821                 }
1822         }
1823
1824         return r;
1825 }