1 /*
2 * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
3 *
4 * This library is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 #include "evolution-config.h"
19
20 #include <glib.h>
21 #include <glib/gi18n-lib.h>
22
23 #include <string.h>
24
25 #include <camel/camel.h>
26 #include <libedataserver/libedataserver.h>
27
28 #include "e-util/e-util.h"
29 #include "em-utils.h"
30
31 #include "e-mail-free-form-exp.h"
32
33 static gchar *
mail_ffe_build_header_sexp(const gchar * word,const gchar * options,const gchar * const * header_names)34 mail_ffe_build_header_sexp (const gchar *word,
35 const gchar *options,
36 const gchar * const *header_names)
37 {
38 GString *sexp = NULL, *encoded_word;
39 const gchar *compare_type = NULL;
40 gint ii;
41
42 g_return_val_if_fail (header_names != NULL, NULL);
43 g_return_val_if_fail (header_names[0] != NULL, NULL);
44
45 if (!word)
46 return NULL;
47
48 if (options) {
49 struct _KnownOptions {
50 const gchar *compare_type;
51 const gchar *alt_name;
52 } known_options[] = {
53 { "contains", "c" },
54 { "has-words", "w" },
55 { "matches", "m" },
56 { "starts-with", "sw" },
57 { "ends-with", "ew" },
58 { "soundex", "se" },
59 { "regex", "r" },
60 { "full-regex", "fr" }
61 };
62
63 for (ii = 0; ii < G_N_ELEMENTS (known_options); ii++) {
64 if (g_ascii_strcasecmp (options, known_options[ii].compare_type) == 0 ||
65 (known_options[ii].alt_name && g_ascii_strcasecmp (options, known_options[ii].alt_name) == 0)) {
66 compare_type = known_options[ii].compare_type;
67 break;
68 }
69 }
70 }
71
72 if (!compare_type)
73 compare_type = "contains";
74
75 encoded_word = g_string_new ("");
76 camel_sexp_encode_string (encoded_word, word);
77
78 if (!header_names[1]) {
79 sexp = g_string_new ("");
80 } else {
81 sexp = g_string_new ("(or ");
82 }
83
84 for (ii = 0; header_names[ii]; ii++) {
85 g_string_append_printf (sexp, "(header-%s \"%s\" %s)", compare_type, header_names[ii], encoded_word->str);
86 }
87
88 if (header_names[1])
89 g_string_append_c (sexp, ')');
90
91 g_string_free (encoded_word, TRUE);
92
93 return sexp ? g_string_free (sexp, FALSE) : NULL;
94 }
95
96 static gchar *
mail_ffe_recips(const gchar * word,const gchar * options,const gchar * hint)97 mail_ffe_recips (const gchar *word,
98 const gchar *options,
99 const gchar *hint)
100 {
101 const gchar *header_names[] = { "To", "Cc", "Subject", NULL };
102
103 /* Include Subject only in the default expression. */
104 if (!hint)
105 header_names[2] = NULL;
106
107 return mail_ffe_build_header_sexp (word, options, header_names);
108 }
109
110 static gchar *
mail_ffe_from(const gchar * word,const gchar * options,const gchar * hint)111 mail_ffe_from (const gchar *word,
112 const gchar *options,
113 const gchar *hint)
114 {
115 const gchar *header_names[] = { "From", NULL };
116
117 return mail_ffe_build_header_sexp (word, options, header_names);
118 }
119
120 static gchar *
mail_ffe_to(const gchar * word,const gchar * options,const gchar * hint)121 mail_ffe_to (const gchar *word,
122 const gchar *options,
123 const gchar *hint)
124 {
125 const gchar *header_names[] = { "To", NULL };
126
127 return mail_ffe_build_header_sexp (word, options, header_names);
128 }
129
130 static gchar *
mail_ffe_cc(const gchar * word,const gchar * options,const gchar * hint)131 mail_ffe_cc (const gchar *word,
132 const gchar *options,
133 const gchar *hint)
134 {
135 const gchar *header_names[] = { "Cc", NULL };
136
137 return mail_ffe_build_header_sexp (word, options, header_names);
138 }
139
140 static gchar *
mail_ffe_subject(const gchar * word,const gchar * options,const gchar * hint)141 mail_ffe_subject (const gchar *word,
142 const gchar *options,
143 const gchar *hint)
144 {
145 const gchar *header_names[] = { "Subject", NULL };
146
147 return mail_ffe_build_header_sexp (word, options, header_names);
148 }
149
150 static gchar *
mail_ffe_list(const gchar * word,const gchar * options,const gchar * hint)151 mail_ffe_list (const gchar *word,
152 const gchar *options,
153 const gchar *hint)
154 {
155 const gchar *header_names[] = { "x-camel-mlist", NULL };
156
157 return mail_ffe_build_header_sexp (word, options, header_names);
158 }
159
160 static gchar *
mail_ffe_header(const gchar * word,const gchar * options,const gchar * hint)161 mail_ffe_header (const gchar *word,
162 const gchar *options,
163 const gchar *hint)
164 {
165 const gchar *header_names[] = { NULL, NULL };
166 const gchar *equal;
167 gchar *header_name, *sexp;
168
169 equal = word ? strchr (word, '=') : NULL;
170 if (!equal)
171 return NULL;
172
173 header_name = g_strndup (word, equal - word);
174 header_names[0] = header_name;
175
176 sexp = mail_ffe_build_header_sexp (equal + 1, options, header_names);
177
178 g_free (header_name);
179
180 return sexp;
181 }
182
183 static gchar *
mail_ffe_exists(const gchar * word,const gchar * options,const gchar * hint)184 mail_ffe_exists (const gchar *word,
185 const gchar *options,
186 const gchar *hint)
187 {
188 GString *encoded_word;
189 gchar *sexp;
190
191 if (!word)
192 return NULL;
193
194 encoded_word = g_string_new ("");
195 camel_sexp_encode_string (encoded_word, word);
196
197 sexp = g_strdup_printf ("(header-exists %s)", encoded_word->str);
198
199 g_string_free (encoded_word, TRUE);
200
201 return sexp;
202 }
203
204 static gchar *
mail_ffe_tag(const gchar * word,const gchar * options,const gchar * hint)205 mail_ffe_tag (const gchar *word,
206 const gchar *options,
207 const gchar *hint)
208 {
209 GString *encoded_word;
210 gchar *sexp;
211
212 if (!word)
213 return NULL;
214
215 encoded_word = g_string_new ("");
216 camel_sexp_encode_string (encoded_word, word);
217
218 sexp = g_strdup_printf ("(not (= (user-tag %s) \"\"))", encoded_word->str);
219
220 g_string_free (encoded_word, TRUE);
221
222 return sexp;
223 }
224
225 static gchar *
mail_ffe_flag(const gchar * word,const gchar * options,const gchar * hint)226 mail_ffe_flag (const gchar *word,
227 const gchar *options,
228 const gchar *hint)
229 {
230 const gchar *system_flags[] = {
231 /* Translators: This is a name of a flag, the same as all strings in the 'ffe' context.
232 The translated value should not contain spaces. */
233 NC_("ffe", "Answered"),
234 NC_("ffe", "Deleted"),
235 NC_("ffe", "Draft"),
236 NC_("ffe", "Flagged"),
237 NC_("ffe", "Seen"),
238 NC_("ffe", "Attachment")
239 };
240 GString *encoded_word;
241 gchar *sexp = NULL;
242 gint ii;
243
244 if (!word)
245 return NULL;
246
247 encoded_word = g_string_new ("");
248 camel_sexp_encode_string (encoded_word, word);
249
250 for (ii = 0; ii < G_N_ELEMENTS (system_flags); ii++) {
251 const gchar *flag = system_flags[ii];
252
253 if (g_ascii_strcasecmp (word, flag) == 0 ||
254 g_ascii_strcasecmp (word, g_dpgettext2 (NULL, "ffe", flag)) == 0) {
255 if (g_ascii_strcasecmp (flag, "Attachment") == 0)
256 flag = "Attachments";
257
258 sexp = g_strdup_printf ("(system-flag \"%s\")", flag);
259 break;
260 }
261 }
262
263 if (!sexp)
264 sexp = g_strdup_printf ("(not (= (user-tag %s) \"\"))", encoded_word->str);
265
266 g_string_free (encoded_word, TRUE);
267
268 return sexp;
269 }
270
271 static gchar *
mail_ffe_label(const gchar * word,const gchar * options,const gchar * hint)272 mail_ffe_label (const gchar *word,
273 const gchar *options,
274 const gchar *hint)
275 {
276 GString *encoded_word;
277 gchar *sexp;
278
279 if (!word)
280 return NULL;
281
282 encoded_word = g_string_new ("");
283 camel_sexp_encode_string (encoded_word, word);
284
285 sexp = g_strdup_printf ("(or (= (user-tag \"label\") %s) (user-flag (+ \"$Label\" %s)) (user-flag %s))",
286 encoded_word->str, encoded_word->str, encoded_word->str);
287
288 g_string_free (encoded_word, TRUE);
289
290 return sexp;
291 }
292
293 static gchar *
mail_ffe_size(const gchar * word,const gchar * options,const gchar * hint)294 mail_ffe_size (const gchar *word,
295 const gchar *options,
296 const gchar *hint)
297 {
298 GString *encoded_word;
299 gchar *sexp;
300 const gchar *cmp = "=";
301
302 if (!word)
303 return NULL;
304
305 if (options) {
306 if (g_ascii_strcasecmp (options, "<") == 0 ||
307 g_ascii_strcasecmp (options, ">") == 0)
308 cmp = options;
309 }
310
311 encoded_word = g_string_new ("");
312 camel_sexp_encode_string (encoded_word, word);
313
314 sexp = g_strdup_printf ("(%s (get-size) (cast-int %s))", cmp, encoded_word->str);
315
316 g_string_free (encoded_word, TRUE);
317
318 return sexp;
319 }
320
321 static gchar *
mail_ffe_score(const gchar * word,const gchar * options,const gchar * hint)322 mail_ffe_score (const gchar *word,
323 const gchar *options,
324 const gchar *hint)
325 {
326 GString *encoded_word;
327 gchar *sexp;
328 const gchar *cmp = "=";
329
330 if (!word)
331 return NULL;
332
333 if (options) {
334 if (g_ascii_strcasecmp (options, "<") == 0 ||
335 g_ascii_strcasecmp (options, ">") == 0)
336 cmp = options;
337 }
338
339 encoded_word = g_string_new ("");
340 camel_sexp_encode_string (encoded_word, word);
341
342 sexp = g_strdup_printf ("(%s (cast-int (user-tag \"score\")) (cast-int %s))", cmp, encoded_word->str);
343
344 g_string_free (encoded_word, TRUE);
345
346 return sexp;
347 }
348
349 static gchar *
mail_ffe_body(const gchar * word,const gchar * options,const gchar * hint)350 mail_ffe_body (const gchar *word,
351 const gchar *options,
352 const gchar *hint)
353 {
354 GString *encoded_word;
355 gchar *sexp;
356 const gchar *cmp = "contains";
357
358 if (!word)
359 return NULL;
360
361 if (options) {
362 if (g_ascii_strcasecmp (options, "regex") == 0 ||
363 g_ascii_strcasecmp (options, "re") == 0 ||
364 g_ascii_strcasecmp (options, "r") == 0)
365 cmp = "regex";
366 }
367
368 encoded_word = g_string_new ("");
369 camel_sexp_encode_string (encoded_word, word);
370
371 sexp = g_strdup_printf ("(body-%s %s)", cmp, encoded_word->str);
372
373 g_string_free (encoded_word, TRUE);
374
375 return sexp;
376 }
377
378 static gboolean
mail_ffe_decode_date_time(const gchar * word,GTimeVal * tv)379 mail_ffe_decode_date_time (const gchar *word,
380 GTimeVal *tv)
381 {
382 struct tm tm;
383
384 g_return_val_if_fail (word != NULL, FALSE);
385 g_return_val_if_fail (tv != NULL, FALSE);
386
387 /* YYYY-MM-DD */
388 if (strlen (word) == 10 && word[4] == '-' && word[7] == '-') {
389 gint yy, mm, dd;
390
391 yy = atoi (word);
392 mm = atoi (word + 5);
393 dd = atoi (word + 8);
394
395 if (g_date_valid_dmy (dd, mm, yy)) {
396 GDate *date;
397
398 date = g_date_new_dmy (dd, mm, yy);
399 g_date_to_struct_tm (date, &tm);
400 g_date_free (date);
401
402 tv->tv_sec = mktime (&tm);
403 tv->tv_usec = 0;
404
405 return TRUE;
406 }
407 }
408
409 if (g_time_val_from_iso8601 (word, tv))
410 return TRUE;
411
412 if (e_time_parse_date_and_time (word, &tm) == E_TIME_PARSE_OK ||
413 e_time_parse_date (word, &tm) == E_TIME_PARSE_OK) {
414 tv->tv_sec = mktime (&tm);
415 tv->tv_usec = 0;
416
417 return TRUE;
418 }
419
420 return FALSE;
421 }
422
423 static gchar *
mail_ffe_process_date(const gchar * get_date_fnc,const gchar * word,const gchar * options)424 mail_ffe_process_date (const gchar *get_date_fnc,
425 const gchar *word,
426 const gchar *options)
427 {
428 gint64 rel_days;
429 gchar *endptr = NULL;
430 const gchar *op = ">";
431 GTimeVal tv;
432
433 g_return_val_if_fail (get_date_fnc != NULL, NULL);
434
435 if (options) {
436 if (g_ascii_strcasecmp (options, "<") == 0) {
437 op = "<";
438 } else if (g_ascii_strcasecmp (options, "=") == 0) {
439 op = "=";
440 } else if (g_ascii_strcasecmp (options, ">") == 0) {
441 op = ">";
442 }
443 }
444
445 rel_days = g_ascii_strtoll (word, &endptr, 10);
446 if (rel_days != 0 && endptr && !*endptr) {
447 return g_strdup_printf ("(%s (compare-date (%s) (%s (get-current-date) %" G_GINT64_FORMAT ")) 0)", op, get_date_fnc,
448 rel_days < 0 ? "+" : "-", (rel_days < 0 ? -1 : 1) * rel_days * 24 * 60 * 60);
449 }
450
451 if (!mail_ffe_decode_date_time (word, &tv))
452 return g_strdup_printf ("(%s (compare-date (%s) (get-current-date)) 0)", op, get_date_fnc);
453
454 return g_strdup_printf ("(%s (compare-date (%s) %" G_GINT64_FORMAT ") 0)", op, get_date_fnc, (gint64) tv.tv_sec);
455 }
456
457 static gchar *
mail_ffe_sent(const gchar * word,const gchar * options,const gchar * hint)458 mail_ffe_sent (const gchar *word,
459 const gchar *options,
460 const gchar *hint)
461 {
462 if (!word)
463 return NULL;
464
465 return mail_ffe_process_date ("get-sent-date", word, options);
466 }
467
468 static gchar *
mail_ffe_received(const gchar * word,const gchar * options,const gchar * hint)469 mail_ffe_received (const gchar *word,
470 const gchar *options,
471 const gchar *hint)
472 {
473 if (!word)
474 return NULL;
475
476 return mail_ffe_process_date ("get-received-date", word, options);
477 }
478
479 static gboolean
mail_ffe_is_neg(const gchar * value)480 mail_ffe_is_neg (const gchar *value)
481 {
482 return value &&
483 (g_ascii_strcasecmp (value, "!") == 0 ||
484 g_ascii_strcasecmp (value, "0") == 0 ||
485 g_ascii_strcasecmp (value, "no") == 0 ||
486 g_ascii_strcasecmp (value, "not") == 0 ||
487 g_ascii_strcasecmp (value, "false") == 0 ||
488 g_ascii_strcasecmp (value, C_("ffe", "no")) == 0 ||
489 g_ascii_strcasecmp (value, C_("ffe", "not")) == 0 ||
490 g_ascii_strcasecmp (value, C_("ffe", "false")) == 0);
491 }
492
493 static gchar *
mail_ffe_attachment(const gchar * word,const gchar * options,const gchar * hint)494 mail_ffe_attachment (const gchar *word,
495 const gchar *options,
496 const gchar *hint)
497 {
498 gboolean is_neg;
499
500 if (!word)
501 return NULL;
502
503 is_neg = mail_ffe_is_neg (word);
504
505 return g_strdup_printf ("%s(system-flag \"Attachments\")%s", is_neg ? "(not " : "", is_neg ? ")" : "");
506 }
507
508 static gchar *
mail_ffe_location(const gchar * word,const gchar * options,const gchar * hint)509 mail_ffe_location (const gchar *word,
510 const gchar *options,
511 const gchar *hint)
512 {
513 GString *encoded_uri;
514 gchar *sexp, *folder_uri;
515 gboolean is_neg;
516
517 if (!word)
518 return NULL;
519
520 is_neg = mail_ffe_is_neg (options);
521
522 folder_uri = em_utils_account_path_to_folder_uri (NULL, word);
523
524 if (!folder_uri)
525 return NULL;
526
527 encoded_uri = g_string_new ("");
528 camel_sexp_encode_string (encoded_uri, folder_uri);
529
530 sexp = g_strdup_printf ("%s(message-location %s)%s", is_neg ? "(not " : "", encoded_uri->str, is_neg ? ")" : "");
531
532 g_string_free (encoded_uri, TRUE);
533 g_free (folder_uri);
534
535 return sexp;
536 }
537
538 static gchar *
mail_ffe_message_id(const gchar * word,const gchar * options,const gchar * hint)539 mail_ffe_message_id (const gchar *word,
540 const gchar *options,
541 const gchar *hint)
542 {
543 GString *encoded_mid;
544 gchar *sexp;
545
546 if (!word)
547 return NULL;
548
549 encoded_mid = g_string_new ("");
550 camel_sexp_encode_string (encoded_mid, word);
551
552 sexp = g_strdup_printf ("(header-matches \"MESSAGE-ID\" %s)", encoded_mid->str);
553
554 g_string_free (encoded_mid, TRUE);
555
556 return sexp;
557 }
558
559 static const EFreeFormExpSymbol mail_ffe_symbols[] = {
560 { "", "1", mail_ffe_recips },
561 { "from:f", NULL, mail_ffe_from },
562 { "to:t", NULL, mail_ffe_to },
563 { "cc:c:", NULL, mail_ffe_cc },
564 { "recips:r", NULL, mail_ffe_recips },
565 { "subject:s", NULL, mail_ffe_subject },
566 { "list", NULL, mail_ffe_list },
567 { "header:h", NULL, mail_ffe_header },
568 { "exists:e", NULL, mail_ffe_exists },
569 { "tag", NULL, mail_ffe_tag },
570 { "flag", NULL, mail_ffe_flag },
571 { "label:l", NULL, mail_ffe_label },
572 { "size:sz", NULL, mail_ffe_size },
573 { "score:sc", NULL, mail_ffe_score },
574 { "body:b", NULL, mail_ffe_body },
575 { "sent", NULL, mail_ffe_sent },
576 { "received:rcv", NULL, mail_ffe_received },
577 { "attachment:a", NULL, mail_ffe_attachment },
578 { "location:m", NULL, mail_ffe_location },
579 { "mid", NULL, mail_ffe_message_id },
580 { NULL, NULL, NULL}
581 };
582
583 static gchar *
get_filter_input_value(EFilterPart * part,const gchar * name)584 get_filter_input_value (EFilterPart *part,
585 const gchar *name)
586 {
587 EFilterElement *elem;
588 EFilterInput *input;
589 GString *value;
590 GList *link;
591
592 g_return_val_if_fail (part != NULL, NULL);
593 g_return_val_if_fail (name != NULL, NULL);
594
595 elem = e_filter_part_find_element (part, name);
596 g_return_val_if_fail (elem != NULL, NULL);
597 g_return_val_if_fail (E_IS_FILTER_INPUT (elem), NULL);
598
599 input = E_FILTER_INPUT (elem);
600 value = g_string_new ("");
601
602 for (link = input->values; link; link = g_list_next (link)) {
603 const gchar *val = link->data;
604
605 if (val && *val) {
606 if (value->len > 0)
607 g_string_append_c (value, ' ');
608 g_string_append (value, val);
609 }
610 }
611
612 return g_string_free (value, FALSE);
613 }
614
615 void
e_mail_free_form_exp_to_sexp(EFilterElement * element,GString * out,EFilterPart * part)616 e_mail_free_form_exp_to_sexp (EFilterElement *element,
617 GString *out,
618 EFilterPart *part)
619 {
620 gchar *ffe, *sexp;
621
622 ffe = get_filter_input_value (part, "ffe");
623 g_return_if_fail (ffe != NULL);
624
625 sexp = e_free_form_exp_to_sexp (ffe, mail_ffe_symbols);
626 if (sexp)
627 g_string_append (out, sexp);
628
629 g_free (sexp);
630 g_free (ffe);
631 }
632