4
* This program is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the License, or (at your option) version 3.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with the program; if not, see <http://www.gnu.org/licenses/>
19
#include "camel-imapx-search.h"
21
#include "camel-offline-store.h"
22
#include "camel-search-private.h"
24
#define CAMEL_IMAPX_SEARCH_GET_PRIVATE(obj) \
25
(G_TYPE_INSTANCE_GET_PRIVATE \
26
((obj), CAMEL_TYPE_IMAPX_SEARCH, CamelIMAPXSearchPrivate))
28
struct _CamelIMAPXSearchPrivate {
30
gint *local_data_search; /* not NULL, if testing whether all used headers are all locally available */
41
CAMEL_TYPE_FOLDER_SEARCH)
44
imapx_search_set_property (GObject *object,
49
switch (property_id) {
51
camel_imapx_search_set_server (
52
CAMEL_IMAPX_SEARCH (object),
53
g_value_get_object (value));
57
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
61
imapx_search_get_property (GObject *object,
66
switch (property_id) {
70
camel_imapx_search_ref_server (
71
CAMEL_IMAPX_SEARCH (object)));
75
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
79
imapx_search_dispose (GObject *object)
81
CamelIMAPXSearchPrivate *priv;
83
priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (object);
85
g_weak_ref_set (&priv->server, NULL);
87
/* Chain up to parent's dispose() method. */
88
G_OBJECT_CLASS (camel_imapx_search_parent_class)->dispose (object);
91
static CamelSExpResult *
92
imapx_search_result_match_all (CamelSExp *sexp,
93
CamelFolderSearch *search)
95
CamelSExpResult *result;
97
g_return_val_if_fail (search != NULL, NULL);
99
if (search->current != NULL) {
100
result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
101
result->value.boolean = TRUE;
105
result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
106
result->value.ptrarray = g_ptr_array_new ();
108
for (ii = 0; ii < search->summary->len; ii++)
110
result->value.ptrarray,
111
(gpointer) search->summary->pdata[ii]);
117
static CamelSExpResult *
118
imapx_search_result_match_none (CamelSExp *sexp,
119
CamelFolderSearch *search)
121
CamelSExpResult *result;
123
g_return_val_if_fail (search != NULL, NULL);
125
if (search->current != NULL) {
126
result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
127
result->value.boolean = FALSE;
129
result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
130
result->value.ptrarray = g_ptr_array_new ();
136
static CamelSExpResult *
137
imapx_search_process_criteria (CamelSExp *sexp,
138
CamelFolderSearch *search,
139
CamelIMAPXServer *server,
140
const GString *criteria,
141
const gchar *from_function)
143
CamelSExpResult *result;
144
GPtrArray *uids = NULL;
145
GError *error = NULL;
147
uids = camel_imapx_server_uid_search (
148
server, search->folder, criteria->str, NULL, &error);
151
g_return_val_if_fail (
152
((uids != NULL) && (error == NULL)) ||
153
((uids == NULL) && (error != NULL)), NULL);
155
/* XXX No allowance for errors in CamelSExp callbacks!
156
* Dump the error to the console and make like we
157
* got an empty result. */
160
"%s: (UID SEARCH %s): %s",
161
from_function, criteria->str, error->message);
162
uids = g_ptr_array_new ();
163
g_error_free (error);
166
if (search->current != NULL) {
167
result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
168
result->value.boolean = (uids && uids->len > 0);
170
result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
171
result->value.ptrarray = g_ptr_array_ref (uids);
174
g_ptr_array_unref (uids);
179
static CamelSExpResult *
180
imapx_search_match_all (CamelSExp *sexp,
182
CamelSExpTerm **argv,
183
CamelFolderSearch *search)
185
CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
186
CamelIMAPXServer *server;
187
CamelSExpResult *result;
189
gint local_data_search = 0, *prev_local_data_search, ii;
192
return imapx_search_result_match_none (sexp, search);
194
server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
195
if (!server || search->current || !search->summary) {
196
g_clear_object (&server);
198
/* Chain up to parent's method. */
199
return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
200
match_all (sexp, argc, argv, search);
203
/* First try to see whether all used headers are available locally - if
204
they are, then do not use server-side filtering at all. */
205
prev_local_data_search = imapx_search->priv->local_data_search;
206
imapx_search->priv->local_data_search = &local_data_search;
208
summary = search->summary_set ? search->summary_set : search->summary;
210
if (!CAMEL_IS_VEE_FOLDER (search->folder)) {
211
camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL);
214
for (ii = 0; ii < summary->len; ii++) {
215
search->current = camel_folder_summary_get (search->folder->summary, summary->pdata[ii]);
216
if (search->current) {
217
result = camel_sexp_term_eval (sexp, argv[0]);
218
camel_sexp_result_free (sexp, result);
219
camel_message_info_free (search->current);
220
search->current = NULL;
224
imapx_search->priv->local_data_search = prev_local_data_search;
226
if (local_data_search >= 0) {
227
g_clear_object (&server);
229
/* Chain up to parent's method. */
230
return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
231
match_all (sexp, argc, argv, search);
234
/* let's change the requirements a bit, the parent class expects as a result boolean,
235
but here is expected GPtrArray of matched UIDs */
236
result = camel_sexp_term_eval (sexp, argv[0]);
238
g_object_unref (server);
240
g_return_val_if_fail (result != NULL, result);
241
g_return_val_if_fail (result->type == CAMEL_SEXP_RES_ARRAY_PTR, result);
246
static CamelSExpResult *
247
imapx_search_body_contains (CamelSExp *sexp,
249
CamelSExpResult **argv,
250
CamelFolderSearch *search)
252
CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
253
CamelIMAPXServer *server;
254
CamelSExpResult *result;
258
/* Always do body-search server-side */
259
if (imapx_search->priv->local_data_search) {
260
*imapx_search->priv->local_data_search = -1;
261
return imapx_search_result_match_none (sexp, search);
264
/* Match everything if argv = [""] */
265
if (argc == 1 && argv[0]->value.string[0] == '\0')
266
return imapx_search_result_match_all (sexp, search);
268
/* Match nothing if empty argv or empty summary. */
269
if (argc == 0 || search->summary->len == 0)
270
return imapx_search_result_match_none (sexp, search);
272
server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
274
/* This will be NULL if we're offline. Search from cache. */
275
if (server == NULL) {
276
/* Chain up to parent's method. */
277
return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
278
body_contains (sexp, argc, argv, search);
281
/* Build the IMAP search criteria. */
283
criteria = g_string_sized_new (128);
285
if (search->current != NULL) {
288
/* Limit the search to a single UID. */
289
uid = camel_message_info_uid (search->current);
290
g_string_append_printf (criteria, "UID %s", uid);
293
for (ii = 0; ii < argc; ii++) {
294
struct _camel_search_words *words;
297
if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
300
/* Handle multiple search words within a single term. */
301
term = (const guchar *) argv[ii]->value.string;
302
words = camel_search_words_split (term);
304
for (jj = 0; jj < words->len; jj++) {
307
if (criteria->len > 0)
308
g_string_append_c (criteria, ' ');
310
g_string_append (criteria, "BODY \"");
312
cp = words->words[jj]->word;
313
for (; *cp != '\0'; cp++) {
314
if (*cp == '\\' || *cp == '"')
315
g_string_append_c (criteria, '\\');
316
g_string_append_c (criteria, *cp);
319
g_string_append_c (criteria, '"');
323
result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC);
325
g_string_free (criteria, TRUE);
326
g_object_unref (server);
332
imapx_search_is_header_from_summary (const gchar *header_name)
334
return g_ascii_strcasecmp (header_name, "From") == 0 ||
335
g_ascii_strcasecmp (header_name, "To") == 0 ||
336
g_ascii_strcasecmp (header_name, "CC") == 0 ||
337
g_ascii_strcasecmp (header_name, "Subject") == 0;
340
static CamelSExpResult *
341
imapx_search_header_contains (CamelSExp *sexp,
343
CamelSExpResult **argv,
344
CamelFolderSearch *search)
346
CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
347
CamelIMAPXServer *server;
348
CamelSExpResult *result;
349
const gchar *headername, *command = NULL;
353
/* Match nothing if empty argv or empty summary. */
355
argv[0]->type != CAMEL_SEXP_RES_STRING ||
356
search->summary->len == 0)
357
return imapx_search_result_match_none (sexp, search);
359
headername = argv[0]->value.string;
361
if (imapx_search_is_header_from_summary (headername)) {
362
if (imapx_search->priv->local_data_search) {
363
if (*imapx_search->priv->local_data_search >= 0)
364
*imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1;
365
return imapx_search_result_match_all (sexp, search);
368
/* Chain up to parent's method. */
369
return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
370
header_contains (sexp, argc, argv, search);
371
} else if (imapx_search->priv->local_data_search) {
372
*imapx_search->priv->local_data_search = -1;
373
return imapx_search_result_match_none (sexp, search);
376
server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
378
/* This will be NULL if we're offline. Search from cache. */
379
if (server == NULL) {
380
/* Chain up to parent's method. */
381
return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
382
header_contains (sexp, argc, argv, search);
385
/* Build the IMAP search criteria. */
387
criteria = g_string_sized_new (128);
389
if (search->current != NULL) {
392
/* Limit the search to a single UID. */
393
uid = camel_message_info_uid (search->current);
394
g_string_append_printf (criteria, "UID %s", uid);
397
if (g_ascii_strcasecmp (headername, "From") == 0)
399
else if (g_ascii_strcasecmp (headername, "To") == 0)
401
else if (g_ascii_strcasecmp (headername, "CC") == 0)
403
else if (g_ascii_strcasecmp (headername, "Bcc") == 0)
405
else if (g_ascii_strcasecmp (headername, "Subject") == 0)
408
for (ii = 1; ii < argc; ii++) {
409
struct _camel_search_words *words;
412
if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
415
/* Handle multiple search words within a single term. */
416
term = (const guchar *) argv[ii]->value.string;
417
words = camel_search_words_split (term);
419
for (jj = 0; jj < words->len; jj++) {
422
if (criteria->len > 0)
423
g_string_append_c (criteria, ' ');
426
g_string_append (criteria, command);
428
g_string_append_printf (criteria, "HEADER \"%s\"", headername);
430
g_string_append (criteria, " \"");
432
cp = words->words[jj]->word;
433
for (; *cp != '\0'; cp++) {
434
if (*cp == '\\' || *cp == '"')
435
g_string_append_c (criteria, '\\');
436
g_string_append_c (criteria, *cp);
439
g_string_append_c (criteria, '"');
443
result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC);
445
g_string_free (criteria, TRUE);
446
g_object_unref (server);
451
static CamelSExpResult *
452
imapx_search_header_exists (CamelSExp *sexp,
454
CamelSExpResult **argv,
455
CamelFolderSearch *search)
457
CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
458
CamelIMAPXServer *server;
459
CamelSExpResult *result;
463
/* Match nothing if empty argv or empty summary. */
464
if (argc == 0 || search->summary->len == 0)
465
return imapx_search_result_match_none (sexp, search);
467
/* Check if asking for locally stored headers only */
468
for (ii = 0; ii < argc; ii++) {
469
if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
472
if (!imapx_search_is_header_from_summary (argv[ii]->value.string))
476
/* All headers are from summary */
478
if (imapx_search->priv->local_data_search) {
479
if (*imapx_search->priv->local_data_search >= 0)
480
*imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1;
482
return imapx_search_result_match_all (sexp, search);
485
/* Chain up to parent's method. */
486
return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
487
header_exists (sexp, argc, argv, search);
488
} else if (imapx_search->priv->local_data_search) {
489
*imapx_search->priv->local_data_search = -1;
490
return imapx_search_result_match_none (sexp, search);
493
server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
495
/* This will be NULL if we're offline. Search from cache. */
496
if (server == NULL) {
497
/* Chain up to parent's method. */
498
return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
499
header_exists (sexp, argc, argv, search);
502
/* Build the IMAP search criteria. */
504
criteria = g_string_sized_new (128);
506
if (search->current != NULL) {
509
/* Limit the search to a single UID. */
510
uid = camel_message_info_uid (search->current);
511
g_string_append_printf (criteria, "UID %s", uid);
514
for (ii = 0; ii < argc; ii++) {
515
const gchar *headername;
517
if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
520
headername = argv[ii]->value.string;
522
if (criteria->len > 0)
523
g_string_append_c (criteria, ' ');
525
g_string_append_printf (criteria, "HEADER \"%s\" \"\"", headername);
528
result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC);
530
g_string_free (criteria, TRUE);
531
g_object_unref (server);
537
camel_imapx_search_class_init (CamelIMAPXSearchClass *class)
539
GObjectClass *object_class;
540
CamelFolderSearchClass *search_class;
542
g_type_class_add_private (class, sizeof (CamelIMAPXSearchPrivate));
544
object_class = G_OBJECT_CLASS (class);
545
object_class->set_property = imapx_search_set_property;
546
object_class->get_property = imapx_search_get_property;
547
object_class->dispose = imapx_search_dispose;
549
search_class = CAMEL_FOLDER_SEARCH_CLASS (class);
550
search_class->match_all = imapx_search_match_all;
551
search_class->body_contains = imapx_search_body_contains;
552
search_class->header_contains = imapx_search_header_contains;
553
search_class->header_exists = imapx_search_header_exists;
555
g_object_class_install_property (
558
g_param_spec_object (
561
"Server proxy for server-side searches",
562
CAMEL_TYPE_IMAPX_SERVER,
564
G_PARAM_STATIC_STRINGS));
568
camel_imapx_search_init (CamelIMAPXSearch *search)
570
search->priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (search);
571
search->priv->local_data_search = NULL;
575
* camel_imapx_search_new:
577
* Returns a new #CamelIMAPXSearch instance.
579
* The #CamelIMAPXSearch must be given a #CamelIMAPXSearch:server before
580
* it can issue server-side search requests. Otherwise it will fallback
581
* to the default #CamelFolderSearch behavior.
583
* Returns: a new #CamelIMAPXSearch
588
camel_imapx_search_new (void)
590
return g_object_new (CAMEL_TYPE_IMAPX_SEARCH, NULL);
594
* camel_imapx_search_ref_server:
595
* @search: a #CamelIMAPXSearch
597
* Returns a #CamelIMAPXServer to use for server-side searches,
598
* or %NULL when the corresponding #CamelIMAPXStore is offline.
600
* The returned #CamelIMAPXSearch is referenced for thread-safety and
601
* must be unreferenced with g_object_unref() when finished with it.
603
* Returns: a #CamelIMAPXServer, or %NULL
608
camel_imapx_search_ref_server (CamelIMAPXSearch *search)
610
g_return_val_if_fail (CAMEL_IS_IMAPX_SEARCH (search), NULL);
612
return g_weak_ref_get (&search->priv->server);
616
* camel_imapx_search_set_server:
617
* @search: a #CamelIMAPXSearch
618
* @server: a #CamelIMAPXServer, or %NULL
620
* Sets a #CamelIMAPXServer to use for server-side searches. Generally
621
* this is set for the duration of a single search when online, and then
627
camel_imapx_search_set_server (CamelIMAPXSearch *search,
628
CamelIMAPXServer *server)
630
g_return_if_fail (CAMEL_IS_IMAPX_SEARCH (search));
633
g_return_if_fail (CAMEL_IS_IMAPX_SERVER (server));
635
g_weak_ref_set (&search->priv->server, server);
637
g_object_notify (G_OBJECT (search), "server");