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