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