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