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