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