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