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