Fix memory leak. Fixes bug #335423.
[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) 2000-2003 Ximian Inc.
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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, 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 #include <stdio.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <sys/types.h>
34 #include <regex.h>
35
36 #include <glib.h>
37
38 #include "libedataserver/e-memory.h"
39
40 #include "camel-folder-search.h"
41 #include "camel-folder-thread.h"
42
43 #include "camel-exception.h"
44 #include "camel-medium.h"
45 #include "camel-multipart.h"
46 #include "camel-mime-message.h"
47 #include "camel-stream-mem.h"
48 #include "camel-search-private.h"
49 #include "camel-i18n.h"
50
51 #define d(x) 
52 #define r(x) 
53
54 struct _CamelFolderSearchPrivate {
55         GHashTable *mempool_hash;
56         CamelException *ex;
57
58         CamelFolderThread *threads;
59         GHashTable *threads_hash;
60 };
61
62 #define _PRIVATE(o) (((CamelFolderSearch *)(o))->priv)
63
64 static ESExpResult *search_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
65
66 static ESExpResult *search_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
67 static ESExpResult *search_header_matches(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
68 static ESExpResult *search_header_starts_with(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
69 static ESExpResult *search_header_ends_with(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
70 static ESExpResult *search_header_exists(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
71 static ESExpResult *search_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search);
72 static ESExpResult *search_match_threads(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *s);
73 static ESExpResult *search_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
74 static ESExpResult *search_user_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
75 static ESExpResult *search_user_tag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
76 static ESExpResult *search_system_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
77 static ESExpResult *search_get_sent_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
78 static ESExpResult *search_get_received_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
79 static ESExpResult *search_get_current_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
80 static ESExpResult *search_get_size(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
81 static ESExpResult *search_uid(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s);
82
83 static ESExpResult *search_dummy(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
84
85 static void camel_folder_search_class_init (CamelFolderSearchClass *klass);
86 static void camel_folder_search_init       (CamelFolderSearch *obj);
87 static void camel_folder_search_finalize   (CamelObject *obj);
88
89 static CamelObjectClass *camel_folder_search_parent;
90
91 static void
92 camel_folder_search_class_init (CamelFolderSearchClass *klass)
93 {
94         camel_folder_search_parent = camel_type_get_global_classfuncs (camel_object_get_type ());
95
96         klass->not = search_not;
97
98         klass->match_all = search_match_all;
99         klass->match_threads = search_match_threads;
100         klass->body_contains = search_body_contains;
101         klass->header_contains = search_header_contains;
102         klass->header_matches = search_header_matches;
103         klass->header_starts_with = search_header_starts_with;
104         klass->header_ends_with = search_header_ends_with;
105         klass->header_exists = search_header_exists;
106         klass->user_tag = search_user_tag;
107         klass->user_flag = search_user_flag;
108         klass->system_flag = search_system_flag;
109         klass->get_sent_date = search_get_sent_date;
110         klass->get_received_date = search_get_received_date;
111         klass->get_current_date = search_get_current_date;
112         klass->get_size = search_get_size;
113         klass->uid = search_uid;
114 }
115
116 static void
117 camel_folder_search_init (CamelFolderSearch *obj)
118 {
119         struct _CamelFolderSearchPrivate *p;
120
121         p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
122
123         obj->sexp = e_sexp_new();
124
125         /* use a hash of mempools to associate the returned uid lists with
126            the backing mempool.  yes pretty weird, but i didn't want to change
127            the api just yet */
128
129         p->mempool_hash = g_hash_table_new(0, 0);
130 }
131
132 static void
133 free_mempool(void *key, void *value, void *data)
134 {
135         GPtrArray *uids = key;
136         EMemPool *pool = value;
137
138         g_warning("Search closed with outstanding result unfreed: %p", uids);
139
140         g_ptr_array_free(uids, TRUE);
141         e_mempool_destroy(pool);
142 }
143
144 static void
145 camel_folder_search_finalize (CamelObject *obj)
146 {
147         CamelFolderSearch *search = (CamelFolderSearch *)obj;
148         struct _CamelFolderSearchPrivate *p = _PRIVATE(obj);
149
150         if (search->sexp)
151                 e_sexp_unref(search->sexp);
152         if (search->summary_hash)
153                 g_hash_table_destroy(search->summary_hash);
154
155         g_free(search->last_search);
156         g_hash_table_foreach(p->mempool_hash, free_mempool, obj);
157         g_hash_table_destroy(p->mempool_hash);
158         g_free(p);
159 }
160
161 CamelType
162 camel_folder_search_get_type (void)
163 {
164         static CamelType type = CAMEL_INVALID_TYPE;
165         
166         if (type == CAMEL_INVALID_TYPE) {
167                 type = camel_type_register (camel_object_get_type (), "CamelFolderSearch",
168                                             sizeof (CamelFolderSearch),
169                                             sizeof (CamelFolderSearchClass),
170                                             (CamelObjectClassInitFunc) camel_folder_search_class_init,
171                                             NULL,
172                                             (CamelObjectInitFunc) camel_folder_search_init,
173                                             (CamelObjectFinalizeFunc) camel_folder_search_finalize);
174         }
175         
176         return type;
177 }
178
179 #ifdef offsetof
180 #define CAMEL_STRUCT_OFFSET(type, field)        ((gint) offsetof (type, field))
181 #else
182 #define CAMEL_STRUCT_OFFSET(type, field)        ((gint) ((gchar*) &((type *) 0)->field))
183 #endif
184
185 static struct {
186         char *name;
187         int offset;
188         int flags;              /* 0x02 = immediate, 0x01 = always enter */
189 } builtins[] = {
190         /* these have default implementations in e-sexp */
191         { "and", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, and), 2 },
192         { "or", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, or), 2 },
193         /* we need to override this one though to implement an 'array not' */
194         { "not", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, not), 0 },
195         { "<", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, lt), 2 },
196         { ">", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, gt), 2 },
197         { "=", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, eq), 2 },
198
199         /* these we have to use our own default if there is none */
200         /* they should all be defined in the language? so it parses, or should they not?? */
201         { "match-all", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_all), 3 },
202         { "match-threads", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, match_threads), 3 },
203         { "body-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, body_contains), 1 },
204         { "header-contains", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_contains), 1 },
205         { "header-matches", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_matches), 1 },
206         { "header-starts-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_starts_with), 1 },
207         { "header-ends-with", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_ends_with), 1 },
208         { "header-exists", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, header_exists), 1 },
209         { "user-tag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_tag), 1 },
210         { "user-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, user_flag), 1 },
211         { "system-flag", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, system_flag), 1 },
212         { "get-sent-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_sent_date), 1 },
213         { "get-received-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_received_date), 1 },
214         { "get-current-date", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_current_date), 1 },
215         { "get-size", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, get_size), 1 },
216         { "uid", CAMEL_STRUCT_OFFSET(CamelFolderSearchClass, uid), 1 },
217 };
218
219 void
220 camel_folder_search_construct (CamelFolderSearch *search)
221 {
222         int i;
223         CamelFolderSearchClass *klass = (CamelFolderSearchClass *)CAMEL_OBJECT_GET_CLASS(search);
224
225         for (i=0;i<sizeof(builtins)/sizeof(builtins[0]);i++) {
226                 void *func;
227                 /* c is sure messy sometimes */
228                 func = *((void **)(((char *)klass)+builtins[i].offset));
229                 if (func == NULL && builtins[i].flags&1) {
230                         g_warning("Search class doesn't implement '%s' method: %s", builtins[i].name, camel_type_to_name(CAMEL_OBJECT_GET_CLASS(search)));
231                         func = (void *)search_dummy;
232                 }
233                 if (func != NULL) {
234                         if (builtins[i].flags&2) {
235                                 e_sexp_add_ifunction(search->sexp, 0, builtins[i].name, (ESExpIFunc *)func, search);
236                         } else {
237                                 e_sexp_add_function(search->sexp, 0, builtins[i].name, (ESExpFunc *)func, search);
238                         }
239                 }
240         }
241 }
242
243 /**
244  * camel_folder_search_new:
245  *
246  * Create a new CamelFolderSearch object.
247  * 
248  * A CamelFolderSearch is a subclassable, extensible s-exp
249  * evaluator which enforces a particular set of s-expressions.
250  * Particular methods may be overriden by an implementation to
251  * implement a search for any sort of backend.
252  *
253  * Return value: A new CamelFolderSearch widget.
254  **/
255 CamelFolderSearch *
256 camel_folder_search_new (void)
257 {
258         CamelFolderSearch *new = CAMEL_FOLDER_SEARCH (camel_object_new (camel_folder_search_get_type ()));
259
260         camel_folder_search_construct(new);
261         return new;
262 }
263
264 /**
265  * camel_folder_search_set_folder:
266  * @search:
267  * @folder: A folder.
268  * 
269  * Set the folder attribute of the search.  This is currently unused, but
270  * could be used to perform a slow-search when indexes and so forth are not
271  * available.  Or for use by subclasses.
272  **/
273 void
274 camel_folder_search_set_folder(CamelFolderSearch *search, CamelFolder *folder)
275 {
276         search->folder = folder;
277 }
278
279 /**
280  * camel_folder_search_set_summary:
281  * @search: 
282  * @summary: An array of CamelMessageInfo pointers.
283  * 
284  * Set the array of summary objects representing the span of the search.
285  *
286  * If this is not set, then a subclass must provide the functions
287  * for searching headers and for the match-all operator.
288  **/
289 void
290 camel_folder_search_set_summary(CamelFolderSearch *search, GPtrArray *summary)
291 {
292         int i;
293
294         search->summary = summary;
295         if (search->summary_hash)
296                 g_hash_table_destroy(search->summary_hash);
297         search->summary_hash = g_hash_table_new(g_str_hash, g_str_equal);
298         for (i=0;i<summary->len;i++)
299                 g_hash_table_insert(search->summary_hash, (char *)camel_message_info_uid(summary->pdata[i]), summary->pdata[i]);
300 }
301
302 /**
303  * camel_folder_search_set_body_index:
304  * @search: 
305  * @index: 
306  * 
307  * Set the index representing the contents of all messages
308  * in this folder.  If this is not set, then the folder implementation
309  * should sub-class the CamelFolderSearch and provide its own
310  * body-contains function.
311  **/
312 void
313 camel_folder_search_set_body_index(CamelFolderSearch *search, CamelIndex *index)
314 {
315         if (search->body_index)
316                 camel_object_unref((CamelObject *)search->body_index);
317         search->body_index = index;
318         if (index)
319                 camel_object_ref((CamelObject *)index);
320 }
321
322 /**
323  * camel_folder_search_execute_expression:
324  * @search: 
325  * @expr: 
326  * @ex: 
327  * 
328  * Execute the search expression @expr, returning an array of
329  * all matches as a GPtrArray of uid's of matching messages.
330  *
331  * Note that any settings such as set_body_index(), set_folder(),
332  * and so on are reset to #NULL once the search has completed.
333  *
334  * TODO: The interface should probably return summary items instead
335  * (since they are much more useful to any client).
336  * 
337  * Return value: A GPtrArray of strings of all matching messages.
338  * This must only be freed by camel_folder_search_free_result.
339  **/
340 GPtrArray *
341 camel_folder_search_execute_expression(CamelFolderSearch *search, const char *expr, CamelException *ex)
342 {
343         ESExpResult *r;
344         GPtrArray *matches;
345         int i;
346         GHashTable *results;
347         EMemPool *pool;
348         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
349
350         p->ex = ex;
351
352         /* only re-parse if the search has changed */
353         if (search->last_search == NULL
354             || strcmp(search->last_search, expr)) {
355                 e_sexp_input_text(search->sexp, expr, strlen(expr));
356                 if (e_sexp_parse(search->sexp) == -1) {
357                         camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
358                         return NULL;
359                 }
360
361                 g_free(search->last_search);
362                 search->last_search = g_strdup(expr);
363         }
364         r = e_sexp_eval(search->sexp);
365         if (r == NULL) {
366                 if (!camel_exception_is_set(ex))
367                         camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
368                 return NULL;
369         }
370
371         matches = g_ptr_array_new();
372
373         /* now create a folder summary to return?? */
374         if (r->type == ESEXP_RES_ARRAY_PTR) {
375                 d(printf("got result ...\n"));
376                 /* we use a mempool to store the strings, packed in tight as possible, and freed together */
377                 /* because the strings are often short (like <8 bytes long), we would be wasting appx 50%
378                    of memory just storing the size tag that malloc assigns us and alignment padding, so this
379                    gets around that (and is faster to allocate and free as a bonus) */
380                 pool = e_mempool_new(512, 256, E_MEMPOOL_ALIGN_BYTE);
381                 if (search->summary) {
382                         /* reorder result in summary order */
383                         results = g_hash_table_new(g_str_hash, g_str_equal);
384                         for (i=0;i<r->value.ptrarray->len;i++) {
385                                 d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
386                                 g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
387                         }
388                         for (i=0;i<search->summary->len;i++) {
389                                 CamelMessageInfo *info = g_ptr_array_index(search->summary, i);
390                                 char *uid = (char *)camel_message_info_uid(info);
391                                 if (g_hash_table_lookup(results, uid)) {
392                                         g_ptr_array_add(matches, e_mempool_strdup(pool, uid));
393                                 }
394                         }
395                         g_hash_table_destroy(results);
396                 } else {
397                         for (i=0;i<r->value.ptrarray->len;i++) {
398                                 d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
399                                 g_ptr_array_add(matches, e_mempool_strdup(pool, g_ptr_array_index(r->value.ptrarray, i)));
400                         }
401                 }
402                 /* instead of putting the mempool_hash in the structure, we keep the api clean by
403                    putting a reference to it in a hashtable.  Lets us do some debugging and catch
404                    unfree'd results as well. */
405                 g_hash_table_insert(p->mempool_hash, matches, pool);
406         } else {
407                 g_warning("Search returned an invalid result type");
408         }
409
410         e_sexp_result_free(search->sexp, r);
411
412         if (p->threads)
413                 camel_folder_thread_messages_unref(p->threads);
414         if (p->threads_hash)
415                 g_hash_table_destroy(p->threads_hash);
416
417         p->threads = NULL;
418         p->threads_hash = NULL;
419         search->folder = NULL;
420         search->summary = NULL;
421         search->current = NULL;
422         search->body_index = NULL;
423
424         return matches;
425 }
426
427 /**
428  * camel_folder_search_search:
429  * @search: 
430  * @expr: 
431  * @uids: to search against, NULL for all uid's.
432  * @ex: 
433  * 
434  * Run a search.  Search must have had Folder already set on it, and
435  * it must implement summaries.
436  * 
437  * Return value: 
438  **/
439 GPtrArray *
440 camel_folder_search_search(CamelFolderSearch *search, const char *expr, GPtrArray *uids, CamelException *ex)
441 {
442         ESExpResult *r;
443         GPtrArray *matches = NULL, *summary_set;
444         int i;
445         GHashTable *results;
446         EMemPool *pool;
447         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
448
449         g_assert(search->folder);
450
451         p->ex = ex;
452
453         /* setup our search list, summary_hash only contains those we're interested in */
454         search->summary = camel_folder_get_summary(search->folder);
455         search->summary_hash = g_hash_table_new(g_str_hash, g_str_equal);
456
457         if (uids) {
458                 GHashTable *uids_hash = g_hash_table_new(g_str_hash, g_str_equal);
459
460                 summary_set = search->summary_set = g_ptr_array_new();
461                 for (i=0;i<uids->len;i++)
462                         g_hash_table_insert(uids_hash, uids->pdata[i], uids->pdata[i]);
463                 for (i=0;i<search->summary->len;i++)
464                         if (g_hash_table_lookup(uids_hash, camel_message_info_uid(search->summary->pdata[i])))
465                                 g_ptr_array_add(search->summary_set, search->summary->pdata[i]);
466                 g_hash_table_destroy(uids_hash);
467         } else {
468                 summary_set = search->summary;
469         }
470
471         for (i=0;i<summary_set->len;i++)
472                 g_hash_table_insert(search->summary_hash, (char *)camel_message_info_uid(summary_set->pdata[i]), summary_set->pdata[i]);
473
474         /* only re-parse if the search has changed */
475         if (search->last_search == NULL
476             || strcmp(search->last_search, expr)) {
477                 e_sexp_input_text(search->sexp, expr, strlen(expr));
478                 if (e_sexp_parse(search->sexp) == -1) {
479                         camel_exception_setv(ex, 1, _("Cannot parse search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
480                         goto fail;
481                 }
482
483                 g_free(search->last_search);
484                 search->last_search = g_strdup(expr);
485         }
486         r = e_sexp_eval(search->sexp);
487         if (r == NULL) {
488                 if (!camel_exception_is_set(ex))
489                         camel_exception_setv(ex, 1, _("Error executing search expression: %s:\n%s"), e_sexp_error(search->sexp), expr);
490                 goto fail;
491         }
492
493         matches = g_ptr_array_new();
494
495         /* now create a folder summary to return?? */
496         if (r->type == ESEXP_RES_ARRAY_PTR) {
497                 d(printf("got result ...\n"));
498
499                 /* we use a mempool to store the strings, packed in tight as possible, and freed together */
500                 /* because the strings are often short (like <8 bytes long), we would be wasting appx 50%
501                    of memory just storing the size tag that malloc assigns us and alignment padding, so this
502                    gets around that (and is faster to allocate and free as a bonus) */
503                 pool = e_mempool_new(512, 256, E_MEMPOOL_ALIGN_BYTE);
504                 /* reorder result in summary order */
505                 results = g_hash_table_new(g_str_hash, g_str_equal);
506                 for (i=0;i<r->value.ptrarray->len;i++) {
507                         d(printf("adding match: %s\n", (char *)g_ptr_array_index(r->value.ptrarray, i)));
508                         g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER (1));
509                 }
510
511                 for (i=0;i<summary_set->len;i++) {
512                         CamelMessageInfo *info = g_ptr_array_index(summary_set, i);
513                         char *uid = (char *)camel_message_info_uid(info);
514                         if (g_hash_table_lookup(results, uid))
515                                 g_ptr_array_add(matches, e_mempool_strdup(pool, uid));
516                 }
517                 g_hash_table_destroy(results);
518
519                 /* instead of putting the mempool_hash in the structure, we keep the api clean by
520                    putting a reference to it in a hashtable.  Lets us do some debugging and catch
521                    unfree'd results as well. */
522                 g_hash_table_insert(p->mempool_hash, matches, pool);
523         } else {
524                 g_warning("Search returned an invalid result type");
525         }
526
527         e_sexp_result_free(search->sexp, r);
528 fail:
529         /* these might be allocated by match-threads */
530         if (p->threads)
531                 camel_folder_thread_messages_unref(p->threads);
532         if (p->threads_hash)
533                 g_hash_table_destroy(p->threads_hash);
534         if (search->summary_set)
535                 g_ptr_array_free(search->summary_set, TRUE);
536         g_hash_table_destroy(search->summary_hash);
537         camel_folder_free_summary(search->folder, search->summary);
538
539         p->threads = NULL;
540         p->threads_hash = NULL;
541         search->folder = NULL;
542         search->summary = NULL;
543         search->summary_hash = NULL;
544         search->summary_set = NULL;
545         search->current = NULL;
546         search->body_index = NULL;
547
548         return matches;
549 }
550
551 void camel_folder_search_free_result(CamelFolderSearch *search, GPtrArray *result)
552 {
553         int i;
554         struct _CamelFolderSearchPrivate *p = _PRIVATE(search);
555         EMemPool *pool;
556
557         pool = g_hash_table_lookup(p->mempool_hash, result);
558         if (pool) {
559                 e_mempool_destroy(pool);
560                 g_hash_table_remove(p->mempool_hash, result);
561         } else {
562                 for (i=0;i<result->len;i++)
563                         g_free(g_ptr_array_index(result, i));
564         }
565         g_ptr_array_free(result, TRUE);
566 }
567
568 /* dummy function, returns false always, or an empty match array */
569 static ESExpResult *
570 search_dummy(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
571 {
572         ESExpResult *r;
573
574         if (search->current == NULL) {
575                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
576                 r->value.bool = FALSE;
577         } else {
578                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
579                 r->value.ptrarray = g_ptr_array_new();
580         }
581
582         return r;
583 }
584
585 /* impelemnt an 'array not', i.e. everything in the summary, not in the supplied array */
586 static ESExpResult *
587 search_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
588 {
589         ESExpResult *r;
590         int i;
591
592         if (argc>0) {
593                 if (argv[0]->type == ESEXP_RES_ARRAY_PTR) {
594                         GPtrArray *v = argv[0]->value.ptrarray;
595                         const char *uid;
596
597                         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
598                         r->value.ptrarray = g_ptr_array_new();
599
600                         /* not against a single message?*/
601                         if (search->current) {
602                                 int found = FALSE;
603
604                                 uid = camel_message_info_uid(search->current);
605                                 for (i=0;!found && i<v->len;i++) {
606                                         if (strcmp(uid, v->pdata[i]) == 0)
607                                                 found = TRUE;
608                                 }
609
610                                 if (!found)
611                                         g_ptr_array_add(r->value.ptrarray, (char *)uid);
612                         } else if (search->summary == NULL) {
613                                 g_warning("No summary set, 'not' against an array requires a summary");
614                         } else {
615                                 /* 'not' against the whole summary */
616                                 GHashTable *have = g_hash_table_new(g_str_hash, g_str_equal);
617                                 char **s;
618                                 CamelMessageInfo **m;
619
620                                 s = (char **)v->pdata;
621                                 for (i=0;i<v->len;i++)
622                                         g_hash_table_insert(have, s[i], s[i]);
623
624                                 v = search->summary_set?search->summary_set:search->summary;
625                                 m = (CamelMessageInfo **)v->pdata;
626                                 for (i=0;i<v->len;i++) {
627                                         char *uid = (char *)camel_message_info_uid(m[i]);
628
629                                         if (g_hash_table_lookup(have, uid) == NULL)
630                                                 g_ptr_array_add(r->value.ptrarray, uid);
631                                 }
632                                 g_hash_table_destroy(have);
633                         }
634                 } else {
635                         int res = TRUE;
636
637                         if (argv[0]->type == ESEXP_RES_BOOL)
638                                 res = ! argv[0]->value.bool;
639
640                         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
641                         r->value.bool = res;
642                 }
643         } else {
644                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
645                 r->value.bool = TRUE;
646         }
647
648         return r;
649 }
650
651 static ESExpResult *
652 search_match_all(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
653 {
654         int i;
655         ESExpResult *r, *r1;
656         GPtrArray *v;
657
658         if (argc>1) {
659                 g_warning("match-all only takes a single argument, other arguments ignored");
660         }
661
662         /* we are only matching a single message?  or already inside a match-all? */
663         if (search->current) {
664                 d(printf("matching against 1 message: %s\n", camel_message_info_subject(search->current)));
665
666                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
667                 r->value.bool = FALSE;
668
669                 if (argc>0) {
670                         r1 = e_sexp_term_eval(f, argv[0]);
671                         if (r1->type == ESEXP_RES_BOOL) {
672                                 r->value.bool = r1->value.bool;
673                         } else {
674                                 g_warning("invalid syntax, matches require a single bool result");
675                                 e_sexp_fatal_error(f, _("(match-all) requires a single bool result"));
676                         }
677                         e_sexp_result_free(f, r1);
678                 } else {
679                         r->value.bool = TRUE;
680                 }
681                 return r;
682         }
683
684         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
685         r->value.ptrarray = g_ptr_array_new();
686
687         if (search->summary == NULL) {
688                 /* TODO: make it work - e.g. use the folder and so forth for a slower search */
689                 g_warning("No summary supplied, match-all doesn't work with no summary");
690                 g_assert(0);
691                 return r;
692         }
693
694         v = search->summary_set?search->summary_set:search->summary;
695         for (i=0;i<v->len;i++) {
696                 const char *uid;
697
698                 search->current = g_ptr_array_index(v, i);
699                 uid = camel_message_info_uid(search->current);
700
701                 if (argc>0) {
702                         r1 = e_sexp_term_eval(f, argv[0]);
703                         if (r1->type == ESEXP_RES_BOOL) {
704                                 if (r1->value.bool)
705                                         g_ptr_array_add(r->value.ptrarray, (char *)uid);
706                         } else {
707                                 g_warning("invalid syntax, matches require a single bool result");
708                                 e_sexp_fatal_error(f, _("(match-all) requires a single bool result"));
709                         }
710                         e_sexp_result_free(f, r1);
711                 } else {
712                         g_ptr_array_add(r->value.ptrarray, (char *)uid);
713                 }
714         }
715         search->current = NULL;
716
717         return r;
718 }
719
720 static void
721 fill_thread_table(struct _CamelFolderThreadNode *root, GHashTable *id_hash)
722 {
723         while (root) {
724                 g_hash_table_insert(id_hash, (char *)camel_message_info_uid(root->message), root);
725                 if (root->child)
726                         fill_thread_table(root->child, id_hash);
727                 root = root->next;
728         }
729 }
730
731 static void
732 add_thread_results(struct _CamelFolderThreadNode *root, GHashTable *result_hash)
733 {
734         while (root) {
735                 g_hash_table_insert(result_hash, (char *)camel_message_info_uid(root->message), GINT_TO_POINTER (1));
736                 if (root->child)
737                         add_thread_results(root->child, result_hash);
738                 root = root->next;
739         }
740 }
741
742 static void
743 add_results(char *uid, void *dummy, GPtrArray *result)
744 {
745         g_ptr_array_add(result, uid);
746 }
747
748 static ESExpResult *
749 search_match_threads(struct _ESExp *f, int argc, struct _ESExpTerm **argv, CamelFolderSearch *search)
750 {
751         ESExpResult *r;
752         struct _CamelFolderSearchPrivate *p = search->priv;
753         int i, type;
754         GHashTable *results;
755
756         /* not supported in match-all */
757         if (search->current)
758                 e_sexp_fatal_error(f, _("(match-threads) not allowed inside match-all"));
759
760         if (argc == 0)
761                 e_sexp_fatal_error(f, _("(match-threads) requires a match type string"));
762
763         r = e_sexp_term_eval(f, argv[0]);
764         if (r->type != ESEXP_RES_STRING)
765                 e_sexp_fatal_error(f, _("(match-threads) requires a match type string"));
766
767         type = 0;
768         if (!strcmp(r->value.string, "none"))
769                 type = 0;
770         else if (!strcmp(r->value.string, "all"))
771                 type = 1;
772         else if (!strcmp(r->value.string, "replies"))
773                 type = 2;
774         else if (!strcmp(r->value.string, "replies_parents"))
775                 type = 3;
776         else if (!strcmp(r->value.string, "single"))
777                 type = 4;
778         e_sexp_result_free(f, r);
779
780         /* behave as (begin does */
781         r = NULL;
782         for (i=1;i<argc;i++) {
783                 if (r)
784                         e_sexp_result_free(f, r);
785                 r = e_sexp_term_eval(f, argv[i]);
786         }
787
788         if (r == NULL || r->type != ESEXP_RES_ARRAY_PTR)
789                 e_sexp_fatal_error(f, _("(match-threads) expects an array result"));
790
791         if (type == 0)
792                 return r;
793
794         if (search->folder == NULL)
795                 e_sexp_fatal_error(f, _("(match-threads) requires the folder set"));
796
797         /* cache this, so we only have to re-calculate once per search at most */
798         if (p->threads == NULL) {
799                 p->threads = camel_folder_thread_messages_new(search->folder, NULL, TRUE);
800                 p->threads_hash = g_hash_table_new(g_str_hash, g_str_equal);
801
802                 fill_thread_table(p->threads->tree, p->threads_hash);
803         }
804
805         results = g_hash_table_new(g_str_hash, g_str_equal);
806         for (i=0;i<r->value.ptrarray->len;i++) {
807                 struct _CamelFolderThreadNode *node, *scan;
808
809                 if (type != 4)
810                         g_hash_table_insert(results, g_ptr_array_index(r->value.ptrarray, i), GINT_TO_POINTER(1));
811
812                 node = g_hash_table_lookup(p->threads_hash, (char *)g_ptr_array_index(r->value.ptrarray, i));
813                 if (node == NULL) /* this shouldn't happen but why cry over spilt milk */
814                         continue;
815
816                 /* select messages in thread according to search criteria */
817                 if (type == 4) {
818                         if (node->child == NULL && node->parent == NULL)
819                                 g_hash_table_insert(results, (char *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
820                 } else {
821                         if (type == 3) {
822                                 scan = node;
823                                 while (scan && scan->parent) {
824                                         scan = scan->parent;
825                                         g_hash_table_insert(results, (char *)camel_message_info_uid(scan->message), GINT_TO_POINTER(1));
826                                 }
827                         } else if (type == 1) {
828                                 while (node && node->parent)
829                                         node = node->parent;
830                         }
831                         g_hash_table_insert(results, (char *)camel_message_info_uid(node->message), GINT_TO_POINTER(1));
832                         if (node->child)
833                                 add_thread_results(node->child, results);
834                 }
835         }
836         e_sexp_result_free(f, r);
837
838         r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
839         r->value.ptrarray = g_ptr_array_new();
840
841         g_hash_table_foreach(results, (GHFunc)add_results, r->value.ptrarray);
842         g_hash_table_destroy(results);
843
844         return r;
845 }
846
847 static ESExpResult *
848 check_header(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search, camel_search_match_t how)
849 {
850         ESExpResult *r;
851         int truth = FALSE;
852
853         r(printf("executing check-header %d\n", how));
854
855         /* are we inside a match-all? */
856         if (search->current && argc>1
857             && argv[0]->type == ESEXP_RES_STRING) {
858                 char *headername;
859                 const char *header = NULL;
860                 char strbuf[32];
861                 int i, j;
862                 camel_search_t type = CAMEL_SEARCH_TYPE_ASIS;
863                 struct _camel_search_words *words;
864
865                 /* only a subset of headers are supported .. */
866                 headername = argv[0]->value.string;
867                 if (!g_ascii_strcasecmp(headername, "subject")) {
868                         header = camel_message_info_subject(search->current);
869                 } else if (!g_ascii_strcasecmp(headername, "date")) {
870                         /* FIXME: not a very useful form of the date */
871                         sprintf(strbuf, "%d", (int)camel_message_info_date_sent(search->current));
872                         header = strbuf;
873                 } else if (!g_ascii_strcasecmp(headername, "from")) {
874                         header = camel_message_info_from(search->current);
875                         type = CAMEL_SEARCH_TYPE_ADDRESS;
876                 } else if (!g_ascii_strcasecmp(headername, "to")) {
877                         header = camel_message_info_to(search->current);
878                         type = CAMEL_SEARCH_TYPE_ADDRESS;
879                 } else if (!g_ascii_strcasecmp(headername, "cc")) {
880                         header = camel_message_info_cc(search->current);
881                         type = CAMEL_SEARCH_TYPE_ADDRESS;
882                 } else if (!g_ascii_strcasecmp(headername, "x-camel-mlist")) {
883                         header = camel_message_info_mlist(search->current);
884                         type = CAMEL_SEARCH_TYPE_MLIST;
885                 } else {
886                         e_sexp_resultv_free(f, argc, argv);
887                         e_sexp_fatal_error(f, _("Performing query on unknown header: %s"), headername);
888                 }
889
890                 if (header == NULL)
891                         header = "";
892
893                 /* performs an OR of all words */
894                 for (i=1;i<argc && !truth;i++) {
895                         if (argv[i]->type == ESEXP_RES_STRING) {
896                                 if (argv[i]->value.string[0] == 0) {
897                                         truth = TRUE;
898                                 } else if (how == CAMEL_SEARCH_MATCH_CONTAINS) {
899                                         /* doesn't make sense to split words on anything but contains i.e. we can't have an ending match different words */
900                                         words = camel_search_words_split(argv[i]->value.string);
901                                         truth = TRUE;
902                                         for (j=0;j<words->len && truth;j++) {
903                                                 truth = camel_search_header_match(header, words->words[j]->word, how, type, NULL);
904                                         }
905                                         camel_search_words_free(words);
906                                 } else {
907                                         truth = camel_search_header_match(header, argv[i]->value.string, how, type, NULL);
908                                 }
909                         }
910                 }
911         }
912         /* TODO: else, find all matches */
913
914         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
915         r->value.bool = truth;
916
917         return r;
918 }
919
920 static ESExpResult *
921 search_header_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
922 {
923         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_CONTAINS);
924 }
925
926 static ESExpResult *
927 search_header_matches(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
928 {
929         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_EXACT);
930 }
931
932 static ESExpResult *
933 search_header_starts_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
934 {
935         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_STARTS);
936 }
937
938 static ESExpResult *
939 search_header_ends_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
940 {
941         return check_header(f, argc, argv, search, CAMEL_SEARCH_MATCH_ENDS);
942 }
943
944 static ESExpResult *
945 search_header_exists (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
946 {
947         ESExpResult *r;
948         
949         r(printf ("executing header-exists\n"));
950         
951         if (search->current) {
952                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
953                 if (argc == 1 && argv[0]->type == ESEXP_RES_STRING)
954                         r->value.bool = camel_medium_get_header(CAMEL_MEDIUM(search->current), argv[0]->value.string) != NULL;
955                 
956         } else {
957                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
958                 r->value.ptrarray = g_ptr_array_new();
959         }
960         
961         return r;
962 }
963
964 /* this is just to OR results together */
965 struct _glib_sux_donkeys {
966         int count;
967         GPtrArray *uids;
968 };
969
970 /* or, store all unique values */
971 static void
972 g_lib_sux_htor(char *key, int value, struct _glib_sux_donkeys *fuckup)
973 {
974         g_ptr_array_add(fuckup->uids, key);
975 }
976
977 /* and, only store duplicates */
978 static void
979 g_lib_sux_htand(char *key, int value, struct _glib_sux_donkeys *fuckup)
980 {
981         if (value == fuckup->count)
982                 g_ptr_array_add(fuckup->uids, key);
983 }
984
985 static int
986 match_message_index(CamelIndex *idx, const char *uid, const char *match, CamelException *ex)
987 {
988         CamelIndexCursor *wc, *nc;
989         const char *word, *name;
990         int truth = FALSE;
991
992         wc = camel_index_words(idx);
993         if (wc) {
994                 while (!truth && (word = camel_index_cursor_next(wc))) {
995                         if (camel_ustrstrcase(word,match) != NULL) {
996                                 /* perf: could have the wc cursor return the name cursor */
997                                 nc = camel_index_find(idx, word);
998                                 if (nc) {
999                                         while (!truth && (name = camel_index_cursor_next(nc)))
1000                                                 truth = strcmp(name, uid) == 0;
1001                                         camel_object_unref((CamelObject *)nc);
1002                                 }
1003                         }
1004                 }
1005                 camel_object_unref((CamelObject *)wc);
1006         }
1007
1008         return truth;
1009 }
1010
1011 /*
1012  "one two" "three" "four five"
1013
1014   one and two
1015 or
1016   three
1017 or
1018   four and five
1019 */
1020
1021 /* returns messages which contain all words listed in words */
1022 static GPtrArray *
1023 match_words_index(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
1024 {
1025         GPtrArray *result = g_ptr_array_new();
1026         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1027         struct _glib_sux_donkeys lambdafoo;
1028         CamelIndexCursor *wc, *nc;
1029         const char *word, *name;
1030         CamelMessageInfo *mi;
1031         int i;
1032
1033         /* we can have a maximum of 32 words, as we use it as the AND mask */
1034                         
1035         wc = camel_index_words(search->body_index);
1036         if (wc) {
1037                 while ((word = camel_index_cursor_next(wc))) {
1038                         for (i=0;i<words->len;i++) {
1039                                 if (camel_ustrstrcase(word, words->words[i]->word) != NULL) {
1040                                         /* perf: could have the wc cursor return the name cursor */
1041                                         nc = camel_index_find(search->body_index, word);
1042                                         if (nc) {
1043                                                 while ((name = camel_index_cursor_next(nc))) {
1044                                                         mi = g_hash_table_lookup(search->summary_hash, name);
1045                                                         if (mi) {
1046                                                                 int mask;
1047                                                                 const char *uid = camel_message_info_uid(mi);
1048
1049                                                                 mask = (GPOINTER_TO_INT(g_hash_table_lookup(ht, uid))) | (1<<i);
1050                                                                 g_hash_table_insert(ht, (char *)uid, GINT_TO_POINTER(mask));
1051                                                         }
1052                                                 }
1053                                                 camel_object_unref((CamelObject *)nc);
1054                                         }
1055                                 }
1056                         }
1057                 }
1058                 camel_object_unref((CamelObject *)wc);
1059
1060                 lambdafoo.uids = result;
1061                 lambdafoo.count = (1<<words->len) - 1;
1062                 g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htand, &lambdafoo);
1063                 g_hash_table_destroy(ht);
1064         }
1065
1066         return result;
1067 }
1068
1069 static gboolean
1070 match_words_1message (CamelDataWrapper *object, struct _camel_search_words *words, guint32 *mask)
1071 {
1072         CamelDataWrapper *containee;
1073         int truth = FALSE;
1074         int parts, i;
1075         
1076         containee = camel_medium_get_content_object (CAMEL_MEDIUM (object));
1077         
1078         if (containee == NULL)
1079                 return FALSE;
1080         
1081         /* using the object types is more accurate than using the mime/types */
1082         if (CAMEL_IS_MULTIPART (containee)) {
1083                 parts = camel_multipart_get_number (CAMEL_MULTIPART (containee));
1084                 for (i = 0; i < parts && truth == FALSE; i++) {
1085                         CamelDataWrapper *part = (CamelDataWrapper *)camel_multipart_get_part (CAMEL_MULTIPART (containee), i);
1086                         if (part)
1087                                 truth = match_words_1message(part, words, mask);
1088                 }
1089         } else if (CAMEL_IS_MIME_MESSAGE (containee)) {
1090                 /* for messages we only look at its contents */
1091                 truth = match_words_1message((CamelDataWrapper *)containee, words, mask);
1092         } else if (camel_content_type_is(CAMEL_DATA_WRAPPER (containee)->mime_type, "text", "*")) {
1093                 /* for all other text parts, we look inside, otherwise we dont care */
1094                 CamelStreamMem *mem = (CamelStreamMem *)camel_stream_mem_new ();
1095
1096                 /* FIXME: The match should be part of a stream op */
1097                 camel_data_wrapper_decode_to_stream (containee, CAMEL_STREAM (mem));
1098                 camel_stream_write (CAMEL_STREAM (mem), "", 1);
1099                 for (i=0;i<words->len;i++) {
1100                         /* FIXME: This is horridly slow, and should use a real search algorithm */
1101                         if (camel_ustrstrcase(mem->buffer->data, words->words[i]->word) != NULL) {
1102                                 *mask |= (1<<i);
1103                                 /* shortcut a match */
1104                                 if (*mask == (1<<(words->len))-1)
1105                                         return TRUE;
1106                         }
1107                 }
1108                 
1109                 camel_object_unref (mem);
1110         }
1111         
1112         return truth;
1113 }
1114
1115 static gboolean
1116 match_words_message(CamelFolder *folder, const char *uid, struct _camel_search_words *words, CamelException *ex)
1117 {
1118         guint32 mask;
1119         CamelMimeMessage *msg;
1120         CamelException x = CAMEL_EXCEPTION_INITIALISER;
1121         int truth;
1122
1123         msg = camel_folder_get_message(folder, uid, &x);
1124         if (msg) {
1125                 mask = 0;
1126                 truth = match_words_1message((CamelDataWrapper *)msg, words, &mask);
1127                 camel_object_unref((CamelObject *)msg);
1128         } else {
1129                 camel_exception_clear(&x);
1130                 truth = FALSE;
1131         }
1132
1133         return truth;
1134 }
1135
1136 static GPtrArray *
1137 match_words_messages(CamelFolderSearch *search, struct _camel_search_words *words, CamelException *ex)
1138 {
1139         int i;
1140         GPtrArray *matches = g_ptr_array_new();
1141
1142         if (search->body_index) {
1143                 GPtrArray *indexed;
1144                 struct _camel_search_words *simple;
1145
1146                 simple = camel_search_words_simple(words);
1147                 indexed = match_words_index(search, simple, ex);
1148                 camel_search_words_free(simple);
1149
1150                 for (i=0;i<indexed->len;i++) {
1151                         const char *uid = g_ptr_array_index(indexed, i);
1152                         
1153                         if (match_words_message(search->folder, uid, words, ex))
1154                                 g_ptr_array_add(matches, (char *)uid);
1155                 }
1156                 
1157                 g_ptr_array_free(indexed, TRUE);
1158         } else {
1159                 GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1160
1161                 for (i=0;i<v->len;i++) {
1162                         CamelMessageInfo *info = g_ptr_array_index(v, i);
1163                         const char *uid = camel_message_info_uid(info);
1164                         
1165                         if (match_words_message(search->folder, uid, words, ex))
1166                                 g_ptr_array_add(matches, (char *)uid);
1167                 }
1168         }
1169
1170         return matches;
1171 }
1172
1173 static ESExpResult *
1174 search_body_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1175 {
1176         int i, j;
1177         CamelException *ex = search->priv->ex;
1178         struct _camel_search_words *words;
1179         ESExpResult *r;
1180         struct _glib_sux_donkeys lambdafoo;
1181
1182         if (search->current) {  
1183                 int truth = FALSE;
1184
1185                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1186                         truth = TRUE;
1187                 } else {
1188                         for (i=0;i<argc && !truth;i++) {
1189                                 if (argv[i]->type == ESEXP_RES_STRING) {
1190                                         words = camel_search_words_split(argv[i]->value.string);
1191                                         truth = TRUE;
1192                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1193                                                 for (j=0;j<words->len && truth;j++)
1194                                                         truth = match_message_index(search->body_index, camel_message_info_uid(search->current), words->words[j]->word, ex);
1195                                         } else {
1196                                                 /* TODO: cache current message incase of multiple body search terms */
1197                                                 truth = match_words_message(search->folder, camel_message_info_uid(search->current), words, ex);
1198                                         }
1199                                         camel_search_words_free(words);
1200                                 }
1201                         }
1202                 }
1203                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1204                 r->value.bool = truth;
1205         } else {
1206                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1207                 r->value.ptrarray = g_ptr_array_new();
1208
1209                 if (argc == 1 && argv[0]->value.string[0] == 0) {
1210                         GPtrArray *v = search->summary_set?search->summary_set:search->summary;
1211
1212                         for (i=0;i<v->len;i++) {
1213                                 CamelMessageInfo *info = g_ptr_array_index(v, i);
1214
1215                                 g_ptr_array_add(r->value.ptrarray, (char *)camel_message_info_uid(info));
1216                         }
1217                 } else {
1218                         GHashTable *ht = g_hash_table_new(g_str_hash, g_str_equal);
1219                         GPtrArray *matches;
1220
1221                         for (i=0;i<argc;i++) {
1222                                 if (argv[i]->type == ESEXP_RES_STRING) {
1223                                         words = camel_search_words_split(argv[i]->value.string);
1224                                         if ((words->type & CAMEL_SEARCH_WORD_COMPLEX) == 0 && search->body_index) {
1225                                                 matches = match_words_index(search, words, ex);
1226                                         } else {
1227                                                 matches = match_words_messages(search, words, ex);
1228                                         }
1229                                         for (j=0;j<matches->len;j++)
1230                                                 g_hash_table_insert(ht, matches->pdata[j], matches->pdata[j]);
1231                                         g_ptr_array_free(matches, TRUE);
1232                                         camel_search_words_free(words);
1233                                 }
1234                         }
1235                         lambdafoo.uids = r->value.ptrarray;
1236                         g_hash_table_foreach(ht, (GHFunc)g_lib_sux_htor, &lambdafoo);
1237                         g_hash_table_destroy(ht);
1238                 }
1239         }
1240
1241         return r;
1242 }
1243
1244 static ESExpResult *
1245 search_user_flag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1246 {
1247         ESExpResult *r;
1248         int i;
1249
1250         r(printf("executing user-flag\n"));
1251
1252         /* are we inside a match-all? */
1253         if (search->current) {
1254                 int truth = FALSE;
1255                 /* performs an OR of all words */
1256                 for (i=0;i<argc && !truth;i++) {
1257                         if (argv[i]->type == ESEXP_RES_STRING
1258                             && camel_message_info_user_flag(search->current, argv[i]->value.string)) {
1259                                 truth = TRUE;
1260                                 break;
1261                         }
1262                 }
1263                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1264                 r->value.bool = truth;
1265         } else {
1266                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1267                 r->value.ptrarray = g_ptr_array_new();
1268         }
1269
1270         return r;
1271 }
1272
1273 static ESExpResult *
1274 search_system_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1275 {
1276         ESExpResult *r;
1277         
1278         r(printf ("executing system-flag\n"));
1279         
1280         if (search->current) {
1281                 gboolean truth = FALSE;
1282                 
1283                 if (argc == 1)
1284                         truth = camel_system_flag_get (camel_message_info_flags(search->current), argv[0]->value.string);
1285                 
1286                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1287                 r->value.bool = truth;
1288         } else {
1289                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1290                 r->value.ptrarray = g_ptr_array_new ();
1291         }
1292         
1293         return r;
1294 }
1295
1296 static ESExpResult *
1297 search_user_tag(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1298 {
1299         const char *value = NULL;
1300         ESExpResult *r;
1301         
1302         r(printf("executing user-tag\n"));
1303         
1304         if (argc == 1)
1305                 value = camel_message_info_user_tag(search->current, argv[0]->value.string);
1306         
1307         r = e_sexp_result_new(f, ESEXP_RES_STRING);
1308         r->value.string = g_strdup (value ? value : "");
1309         
1310         return r;
1311 }
1312
1313 static ESExpResult *
1314 search_get_sent_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1315 {
1316         ESExpResult *r;
1317
1318         r(printf("executing get-sent-date\n"));
1319
1320         /* are we inside a match-all? */
1321         if (s->current) {
1322                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1323
1324                 r->value.number = camel_message_info_date_sent(s->current);
1325         } else {
1326                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1327                 r->value.ptrarray = g_ptr_array_new ();
1328         }
1329
1330         return r;
1331 }
1332
1333 static ESExpResult *
1334 search_get_received_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1335 {
1336         ESExpResult *r;
1337
1338         r(printf("executing get-received-date\n"));
1339
1340         /* are we inside a match-all? */
1341         if (s->current) {
1342                 r = e_sexp_result_new(f, ESEXP_RES_INT);
1343
1344                 r->value.number = camel_message_info_date_received(s->current);
1345         } else {
1346                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1347                 r->value.ptrarray = g_ptr_array_new ();
1348         }
1349
1350         return r;
1351 }
1352
1353 static ESExpResult *
1354 search_get_current_date(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1355 {
1356         ESExpResult *r;
1357
1358         r(printf("executing get-current-date\n"));
1359
1360         r = e_sexp_result_new(f, ESEXP_RES_INT);
1361         r->value.number = time (NULL);
1362         return r;
1363 }
1364
1365 static ESExpResult *
1366 search_get_size (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *s)
1367 {
1368         ESExpResult *r;
1369         
1370         r(printf("executing get-size\n"));
1371         
1372         /* are we inside a match-all? */
1373         if (s->current) {
1374                 r = e_sexp_result_new (f, ESEXP_RES_INT);
1375                 r->value.number = camel_message_info_size(s->current) / 1024;
1376         } else {
1377                 r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
1378                 r->value.ptrarray = g_ptr_array_new ();
1379         }
1380         
1381         return r;
1382 }
1383
1384 static ESExpResult *
1385 search_uid(struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
1386 {
1387         ESExpResult *r;
1388         int i;
1389
1390         r(printf("executing uid\n"));
1391
1392         /* are we inside a match-all? */
1393         if (search->current) {
1394                 int truth = FALSE;
1395                 const char *uid = camel_message_info_uid(search->current);
1396
1397                 /* performs an OR of all words */
1398                 for (i=0;i<argc && !truth;i++) {
1399                         if (argv[i]->type == ESEXP_RES_STRING
1400                             && !strcmp(uid, argv[i]->value.string)) {
1401                                 truth = TRUE;
1402                                 break;
1403                         }
1404                 }
1405                 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
1406                 r->value.bool = truth;
1407         } else {
1408                 r = e_sexp_result_new(f, ESEXP_RES_ARRAY_PTR);
1409                 r->value.ptrarray = g_ptr_array_new();
1410                 for (i=0;i<argc;i++) {
1411                         if (argv[i]->type == ESEXP_RES_STRING)
1412                                 g_ptr_array_add(r->value.ptrarray, argv[i]->value.string);
1413                 }
1414         }
1415
1416         return r;
1417 }